From 3fbece25ffc2be68d415b2c3fb31235c2b92c22c Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 11:43:30 -0800 Subject: [PATCH 01/21] feat(enhanced): make hoisted runtime the default implementation --- apps/modernjs/modern.config.ts | 3 - apps/next-app-router-4000/.env | 2 + apps/next-app-router-4001/.env | 2 + .../src/lib/container/ContainerEntryModule.ts | 18 +- .../lib/container/ModuleFederationPlugin.ts | 6 +- .../runtime/FederationRuntimePlugin.ts | 132 ++-- .../container/ContainerPlugin.check.ts | 734 +++--------------- .../src/schemas/container/ContainerPlugin.ts | 41 +- .../ContainerReferencePlugin.check.ts | 5 - .../ModuleFederationPlugin.check.d.ts | 5 - .../src/plugins/NextFederationPlugin/index.ts | 3 - .../sdk/src/types/plugins/ContainerPlugin.ts | 3 - .../types/plugins/ModuleFederationPlugin.ts | 2 +- 13 files changed, 166 insertions(+), 790 deletions(-) create mode 100644 apps/next-app-router-4000/.env create mode 100644 apps/next-app-router-4001/.env diff --git a/apps/modernjs/modern.config.ts b/apps/modernjs/modern.config.ts index 9736d3dd2cc..e503e34552c 100644 --- a/apps/modernjs/modern.config.ts +++ b/apps/modernjs/modern.config.ts @@ -60,9 +60,6 @@ export default defineConfig({ requiredVersion: '^18.3.1', }, }, - experiments: { - federationRuntime: 'hoisted', - }, dataPrefetch: true, }) as any, ]); diff --git a/apps/next-app-router-4000/.env b/apps/next-app-router-4000/.env new file mode 100644 index 00000000000..70b0efc2843 --- /dev/null +++ b/apps/next-app-router-4000/.env @@ -0,0 +1,2 @@ +NEXT_PRIVATE_LOCAL_WEBPACK=true +NODE_OPTIONS="--experimental-vm-modules" diff --git a/apps/next-app-router-4001/.env b/apps/next-app-router-4001/.env new file mode 100644 index 00000000000..70b0efc2843 --- /dev/null +++ b/apps/next-app-router-4001/.env @@ -0,0 +1,2 @@ +NEXT_PRIVATE_LOCAL_WEBPACK=true +NODE_OPTIONS="--experimental-vm-modules" diff --git a/packages/enhanced/src/lib/container/ContainerEntryModule.ts b/packages/enhanced/src/lib/container/ContainerEntryModule.ts index 58e2461b2c0..bbe480bbcf2 100644 --- a/packages/enhanced/src/lib/container/ContainerEntryModule.ts +++ b/packages/enhanced/src/lib/container/ContainerEntryModule.ts @@ -203,9 +203,8 @@ class ContainerEntryModule extends Module { ) as unknown as Dependency, ); - if (!this._experiments?.federationRuntime) { - this.addDependency(new EntryDependency(this._injectRuntimeEntry)); - } + this.addDependency(new EntryDependency(this._injectRuntimeEntry)); + callback(); } @@ -272,18 +271,6 @@ class ContainerEntryModule extends Module { )}`, ); } - const initRuntimeDep = this.dependencies[1]; - // no runtime module getter needed if runtime is hoisted - const initRuntimeModuleGetter = this._experiments?.federationRuntime - ? '' - : runtimeTemplate.moduleRaw({ - module: moduleGraph.getModule(initRuntimeDep), - chunkGraph, - // @ts-expect-error flaky type definition for Dependency - request: initRuntimeDep.userRequest, - weak: false, - runtimeRequirements, - }); const federationGlobal = getFederationGlobalScope( RuntimeGlobals || ({} as typeof RuntimeGlobals), ); @@ -326,7 +313,6 @@ class ContainerEntryModule extends Module { ], )};`, this._dataPrefetch ? PrefetchPlugin.setRemoteIdentifier() : '', - `${initRuntimeModuleGetter}`, this._dataPrefetch ? PrefetchPlugin.removeRemoteIdentifier() : '', '// This exports getters to disallow modifications', `${RuntimeGlobals.definePropertyGetters}(exports, {`, diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 3dd5dbb9e1d..18a4f863145 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -93,8 +93,10 @@ class ModuleFederationPlugin implements WebpackPluginInstance { }).apply(compiler); } - if (options.experiments?.federationRuntime) { - new FederationModulesPlugin().apply(compiler); + // federation hooks + new FederationModulesPlugin().apply(compiler); + + if (options.experiments?.asyncStartup) { new StartupChunkDependenciesPlugin({ asyncChunkLoading: true, }).apply(compiler); diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 6d9c6667b41..34eec96bf74 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -47,12 +47,9 @@ const BundlerRuntimePath = require.resolve( const RuntimePath = require.resolve('@module-federation/runtime', { paths: [RuntimeToolsPath], }); -const EmbeddedRuntimePath = require.resolve( - '@module-federation/runtime/embedded', - { - paths: [RuntimeToolsPath], - }, -); +const EmbeddedRuntimePath = require.resolve('@module-federation/runtime-core', { + paths: [RuntimeToolsPath], +}); const federationGlobal = getFederationGlobalScope(RuntimeGlobals); @@ -167,6 +164,7 @@ class FederationRuntimePlugin { ); return path.join(TEMP_DIR, `entry.${hash}.js`); } + getFilePath(compiler: Compiler) { if (this.entryFilePath) { return this.entryFilePath; @@ -195,6 +193,7 @@ class FederationRuntimePlugin { } return this.entryFilePath; } + ensureFile(compiler: Compiler) { if (!this.options) { return; @@ -237,61 +236,38 @@ class FederationRuntimePlugin { this.ensureFile(compiler); } - //if using runtime experiment, use the new include method else patch entry - if (this.options?.experiments?.federationRuntime) { - compiler.hooks.thisCompilation.tap( - this.constructor.name, - (compilation: Compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - FederationRuntimeDependency, - normalModuleFactory, - ); - compilation.dependencyTemplates.set( - FederationRuntimeDependency, - new ModuleDependency.Template(), - ); - }, - ); - compiler.hooks.make.tapAsync( - this.constructor.name, - (compilation: Compilation, callback) => { - const federationRuntimeDependency = this.getDependency(compiler); - const hooks = - FederationModulesPlugin.getCompilationHooks(compilation); - compilation.addInclude( - compiler.context, - federationRuntimeDependency, - { name: undefined }, - (err, module) => { - if (err) { - return callback(err); - } - hooks.addFederationRuntimeModule.call( - federationRuntimeDependency, - ); - callback(); - }, - ); - }, - ); - } else { - const entryFilePath = this.getFilePath(compiler); - modifyEntry({ - compiler, - prependEntry: (entry: Record) => { - Object.keys(entry).forEach((entryName) => { - const entryItem = entry[entryName]; - if (!entryItem.import) { - // TODO: maybe set this variable as constant is better https://github.com/webpack/webpack/blob/main/lib/config/defaults.js#L176 - entryItem.import = ['./src']; - } - if (!entryItem.import.includes(entryFilePath)) { - entryItem.import.unshift(entryFilePath); + compiler.hooks.thisCompilation.tap( + this.constructor.name, + (compilation: Compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + FederationRuntimeDependency, + normalModuleFactory, + ); + compilation.dependencyTemplates.set( + FederationRuntimeDependency, + new ModuleDependency.Template(), + ); + }, + ); + compiler.hooks.make.tapAsync( + this.constructor.name, + (compilation: Compilation, callback) => { + const federationRuntimeDependency = this.getDependency(compiler); + const hooks = FederationModulesPlugin.getCompilationHooks(compilation); + compilation.addInclude( + compiler.context, + federationRuntimeDependency, + { name: undefined }, + (err, module) => { + if (err) { + return callback(err); } - }); - }, - }); - } + hooks.addFederationRuntimeModule.call(federationRuntimeDependency); + callback(); + }, + ); + }, + ); } injectRuntime(compiler: Compiler) { @@ -360,14 +336,12 @@ class FederationRuntimePlugin { setRuntimeAlias(compiler: Compiler) { const { experiments, implementation } = this.options || {}; - const isHoisted = experiments?.federationRuntime === 'hoisted'; - let runtimePath = isHoisted ? EmbeddedRuntimePath : RuntimePath; + let runtimePath = EmbeddedRuntimePath; if (implementation) { - runtimePath = require.resolve( - `@module-federation/runtime${isHoisted ? '/embedded' : ''}`, - { paths: [implementation] }, - ); + runtimePath = require.resolve(`@module-federation/runtime/embedded`, { + paths: [implementation], + }); } const alias: any = compiler.options.resolve.alias || {}; @@ -432,24 +406,22 @@ class FederationRuntimePlugin { ); } - if (this.options?.experiments?.federationRuntime === 'hoisted') { - new EmbedFederationRuntimePlugin().apply(compiler); + new EmbedFederationRuntimePlugin().apply(compiler); - new HoistContainerReferences().apply(compiler); + new HoistContainerReferences().apply(compiler); - new compiler.webpack.NormalModuleReplacementPlugin( - /@module-federation\/runtime/, - (resolveData) => { - if (/webpack-bundler-runtime/.test(resolveData.contextInfo.issuer)) { - resolveData.request = RuntimePath; + new compiler.webpack.NormalModuleReplacementPlugin( + /@module-federation\/runtime/, + (resolveData) => { + if (/webpack-bundler-runtime/.test(resolveData.contextInfo.issuer)) { + resolveData.request = RuntimePath; - if (resolveData.createData) { - resolveData.createData.request = resolveData.request; - } + if (resolveData.createData) { + resolveData.createData.request = resolveData.request; } - }, - ).apply(compiler); - } + } + }, + ).apply(compiler); // dont run multiple times on every apply() if (!onceForCompler.has(compiler)) { this.prependEntry(compiler); diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts index c80302d72da..630147d788a 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts @@ -1,12 +1,6 @@ /* eslint-disable */ //@ts-nocheck -/* - * This file was automatically generated. - * DO NOT MODIFY BY HAND. - * Run `yarn special-lint-fix` to update - */ const r = /^(?:[A-Za-z]:[\\/]|\\\\|\/)/; - function t( r, { @@ -17,47 +11,20 @@ function t( } = {}, ) { if (!Array.isArray(r)) - return ( - (t.errors = [ - { - params: { - type: 'array', - }, - }, - ]), - !1 - ); + return (t.errors = [{ params: { type: 'array' } }]), !1; { const e = r.length; for (let n = 0; n < e; n++) { let e = r[n]; const s = 0; if ('string' != typeof e) - return ( - (t.errors = [ - { - params: { - type: 'string', - }, - }, - ]), - !1 - ); - if (e.length < 1) - return ( - (t.errors = [ - { - params: {}, - }, - ]), - !1 - ); + return (t.errors = [{ params: { type: 'string' } }]), !1; + if (e.length < 1) return (t.errors = [{ params: {} }]), !1; if (0 !== s) break; } } return (t.errors = null), !0; } - function e( r, { @@ -71,43 +38,16 @@ function e( l = 0; if (0 === l) { if (!r || 'object' != typeof r || Array.isArray(r)) - return ( - (e.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); + return (e.errors = [{ params: { type: 'object' } }]), !1; { let s; if (void 0 === r.import && (s = 'import')) - return ( - (e.errors = [ - { - params: { - missingProperty: s, - }, - }, - ]), - !1 - ); + return (e.errors = [{ params: { missingProperty: s } }]), !1; { const s = l; for (const t in r) if ('import' !== t && 'name' !== t) - return ( - (e.errors = [ - { - params: { - additionalProperty: t, - }, - }, - ]), - !1 - ); + return (e.errors = [{ params: { additionalProperty: t } }]), !1; if (s === l) { if (void 0 !== r.import) { let s = r.import; @@ -118,17 +58,11 @@ function e( if (l == l) if ('string' == typeof s) { if (s.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === i ? (i = [r]) : i.push(r), l++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === i ? (i = [r]) : i.push(r), l++; } var p = m === l; @@ -146,9 +80,7 @@ function e( (c = c || p); } if (!c) { - const r = { - params: {}, - }; + const r = { params: {} }; return ( null === i ? (i = [r]) : i.push(r), l++, (e.errors = i), !1 ); @@ -160,16 +92,7 @@ function e( if (void 0 !== r.name) { const t = l; if ('string' != typeof r.name) - return ( - (e.errors = [ - { - params: { - type: 'string', - }, - }, - ]), - !1 - ); + return (e.errors = [{ params: { type: 'string' } }]), !1; f = t === l; } else f = !0; } @@ -178,7 +101,6 @@ function e( } return (e.errors = i), 0 === l; } - function n( r, { @@ -192,16 +114,7 @@ function n( p = 0; if (0 === p) { if (!r || 'object' != typeof r || Array.isArray(r)) - return ( - (n.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); + return (n.errors = [{ params: { type: 'object' } }]), !1; for (const a in r) { let o = r[a]; const u = p, @@ -220,17 +133,11 @@ function n( if (p == p) if ('string' == typeof o) { if (o.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === l ? (l = [r]) : l.push(r), p++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } if (((f = e === p), (m = m || f), !m)) { @@ -247,9 +154,7 @@ function n( } } if (!m) { - const r = { - params: {}, - }; + const r = { params: {} }; return null === l ? (l = [r]) : l.push(r), p++, (n.errors = l), !1; } if (((p = c), null !== l && (c ? (l.length = c) : (l = null)), u !== p)) @@ -258,7 +163,6 @@ function n( } return (n.errors = l), 0 === p; } - function s( r, { @@ -285,17 +189,11 @@ function s( if (l == l) if ('string' == typeof e) { if (e.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === i ? (i = [r]) : i.push(r), l++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === i ? (i = [r]) : i.push(r), l++; } var c = u === l; @@ -313,19 +211,13 @@ function s( } if (f) (l = p), null !== i && (p ? (i.length = p) : (i = null)); else { - const r = { - params: {}, - }; + const r = { params: {} }; null === i ? (i = [r]) : i.push(r), l++; } if (a !== l) break; } } else { - const r = { - params: { - type: 'array', - }, - }; + const r = { params: { type: 'array' } }; null === i ? (i = [r]) : i.push(r), l++; } var m = u === l; @@ -341,9 +233,7 @@ function s( (f = f || m); } if (!f) { - const r = { - params: {}, - }; + const r = { params: {} }; return null === i ? (i = [r]) : i.push(r), l++, (s.errors = i), !1; } return ( @@ -353,7 +243,6 @@ function s( 0 === l ); } - function a( r, { @@ -369,11 +258,7 @@ function a( let p = !1; const f = i; if ('string' != typeof r) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === o ? (o = [r]) : o.push(r), i++; } var u = f === i; @@ -389,11 +274,7 @@ function a( 'commonjs2' !== t && 'root' !== t ) { - const r = { - params: { - additionalProperty: t, - }, - }; + const r = { params: { additionalProperty: t } }; null === o ? (o = [r]) : o.push(r), i++; break; } @@ -401,11 +282,7 @@ function a( if (void 0 !== r.amd) { const t = i; if ('string' != typeof r.amd) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === o ? (o = [r]) : o.push(r), i++; } var c = t === i; @@ -414,11 +291,7 @@ function a( if (void 0 !== r.commonjs) { const t = i; if ('string' != typeof r.commonjs) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === o ? (o = [r]) : o.push(r), i++; } c = t === i; @@ -427,11 +300,7 @@ function a( if (void 0 !== r.commonjs2) { const t = i; if ('string' != typeof r.commonjs2) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === o ? (o = [r]) : o.push(r), i++; } c = t === i; @@ -440,11 +309,7 @@ function a( if (void 0 !== r.root) { const t = i; if ('string' != typeof r.root) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === o ? (o = [r]) : o.push(r), i++; } c = t === i; @@ -453,19 +318,13 @@ function a( } } } else { - const r = { - params: { - type: 'object', - }, - }; + const r = { params: { type: 'object' } }; null === o ? (o = [r]) : o.push(r), i++; } (u = t === i), (p = p || u); } if (!p) { - const r = { - params: {}, - }; + const r = { params: {} }; return null === o ? (o = [r]) : o.push(r), i++, (a.errors = o), !1; } return ( @@ -475,7 +334,6 @@ function a( 0 === i ); } - function o( r, { @@ -493,11 +351,7 @@ function o( if (i === f) if (Array.isArray(r)) if (r.length < 1) { - const r = { - params: { - limit: 1, - }, - }; + const r = { params: { limit: 1 } }; null === a ? (a = [r]) : a.push(r), i++; } else { const t = r.length; @@ -507,28 +361,18 @@ function o( if (i === n) if ('string' == typeof t) { if (t.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } if (n !== i) break; } } else { - const r = { - params: { - type: 'array', - }, - }; + const r = { params: { type: 'array' } }; null === a ? (a = [r]) : a.push(r), i++; } var u = f === i; @@ -537,17 +381,11 @@ function o( if (i === t) if ('string' == typeof r) { if (r.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } if (((u = t === i), (p = p || u), !p)) { @@ -557,11 +395,7 @@ function o( const t = i; for (const t in r) if ('amd' !== t && 'commonjs' !== t && 'root' !== t) { - const r = { - params: { - additionalProperty: t, - }, - }; + const r = { params: { additionalProperty: t } }; null === a ? (a = [r]) : a.push(r), i++; break; } @@ -572,17 +406,11 @@ function o( if (i === e) if ('string' == typeof t) { if (t.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } var c = e === i; @@ -594,17 +422,11 @@ function o( if (i === e) if ('string' == typeof t) { if (t.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } c = e === i; @@ -625,27 +447,17 @@ function o( if (i === n) if ('string' == typeof r) { if (r.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } if (n !== i) break; } } else { - const r = { - params: { - type: 'array', - }, - }; + const r = { params: { type: 'array' } }; null === a ? (a = [r]) : a.push(r), i++; } var m = o === i; @@ -654,17 +466,11 @@ function o( if (i === r) if ('string' == typeof t) { if (t.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === a ? (a = [r]) : a.push(r), i++; } (m = r === i), (s = s || m); @@ -672,9 +478,7 @@ function o( if (s) (i = n), null !== a && (n ? (a.length = n) : (a = null)); else { - const r = { - params: {}, - }; + const r = { params: {} }; null === a ? (a = [r]) : a.push(r), i++; } c = e === i; @@ -682,20 +486,14 @@ function o( } } } else { - const r = { - params: { - type: 'object', - }, - }; + const r = { params: { type: 'object' } }; null === a ? (a = [r]) : a.push(r), i++; } (u = t === i), (p = p || u); } } if (!p) { - const r = { - params: {}, - }; + const r = { params: {} }; return null === a ? (a = [r]) : a.push(r), i++, (o.errors = a), !1; } return ( @@ -705,7 +503,6 @@ function o( 0 === i ); } - function i( r, { @@ -719,29 +516,11 @@ function i( p = 0; if (0 === p) { if (!r || 'object' != typeof r || Array.isArray(r)) - return ( - (i.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); + return (i.errors = [{ params: { type: 'object' } }]), !1; { let e; if (void 0 === r.type && (e = 'type')) - return ( - (i.errors = [ - { - params: { - missingProperty: e, - }, - }, - ]), - !1 - ); + return (i.errors = [{ params: { missingProperty: e } }]), !1; { const e = p; for (const t in r) @@ -753,41 +532,15 @@ function i( 'type' !== t && 'umdNamedDefine' !== t ) - return ( - (i.errors = [ - { - params: { - additionalProperty: t, - }, - }, - ]), - !1 - ); + return (i.errors = [{ params: { additionalProperty: t } }]), !1; if (e === p) { if (void 0 !== r.amdContainer) { let t = r.amdContainer; const e = p; if (p == p) { if ('string' != typeof t) - return ( - (i.errors = [ - { - params: { - type: 'string', - }, - }, - ]), - !1 - ); - if (t.length < 1) - return ( - (i.errors = [ - { - params: {}, - }, - ]), - !1 - ); + return (i.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (i.errors = [{ params: {} }]), !1; } var f = e === p; } else f = !0; @@ -820,27 +573,17 @@ function i( if (p === n) if ('string' == typeof r) { if (r.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === l ? (l = [r]) : l.push(r), p++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } if (n !== p) break; } } else { - const r = { - params: { - type: 'array', - }, - }; + const r = { params: { type: 'array' } }; null === l ? (l = [r]) : l.push(r), p++; } var u = a === p; @@ -849,25 +592,17 @@ function i( if (p === r) if ('string' == typeof t) { if (t.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === l ? (l = [r]) : l.push(r), p++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } (u = r === p), (s = s || u); } if (!s) { - const r = { - params: {}, - }; + const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), p++, (i.errors = l), !1 ); @@ -916,28 +651,20 @@ function i( 'jsonp' !== t && 'system' !== t ) { - const r = { - params: {}, - }; + const r = { params: {} }; null === l ? (l = [r]) : l.push(r), p++; } var c = a === p; if (((s = s || c), !s)) { const r = p; if ('string' != typeof t) { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === l ? (l = [r]) : l.push(r), p++; } (c = r === p), (s = s || c); } if (!s) { - const r = { - params: {}, - }; + const r = { params: {} }; return ( null === l ? (l = [r]) : l.push(r), p++, @@ -954,14 +681,7 @@ function i( const t = p; if ('boolean' != typeof r.umdNamedDefine) return ( - (i.errors = [ - { - params: { - type: 'boolean', - }, - }, - ]), - !1 + (i.errors = [{ params: { type: 'boolean' } }]), !1 ); f = t === p; } else f = !0; @@ -975,7 +695,6 @@ function i( } return (i.errors = l), 0 === p; } - function l( t, { @@ -989,32 +708,14 @@ function l( f = 0; if (0 === f) { if (!t || 'object' != typeof t || Array.isArray(t)) - return ( - (l.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); + return (l.errors = [{ params: { type: 'object' } }]), !1; { let n; if ( (void 0 === t.name && (n = 'name')) || (void 0 === t.exposes && (n = 'exposes')) ) - return ( - (l.errors = [ - { - params: { - missingProperty: n, - }, - }, - ]), - !1 - ); + return (l.errors = [{ params: { missingProperty: n } }]), !1; { const n = f; for (const r in t) @@ -1028,96 +729,50 @@ function l( 'runtimePlugins' !== r && 'shareScope' !== r ) - return ( - (l.errors = [ - { - params: { - additionalProperty: r, - }, - }, - ]), - !1 - ); + return (l.errors = [{ params: { additionalProperty: r } }]), !1; if (n === f) { if (void 0 !== t.experiments) { let r = t.experiments; const e = f; if (f === e) { if (!r || 'object' != typeof r || Array.isArray(r)) - return ( - (l.errors = [ - { - params: { - type: 'object', - }, - }, - ]), - !1 - ); + return (l.errors = [{ params: { type: 'object' } }]), !1; { const t = f; for (const t in r) - if ( - 'externalRuntime' !== t && - 'federationRuntime' !== t && - 'provideExternalRuntime' !== t - ) + if ('asyncStartup' !== t && 'externalRuntime' !== t) return ( - (l.errors = [ - { - params: { - additionalProperty: t, - }, - }, - ]), - !1 + (l.errors = [{ params: { additionalProperty: t } }]), !1 ); if (t === f) { - if (void 0 !== r.externalRuntime) { + if (void 0 !== r.asyncStartup) { const t = f; - if ('boolean' != typeof r.externalRuntime) - return ( - (l.errors = [ - { - params: { - type: 'boolean', - }, - }, - ]), - !1 - ); + if ('boolean' != typeof r.asyncStartup) + return (l.errors = [{ params: { type: 'boolean' } }]), !1; var u = t === f; } else u = !0; - if (u) { - if (void 0 !== r.federationRuntime) { - let t = r.federationRuntime; + if (u) + if (void 0 !== r.externalRuntime) { + let t = r.externalRuntime; const e = f, n = f; let s = !1; const a = f; - if ('hoisted' !== t) { - const r = { - params: {}, - }; + if ('provide' !== t) { + const r = { params: {} }; null === p ? (p = [r]) : p.push(r), f++; } var c = a === f; if (((s = s || c), !s)) { const r = f; if ('boolean' != typeof t) { - const r = { - params: { - type: 'boolean', - }, - }; + const r = { params: { type: 'boolean' } }; null === p ? (p = [r]) : p.push(r), f++; } (c = r === f), (s = s || c); } if (!s) { - const r = { - params: {}, - }; + const r = { params: {} }; return ( null === p ? (p = [r]) : p.push(r), f++, @@ -1129,23 +784,6 @@ function l( null !== p && (n ? (p.length = n) : (p = null)), (u = e === f); } else u = !0; - if (u) - if (void 0 !== r.provideExternalRuntime) { - const t = f; - if ('boolean' != typeof r.provideExternalRuntime) - return ( - (l.errors = [ - { - params: { - type: 'boolean', - }, - }, - ]), - !1 - ); - u = t === f; - } else u = !0; - } } } } @@ -1170,34 +808,10 @@ function l( const n = f; if (f === n) { if ('string' != typeof e) - return ( - (l.errors = [ - { - params: { - type: 'string', - }, - }, - ]), - !1 - ); + return (l.errors = [{ params: { type: 'string' } }]), !1; if (e.includes('!') || !1 !== r.test(e)) - return ( - (l.errors = [ - { - params: {}, - }, - ]), - !1 - ); - if (e.length < 1) - return ( - (l.errors = [ - { - params: {}, - }, - ]), - !1 - ); + return (l.errors = [{ params: {} }]), !1; + if (e.length < 1) return (l.errors = [{ params: {} }]), !1; } m = n === f; } else m = !0; @@ -1221,24 +835,10 @@ function l( if (f === e) { if ('string' != typeof r) return ( - (l.errors = [ - { - params: { - type: 'string', - }, - }, - ]), - !1 + (l.errors = [{ params: { type: 'string' } }]), !1 ); if (r.length < 1) - return ( - (l.errors = [ - { - params: {}, - }, - ]), - !1 - ); + return (l.errors = [{ params: {} }]), !1; } m = e === f; } else m = !0; @@ -1250,9 +850,7 @@ function l( let s = !1; const a = f; if (!1 !== r) { - const r = { - params: {}, - }; + const r = { params: {} }; null === p ? (p = [r]) : p.push(r), f++; } var y = a === f; @@ -1261,25 +859,17 @@ function l( if (f === t) if ('string' == typeof r) { if (r.length < 1) { - const r = { - params: {}, - }; + const r = { params: {} }; null === p ? (p = [r]) : p.push(r), f++; } } else { - const r = { - params: { - type: 'string', - }, - }; + const r = { params: { type: 'string' } }; null === p ? (p = [r]) : p.push(r), f++; } (y = t === f), (s = s || y); } if (!s) { - const r = { - params: {}, - }; + const r = { params: {} }; return ( null === p ? (p = [r]) : p.push(r), f++, @@ -1298,143 +888,18 @@ function l( if (f === e) { if (!Array.isArray(r)) return ( - (l.errors = [ - { - params: { - type: 'array', - }, - }, - ]), - !1 + (l.errors = [{ params: { type: 'array' } }]), !1 ); { const t = r.length; for (let e = 0; e < t; e++) { - let t = r[e]; - const n = f, - s = f; - let a = !1; - const o = f; - if (f === o) - if ('string' == typeof t) { - if (t.length < 1) { - const r = { - params: {}, - }; - null === p ? (p = [r]) : p.push(r), f++; - } - } else { - const r = { - params: { - type: 'string', - }, - }; - null === p ? (p = [r]) : p.push(r), f++; - } - var h = o === f; - if (((a = a || h), !a)) { - const r = f; - if (f === r) - if ( - t && - 'object' == typeof t && - !Array.isArray(t) - ) { - let r; - if ( - (void 0 === t.import && (r = 'import')) || - (void 0 === t.async && (r = 'async')) - ) { - const t = { - params: { - missingProperty: r, - }, - }; - null === p ? (p = [t]) : p.push(t), f++; - } else { - const r = f; - for (const r in t) - if ('async' !== r && 'import' !== r) { - const t = { - params: { - additionalProperty: r, - }, - }; - null === p ? (p = [t]) : p.push(t), - f++; - break; - } - if (r === f) { - if (void 0 !== t.async) { - const r = f; - if ('boolean' != typeof t.async) { - const r = { - params: { - type: 'boolean', - }, - }; - null === p ? (p = [r]) : p.push(r), - f++; - } - var g = r === f; - } else g = !0; - if (g) - if (void 0 !== t.import) { - let r = t.import; - const e = f; - if (f === e) - if ('string' == typeof r) { - if (r.length < 1) { - const r = { - params: {}, - }; - null === p - ? (p = [r]) - : p.push(r), - f++; - } - } else { - const r = { - params: { - type: 'string', - }, - }; - null === p - ? (p = [r]) - : p.push(r), - f++; - } - g = e === f; - } else g = !0; - } - } - } else { - const r = { - params: { - type: 'object', - }, - }; - null === p ? (p = [r]) : p.push(r), f++; - } - (h = r === f), (a = a || h); - } - if (!a) { - const r = { - params: {}, - }; + const t = f; + if ('string' != typeof r[e]) return ( - null === p ? (p = [r]) : p.push(r), - f++, - (l.errors = p), + (l.errors = [{ params: { type: 'string' } }]), !1 ); - } - if ( - ((f = s), - null !== p && (s ? (p.length = s) : (p = null)), - n !== f) - ) - break; + if (t !== f) break; } } } @@ -1447,24 +912,11 @@ function l( if (f === e) { if ('string' != typeof r) return ( - (l.errors = [ - { - params: { - type: 'string', - }, - }, - ]), + (l.errors = [{ params: { type: 'string' } }]), !1 ); if (r.length < 1) - return ( - (l.errors = [ - { - params: {}, - }, - ]), - !1 - ); + return (l.errors = [{ params: {} }]), !1; } m = e === f; } else m = !0; diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.ts b/packages/enhanced/src/schemas/container/ContainerPlugin.ts index 58747f5a66d..054903d8efc 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.ts +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.ts @@ -1,31 +1,4 @@ //@ts-nocheck -const runtimePlugin = { - type: 'array', - items: { - anyOf: [ - { - type: 'string', - minLength: 1, - description: 'Runtime Plugin File Path.', - }, - { - type: 'object', - required: ['import', 'async'], - properties: { - import: { - type: 'string', - minLength: 1, - description: 'Runtime Plugin File Path.', - }, - async: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - }, -}; export default { definitions: { @@ -330,7 +303,13 @@ export default { runtime: { $ref: '#/definitions/EntryRuntime', }, - runtimePlugins: runtimePlugin, + runtimePlugins: { + description: 'Runtime plugin file paths or package name.', + type: 'array', + items: { + type: 'string', + }, + }, shareScope: { description: "The name of the share scope which is shared with the host (defaults to 'default').", @@ -340,12 +319,12 @@ export default { experiments: { type: 'object', properties: { - federationRuntime: { - anyOf: [{ type: 'boolean' }, { enum: ['hoisted'] }], - }, externalRuntime: { anyOf: [{ type: 'boolean' }, { enum: ['provide'] }], }, + asyncStartup: { + type: 'boolean', + }, }, additionalProperties: false, }, diff --git a/packages/enhanced/src/schemas/container/ContainerReferencePlugin.check.ts b/packages/enhanced/src/schemas/container/ContainerReferencePlugin.check.ts index e0fb064b137..3da51974b41 100644 --- a/packages/enhanced/src/schemas/container/ContainerReferencePlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ContainerReferencePlugin.check.ts @@ -1,10 +1,5 @@ /* eslint-disable */ //@ts-nocheck -/* - * This file was automatically generated. - * DO NOT MODIFY BY HAND. - * Run `yarn special-lint-fix` to update - */ const schema40 = { definitions: { diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.d.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.d.ts index c278b27e3ce..2c7636fbc0b 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.d.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.d.ts @@ -1,8 +1,3 @@ -/* - * This file was automatically generated. - * DO NOT MODIFY BY HAND. - * Run `yarn special-lint-fix` to update - */ declare const check: ( options: import('@module-federation/sdk').moduleFederationPlugin.ModuleFederationPluginOptions, ) => boolean; diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts index 8aaa2e66f32..3fe5bd78e2b 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts @@ -212,9 +212,6 @@ export class NextFederationPlugin { // nextjs project needs to add config.watchOptions = ['**/node_modules/**', '**/@mf-types/**'] to prevent loop types update dts: this._options.dts ?? false, shareStrategy: this._options.shareStrategy ?? 'loaded-first', - experiments: { - federationRuntime: 'hoisted', - }, }; } diff --git a/packages/sdk/src/types/plugins/ContainerPlugin.ts b/packages/sdk/src/types/plugins/ContainerPlugin.ts index da350d51707..eb9ce04a980 100644 --- a/packages/sdk/src/types/plugins/ContainerPlugin.ts +++ b/packages/sdk/src/types/plugins/ContainerPlugin.ts @@ -99,9 +99,6 @@ export interface ContainerPluginOptions { */ runtimePlugins?: string[]; - experiments?: { - federationRuntime?: false | 'hoisted'; - }; dataPrefetch?: DataPrefetch; } /** diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index 70105e26ac5..8166d5e319b 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -236,9 +236,9 @@ export interface ModuleFederationPluginOptions { dataPrefetch?: DataPrefetch; virtualRuntimeEntry?: boolean; experiments?: { - federationRuntime?: false | 'hoisted'; externalRuntime?: boolean; provideExternalRuntime?: boolean; + asyncStartup?: boolean; }; bridge?: { /** From 757696c656099150d708f66885fedac89f7a53a3 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 13:08:02 -0800 Subject: [PATCH 02/21] fix(enhanced): remove unused experiments params --- .../src/lib/container/ContainerEntryDependency.ts | 5 ----- .../enhanced/src/lib/container/ContainerEntryModule.ts | 8 +------- .../src/lib/container/ContainerEntryModuleFactory.ts | 1 - packages/enhanced/src/lib/container/ContainerPlugin.ts | 3 --- .../enhanced/src/lib/container/ModuleFederationPlugin.ts | 1 - .../nextjs-mf/src/plugins/NextFederationPlugin/index.ts | 4 +++- 6 files changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/enhanced/src/lib/container/ContainerEntryDependency.ts b/packages/enhanced/src/lib/container/ContainerEntryDependency.ts index 6552f89e5e5..9209aafc9df 100644 --- a/packages/enhanced/src/lib/container/ContainerEntryDependency.ts +++ b/packages/enhanced/src/lib/container/ContainerEntryDependency.ts @@ -20,8 +20,6 @@ class ContainerEntryDependency extends Dependency { public exposes: [string, ExposeOptions][]; public shareScope: string; public injectRuntimeEntry: string; - /** Additional experimental options for container plugin customization */ - public experiments: containerPlugin.ContainerPluginOptions['experiments']; public dataPrefetch: containerPlugin.ContainerPluginOptions['dataPrefetch']; /** @@ -29,7 +27,6 @@ class ContainerEntryDependency extends Dependency { * @param {[string, ExposeOptions][]} exposes list of exposed modules * @param {string} shareScope name of the share scope * @param {string[]} injectRuntimeEntry the path of injectRuntime file. - * @param {containerPlugin.ContainerPluginOptions['experiments']} experiments additional experiments options * @param {containerPlugin.ContainerPluginOptions['dataPrefetch']} dataPrefetch whether enable dataPrefetch */ constructor( @@ -37,7 +34,6 @@ class ContainerEntryDependency extends Dependency { exposes: [string, ExposeOptions][], shareScope: string, injectRuntimeEntry: string, - experiments: containerPlugin.ContainerPluginOptions['experiments'], dataPrefetch: containerPlugin.ContainerPluginOptions['dataPrefetch'], ) { super(); @@ -45,7 +41,6 @@ class ContainerEntryDependency extends Dependency { this.exposes = exposes; this.shareScope = shareScope; this.injectRuntimeEntry = injectRuntimeEntry; - this.experiments = experiments; this.dataPrefetch = dataPrefetch; } diff --git a/packages/enhanced/src/lib/container/ContainerEntryModule.ts b/packages/enhanced/src/lib/container/ContainerEntryModule.ts index bbe480bbcf2..7bcd0cf139c 100644 --- a/packages/enhanced/src/lib/container/ContainerEntryModule.ts +++ b/packages/enhanced/src/lib/container/ContainerEntryModule.ts @@ -64,7 +64,6 @@ class ContainerEntryModule extends Module { private _exposes: [string, ExposeOptions][]; private _shareScope: string; private _injectRuntimeEntry: string; - private _experiments: containerPlugin.ContainerPluginOptions['experiments']; private _dataPrefetch: containerPlugin.ContainerPluginOptions['dataPrefetch']; /** @@ -72,7 +71,6 @@ class ContainerEntryModule extends Module { * @param {[string, ExposeOptions][]} exposes list of exposed modules * @param {string} shareScope name of the share scope * @param {string} injectRuntimeEntry the path of injectRuntime file. - * @param {containerPlugin.ContainerPluginOptions['experiments']} experiments additional experiments options * @param {containerPlugin.ContainerPluginOptions['dataPrefetch']} dataPrefetch whether enable dataPrefetch */ constructor( @@ -80,7 +78,6 @@ class ContainerEntryModule extends Module { exposes: [string, ExposeOptions][], shareScope: string, injectRuntimeEntry: string, - experiments: containerPlugin.ContainerPluginOptions['experiments'], dataPrefetch: containerPlugin.ContainerPluginOptions['dataPrefetch'], ) { super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); @@ -88,7 +85,6 @@ class ContainerEntryModule extends Module { this._exposes = exposes; this._shareScope = shareScope; this._injectRuntimeEntry = injectRuntimeEntry; - this._experiments = experiments; this._dataPrefetch = dataPrefetch; } @@ -104,7 +100,6 @@ class ContainerEntryModule extends Module { read(), read(), read(), - read(), ); obj.deserialize(context); return obj; @@ -122,7 +117,7 @@ class ContainerEntryModule extends Module { override identifier(): string { return `container entry (${this._shareScope}) ${JSON.stringify( this._exposes, - )} ${this._injectRuntimeEntry} ${JSON.stringify(this._experiments)} ${JSON.stringify(this._dataPrefetch)}`; + )} ${this._injectRuntimeEntry} ${JSON.stringify(this._dataPrefetch)}`; } /** * @param {RequestShortener} requestShortener the request shortener @@ -352,7 +347,6 @@ class ContainerEntryModule extends Module { write(this._exposes); write(this._shareScope); write(this._injectRuntimeEntry); - write(this._experiments); write(this._dataPrefetch); super.serialize(context); } diff --git a/packages/enhanced/src/lib/container/ContainerEntryModuleFactory.ts b/packages/enhanced/src/lib/container/ContainerEntryModuleFactory.ts index 56a33c33397..abb1c1bbb0f 100644 --- a/packages/enhanced/src/lib/container/ContainerEntryModuleFactory.ts +++ b/packages/enhanced/src/lib/container/ContainerEntryModuleFactory.ts @@ -38,7 +38,6 @@ export default class ContainerEntryModuleFactory extends ModuleFactory { dep.exposes, dep.shareScope, dep.injectRuntimeEntry, - dep.experiments, dep.dataPrefetch, ), }); diff --git a/packages/enhanced/src/lib/container/ContainerPlugin.ts b/packages/enhanced/src/lib/container/ContainerPlugin.ts index 0a5c5339445..25a3db206aa 100644 --- a/packages/enhanced/src/lib/container/ContainerPlugin.ts +++ b/packages/enhanced/src/lib/container/ContainerPlugin.ts @@ -68,7 +68,6 @@ class ContainerPlugin { }), ), runtimePlugins: options.runtimePlugins, - experiments: options.experiments, dataPrefetch: options.dataPrefetch, }; } @@ -205,7 +204,6 @@ class ContainerPlugin { exposes, shareScope, federationRuntimePluginInstance.entryFilePath, - this._options.experiments, this._options.dataPrefetch, ); dep.loc = { name }; @@ -284,7 +282,6 @@ class ContainerPlugin { exposes, shareScope, federationRuntimePluginInstance.entryFilePath, - this._options.experiments, this._options.dataPrefetch, ); diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 18a4f863145..53f69507ee8 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -160,7 +160,6 @@ class ModuleFederationPlugin implements WebpackPluginInstance { shareScope: options.shareScope, exposes: options.exposes!, runtimePlugins: options.runtimePlugins, - experiments: options.experiments, }).apply(compiler); } if ( diff --git a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts index 3fe5bd78e2b..950cb70abe5 100644 --- a/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts +++ b/packages/nextjs-mf/src/plugins/NextFederationPlugin/index.ts @@ -20,7 +20,6 @@ import { validatePluginOptions, } from './validate-options'; import { - modifyEntry, applyServerPlugins, configureServerCompilerOptions, configureServerLibraryAndFilename, @@ -212,6 +211,9 @@ export class NextFederationPlugin { // nextjs project needs to add config.watchOptions = ['**/node_modules/**', '**/@mf-types/**'] to prevent loop types update dts: this._options.dts ?? false, shareStrategy: this._options.shareStrategy ?? 'loaded-first', + experiments: { + asyncStartup: true, + }, }; } From e0dcc71b8a64f44c06a8b6fdf9e08e509dd8d136 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 15:23:09 -0800 Subject: [PATCH 03/21] fix(enhanced): update embedded runtime path to use the correct module federation package --- .../src/lib/container/runtime/FederationRuntimePlugin.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts index 34eec96bf74..bd8bf6bd2d7 100644 --- a/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/FederationRuntimePlugin.ts @@ -47,9 +47,12 @@ const BundlerRuntimePath = require.resolve( const RuntimePath = require.resolve('@module-federation/runtime', { paths: [RuntimeToolsPath], }); -const EmbeddedRuntimePath = require.resolve('@module-federation/runtime-core', { - paths: [RuntimeToolsPath], -}); +const EmbeddedRuntimePath = require.resolve( + '@module-federation/runtime/embedded', + { + paths: [RuntimeToolsPath], + }, +); const federationGlobal = getFederationGlobalScope(RuntimeGlobals); From c6a3f78d80f0cbbe831eb0935e9b5c52bfdff6dc Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 17:14:13 -0800 Subject: [PATCH 04/21] chore: update webpack and modern configurations to enable async startup --- apps/manifest-demo/webpack-host/webpack.config.js | 4 ++-- apps/modernjs/modern.config.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index b9efd43734a..835a911b453 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -44,11 +44,10 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { }, }, dataPrefetch: true, - // experiments: { federationRuntime: 'hoisted' }, runtimePlugins: [path.join(__dirname, './runtimePlugin.ts')], experiments: { provideExternalRuntime: true, - federationRuntime: 'hoisted', + asyncStartup: true, }, }), ); @@ -61,6 +60,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { }); if (config.devServer) { config.devServer.client.overlay = false; + config.devServer.devMiddleware.writeToDisk = true; } config.entry = './src/index.tsx'; //Temporary workaround - https://github.com/nrwl/nx/issues/16983 diff --git a/apps/modernjs/modern.config.ts b/apps/modernjs/modern.config.ts index e503e34552c..6d4c62fc090 100644 --- a/apps/modernjs/modern.config.ts +++ b/apps/modernjs/modern.config.ts @@ -41,6 +41,9 @@ export default defineConfig({ './react-component': './src/components/react-component.tsx', }, runtimePlugins: ['./runtimePlugin.ts'], + experiments: { + asyncStartup: true, + }, filename: 'remoteEntry.js', shared: { 'react/': { From e1ddc904b3d77bf0e8fac699a78f5b3b9851dcaa Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 21 Jan 2025 17:21:45 -0800 Subject: [PATCH 05/21] chore: update webpack and modern configurations to enable async startup --- apps/runtime-demo/3005-runtime-host/webpack.config.js | 2 +- apps/runtime-demo/3006-runtime-remote/webpack.config.js | 3 +++ packages/enhanced/test/ConfigTestCases.embedruntime.js | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/runtime-demo/3005-runtime-host/webpack.config.js b/apps/runtime-demo/3005-runtime-host/webpack.config.js index 55ea77ae395..05b99f7e4b7 100644 --- a/apps/runtime-demo/3005-runtime-host/webpack.config.js +++ b/apps/runtime-demo/3005-runtime-host/webpack.config.js @@ -16,7 +16,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { config.plugins.push( new ModuleFederationPlugin({ name: 'runtime_host', - experiments: { federationRuntime: 'hoisted' }, + experiments: { asyncStartup: true }, remotes: { // remote2: 'runtime_remote2@http://localhost:3007/remoteEntry.js', remote1: 'runtime_remote1@http://127.0.0.1:3006/mf-manifest.json', diff --git a/apps/runtime-demo/3006-runtime-remote/webpack.config.js b/apps/runtime-demo/3006-runtime-remote/webpack.config.js index e710efebfd2..29cde381cb3 100644 --- a/apps/runtime-demo/3006-runtime-remote/webpack.config.js +++ b/apps/runtime-demo/3006-runtime-remote/webpack.config.js @@ -39,6 +39,9 @@ module.exports = composePlugins( './WebpackPng': './src/components/WebpackPng', }, shareStrategy: 'loaded-first', + experiments: { + asyncStartup: true, + }, shared: { lodash: { singleton: true, diff --git a/packages/enhanced/test/ConfigTestCases.embedruntime.js b/packages/enhanced/test/ConfigTestCases.embedruntime.js index 0b1dbda4a91..05b3ab50f91 100644 --- a/packages/enhanced/test/ConfigTestCases.embedruntime.js +++ b/packages/enhanced/test/ConfigTestCases.embedruntime.js @@ -14,6 +14,6 @@ jest.resetModules(); describeCases({ name: 'ConfigTestCases', federation: { - federationRuntime: 'hoisted', + asyncStartup: true, }, }); From 701c43db0be622f4eac7938ba9820e44d40972ef Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 23 Jan 2025 15:49:47 -0800 Subject: [PATCH 06/21] chore: update webpack and modern configurations to enable async startup --- .cursorrules | 40 + .gitignore | 4 +- apps/next-app-router-4000/.env | 2 - apps/next-app-router-4001/.env | 2 - apps/node-host/src/bootstrap.js | 4 +- .../runtime/EmbedFederationRuntimeModule.ts | 18 +- .../runtime/EmbedFederationRuntimePlugin.ts | 33 +- webpack-lib/lib/APIPlugin.js | 324 + webpack-lib/lib/AbstractMethodError.js | 54 + webpack-lib/lib/AsyncDependenciesBlock.js | 114 + .../lib/AsyncDependencyToInitialChunkError.js | 31 + webpack-lib/lib/AutomaticPrefetchPlugin.js | 66 + webpack-lib/lib/BannerPlugin.js | 139 + webpack-lib/lib/Cache.js | 165 + webpack-lib/lib/CacheFacade.js | 349 ++ .../lib/CaseSensitiveModulesWarning.js | 71 + webpack-lib/lib/Chunk.js | 874 +++ webpack-lib/lib/ChunkGraph.js | 1868 ++++++ webpack-lib/lib/ChunkGroup.js | 604 ++ webpack-lib/lib/ChunkRenderError.js | 31 + webpack-lib/lib/ChunkTemplate.js | 181 + webpack-lib/lib/CleanPlugin.js | 443 ++ webpack-lib/lib/CodeGenerationError.js | 29 + webpack-lib/lib/CodeGenerationResults.js | 157 + webpack-lib/lib/CommentCompilationWarning.js | 32 + webpack-lib/lib/CompatibilityPlugin.js | 191 + webpack-lib/lib/Compilation.js | 5549 +++++++++++++++++ webpack-lib/lib/Compiler.js | 1384 ++++ webpack-lib/lib/ConcatenationScope.js | 143 + webpack-lib/lib/ConcurrentCompilationError.js | 18 + webpack-lib/lib/ConditionalInitFragment.js | 120 + webpack-lib/lib/ConstPlugin.js | 537 ++ webpack-lib/lib/ContextExclusionPlugin.js | 32 + webpack-lib/lib/ContextModule.js | 1253 ++++ webpack-lib/lib/ContextModuleFactory.js | 481 ++ webpack-lib/lib/ContextReplacementPlugin.js | 172 + webpack-lib/lib/CssModule.js | 175 + webpack-lib/lib/DefinePlugin.js | 719 +++ webpack-lib/lib/DelegatedModule.js | 262 + .../lib/DelegatedModuleFactoryPlugin.js | 111 + webpack-lib/lib/DelegatedPlugin.js | 47 + webpack-lib/lib/DependenciesBlock.js | 122 + webpack-lib/lib/Dependency.js | 367 ++ webpack-lib/lib/DependencyTemplate.js | 69 + webpack-lib/lib/DependencyTemplates.js | 67 + webpack-lib/lib/DllEntryPlugin.js | 72 + webpack-lib/lib/DllModule.js | 174 + webpack-lib/lib/DllModuleFactory.js | 38 + webpack-lib/lib/DllPlugin.js | 70 + webpack-lib/lib/DllReferencePlugin.js | 193 + webpack-lib/lib/DynamicEntryPlugin.js | 86 + webpack-lib/lib/EntryOptionPlugin.js | 96 + webpack-lib/lib/EntryPlugin.js | 70 + webpack-lib/lib/Entrypoint.js | 101 + .../lib/EnvironmentNotSupportAsyncWarning.js | 52 + webpack-lib/lib/EnvironmentPlugin.js | 67 + webpack-lib/lib/ErrorHelpers.js | 100 + webpack-lib/lib/EvalDevToolModulePlugin.js | 129 + webpack-lib/lib/EvalSourceMapDevToolPlugin.js | 227 + webpack-lib/lib/ExportsInfo.js | 1624 +++++ webpack-lib/lib/ExportsInfoApiPlugin.js | 87 + webpack-lib/lib/ExternalModule.js | 999 +++ .../lib/ExternalModuleFactoryPlugin.js | 326 + webpack-lib/lib/ExternalsPlugin.js | 37 + webpack-lib/lib/FalseIIFEUmdWarning.js | 19 + webpack-lib/lib/FileSystemInfo.js | 4041 ++++++++++++ webpack-lib/lib/FlagAllModulesAsUsedPlugin.js | 55 + .../lib/FlagDependencyExportsPlugin.js | 420 ++ webpack-lib/lib/FlagDependencyUsagePlugin.js | 347 ++ .../lib/FlagEntryExportAsUsedPlugin.js | 56 + webpack-lib/lib/Generator.js | 155 + webpack-lib/lib/GraphHelpers.js | 38 + webpack-lib/lib/HarmonyLinkingError.js | 16 + webpack-lib/lib/HookWebpackError.js | 91 + webpack-lib/lib/HotModuleReplacementPlugin.js | 875 +++ webpack-lib/lib/HotUpdateChunk.js | 19 + webpack-lib/lib/IgnoreErrorModuleFactory.js | 39 + webpack-lib/lib/IgnorePlugin.js | 102 + webpack-lib/lib/IgnoreWarningsPlugin.js | 36 + webpack-lib/lib/InitFragment.js | 185 + .../lib/InvalidDependenciesModuleWarning.js | 44 + webpack-lib/lib/JavascriptMetaInfoPlugin.js | 80 + webpack-lib/lib/LibManifestPlugin.js | 144 + webpack-lib/lib/LibraryTemplatePlugin.js | 48 + webpack-lib/lib/LoaderOptionsPlugin.js | 83 + webpack-lib/lib/LoaderTargetPlugin.js | 37 + webpack-lib/lib/MainTemplate.js | 382 ++ webpack-lib/lib/Module.js | 1198 ++++ webpack-lib/lib/ModuleBuildError.js | 79 + webpack-lib/lib/ModuleDependencyError.js | 42 + webpack-lib/lib/ModuleDependencyWarning.js | 47 + webpack-lib/lib/ModuleError.js | 66 + webpack-lib/lib/ModuleFactory.js | 50 + webpack-lib/lib/ModuleFilenameHelpers.js | 383 ++ webpack-lib/lib/ModuleGraph.js | 896 +++ webpack-lib/lib/ModuleGraphConnection.js | 205 + webpack-lib/lib/ModuleHashingError.js | 29 + webpack-lib/lib/ModuleInfoHeaderPlugin.js | 314 + webpack-lib/lib/ModuleNotFoundError.js | 89 + webpack-lib/lib/ModuleParseError.js | 117 + webpack-lib/lib/ModuleProfile.js | 108 + webpack-lib/lib/ModuleRestoreError.js | 44 + webpack-lib/lib/ModuleSourceTypesConstants.js | 112 + webpack-lib/lib/ModuleStoreError.js | 43 + webpack-lib/lib/ModuleTemplate.js | 174 + webpack-lib/lib/ModuleTypeConstants.js | 168 + webpack-lib/lib/ModuleWarning.js | 66 + webpack-lib/lib/MultiCompiler.js | 659 ++ webpack-lib/lib/MultiStats.js | 208 + webpack-lib/lib/MultiWatching.js | 81 + webpack-lib/lib/NoEmitOnErrorsPlugin.js | 28 + webpack-lib/lib/NoModeWarning.js | 22 + webpack-lib/lib/NodeStuffInWebError.js | 34 + webpack-lib/lib/NodeStuffPlugin.js | 275 + webpack-lib/lib/NormalModule.js | 1654 +++++ webpack-lib/lib/NormalModuleFactory.js | 1326 ++++ .../lib/NormalModuleReplacementPlugin.js | 77 + webpack-lib/lib/NullFactory.js | 23 + webpack-lib/lib/OptimizationStages.js | 10 + webpack-lib/lib/OptionsApply.js | 22 + webpack-lib/lib/Parser.js | 38 + webpack-lib/lib/PlatformPlugin.js | 39 + webpack-lib/lib/PrefetchPlugin.js | 54 + webpack-lib/lib/ProgressPlugin.js | 709 +++ webpack-lib/lib/ProvidePlugin.js | 119 + webpack-lib/lib/RawModule.js | 164 + webpack-lib/lib/RecordIdsPlugin.js | 238 + webpack-lib/lib/RequestShortener.js | 34 + webpack-lib/lib/RequireJsStuffPlugin.js | 84 + webpack-lib/lib/ResolverFactory.js | 155 + webpack-lib/lib/RuntimeGlobals.js | 387 ++ webpack-lib/lib/RuntimeModule.js | 214 + webpack-lib/lib/RuntimePlugin.js | 496 ++ webpack-lib/lib/RuntimeTemplate.js | 1109 ++++ webpack-lib/lib/SelfModuleFactory.js | 33 + webpack-lib/lib/SingleEntryPlugin.js | 8 + webpack-lib/lib/SizeFormatHelpers.js | 25 + .../SourceMapDevToolModuleOptionsPlugin.js | 61 + webpack-lib/lib/SourceMapDevToolPlugin.js | 618 ++ webpack-lib/lib/Stats.js | 89 + webpack-lib/lib/Template.js | 415 ++ webpack-lib/lib/TemplatedPathPlugin.js | 395 ++ webpack-lib/lib/UnhandledSchemeError.js | 33 + webpack-lib/lib/UnsupportedFeatureWarning.js | 32 + webpack-lib/lib/UseStrictPlugin.js | 81 + .../lib/WarnCaseSensitiveModulesPlugin.js | 65 + webpack-lib/lib/WarnDeprecatedOptionPlugin.js | 60 + webpack-lib/lib/WarnNoModeSetPlugin.js | 25 + webpack-lib/lib/WatchIgnorePlugin.js | 153 + webpack-lib/lib/Watching.js | 528 ++ webpack-lib/lib/WebpackError.js | 70 + webpack-lib/lib/WebpackIsIncludedPlugin.js | 94 + webpack-lib/lib/WebpackOptionsApply.js | 783 +++ webpack-lib/lib/WebpackOptionsDefaulter.js | 26 + webpack-lib/lib/asset/AssetGenerator.js | 739 +++ webpack-lib/lib/asset/AssetModulesPlugin.js | 250 + webpack-lib/lib/asset/AssetParser.js | 65 + webpack-lib/lib/asset/AssetSourceGenerator.js | 146 + webpack-lib/lib/asset/AssetSourceParser.js | 37 + webpack-lib/lib/asset/RawDataUrlModule.js | 163 + .../AwaitDependenciesInitFragment.js | 72 + .../async-modules/InferAsyncModulesPlugin.js | 55 + webpack-lib/lib/buildChunkGraph.js | 1344 ++++ webpack-lib/lib/cli.js | 699 +++ .../lib/config/browserslistTargetHandler.js | 363 ++ webpack-lib/lib/config/defaults.js | 1673 +++++ webpack-lib/lib/config/normalization.js | 558 ++ webpack-lib/lib/config/target.js | 377 ++ .../lib/container/ContainerEntryDependency.js | 48 + .../lib/container/ContainerEntryModule.js | 295 + .../container/ContainerEntryModuleFactory.js | 27 + .../container/ContainerExposedDependency.js | 61 + webpack-lib/lib/container/ContainerPlugin.js | 119 + .../lib/container/ContainerReferencePlugin.js | 142 + .../lib/container/FallbackDependency.js | 64 + .../lib/container/FallbackItemDependency.js | 33 + webpack-lib/lib/container/FallbackModule.js | 184 + .../lib/container/FallbackModuleFactory.js | 27 + .../HoistContainerReferencesPlugin.js | 250 + .../lib/container/ModuleFederationPlugin.js | 131 + webpack-lib/lib/container/RemoteModule.js | 184 + .../lib/container/RemoteRuntimeModule.js | 144 + .../container/RemoteToExternalDependency.js | 33 + webpack-lib/lib/container/options.js | 105 + webpack-lib/lib/css/CssGenerator.js | 261 + .../lib/css/CssLoadingRuntimeModule.js | 503 ++ webpack-lib/lib/css/CssModulesPlugin.js | 887 +++ webpack-lib/lib/css/CssParser.js | 1602 +++++ webpack-lib/lib/css/walkCssTokens.js | 1627 +++++ webpack-lib/lib/debug/ProfilingPlugin.js | 504 ++ .../lib/dependencies/AMDDefineDependency.js | 265 + .../AMDDefineDependencyParserPlugin.js | 497 ++ webpack-lib/lib/dependencies/AMDPlugin.js | 236 + .../dependencies/AMDRequireArrayDependency.js | 123 + .../AMDRequireContextDependency.js | 68 + .../AMDRequireDependenciesBlock.js | 28 + ...AMDRequireDependenciesBlockParserPlugin.js | 410 ++ .../lib/dependencies/AMDRequireDependency.js | 189 + .../dependencies/AMDRequireItemDependency.js | 41 + .../lib/dependencies/AMDRuntimeModules.js | 48 + .../lib/dependencies/CachedConstDependency.js | 128 + .../dependencies/CommonJsDependencyHelpers.js | 63 + .../CommonJsExportRequireDependency.js | 404 ++ .../dependencies/CommonJsExportsDependency.js | 183 + .../CommonJsExportsParserPlugin.js | 411 ++ .../CommonJsFullRequireDependency.js | 166 + .../CommonJsImportsParserPlugin.js | 783 +++ .../lib/dependencies/CommonJsPlugin.js | 305 + .../CommonJsRequireContextDependency.js | 72 + .../dependencies/CommonJsRequireDependency.js | 42 + .../CommonJsSelfReferenceDependency.js | 155 + .../lib/dependencies/ConstDependency.js | 117 + .../lib/dependencies/ContextDependency.js | 178 + .../dependencies/ContextDependencyHelpers.js | 262 + .../ContextDependencyTemplateAsId.js | 62 + .../ContextDependencyTemplateAsRequireCall.js | 59 + .../dependencies/ContextElementDependency.js | 135 + .../dependencies/CreateScriptUrlDependency.js | 75 + .../dependencies/CriticalDependencyWarning.js | 28 + .../dependencies/CssIcssExportDependency.js | 156 + .../dependencies/CssIcssImportDependency.js | 118 + .../dependencies/CssIcssSymbolDependency.js | 132 + .../lib/dependencies/CssImportDependency.js | 117 + .../CssLocalIdentifierDependency.js | 250 + .../CssSelfLocalIdentifierDependency.js | 111 + .../lib/dependencies/CssUrlDependency.js | 195 + .../dependencies/DelegatedSourceDependency.js | 33 + .../lib/dependencies/DllEntryDependency.js | 61 + .../lib/dependencies/DynamicExports.js | 73 + .../lib/dependencies/EntryDependency.js | 30 + .../lib/dependencies/ExportsInfoDependency.js | 158 + .../dependencies/ExternalModuleDependency.js | 109 + .../ExternalModuleInitFragment.js | 133 + .../dependencies/HarmonyAcceptDependency.js | 143 + .../HarmonyAcceptImportDependency.js | 40 + .../HarmonyCompatibilityDependency.js | 92 + .../HarmonyDetectionParserPlugin.js | 123 + ...rmonyEvaluatedImportSpecifierDependency.js | 152 + .../HarmonyExportDependencyParserPlugin.js | 221 + .../HarmonyExportExpressionDependency.js | 207 + .../HarmonyExportHeaderDependency.js | 78 + ...armonyExportImportedSpecifierDependency.js | 1396 +++++ .../dependencies/HarmonyExportInitFragment.js | 177 + .../HarmonyExportSpecifierDependency.js | 123 + .../lib/dependencies/HarmonyExports.js | 46 + .../dependencies/HarmonyImportDependency.js | 382 ++ .../HarmonyImportDependencyParserPlugin.js | 370 ++ .../HarmonyImportSideEffectDependency.js | 86 + .../HarmonyImportSpecifierDependency.js | 468 ++ .../lib/dependencies/HarmonyModulesPlugin.js | 149 + .../HarmonyTopLevelThisParserPlugin.js | 39 + .../dependencies/ImportContextDependency.js | 67 + .../lib/dependencies/ImportDependency.js | 139 + .../lib/dependencies/ImportEagerDependency.js | 74 + .../ImportMetaContextDependency.js | 42 + ...ImportMetaContextDependencyParserPlugin.js | 301 + .../dependencies/ImportMetaContextPlugin.js | 72 + .../ImportMetaHotAcceptDependency.js | 41 + .../ImportMetaHotDeclineDependency.js | 42 + .../lib/dependencies/ImportMetaPlugin.js | 253 + .../lib/dependencies/ImportParserPlugin.js | 339 + webpack-lib/lib/dependencies/ImportPlugin.js | 96 + .../lib/dependencies/ImportWeakDependency.js | 72 + .../lib/dependencies/JsonExportsDependency.js | 117 + .../lib/dependencies/LoaderDependency.js | 41 + .../dependencies/LoaderImportDependency.js | 42 + webpack-lib/lib/dependencies/LoaderPlugin.js | 292 + webpack-lib/lib/dependencies/LocalModule.js | 60 + .../lib/dependencies/LocalModuleDependency.js | 84 + .../lib/dependencies/LocalModulesHelpers.js | 68 + .../dependencies/ModuleDecoratorDependency.js | 137 + .../lib/dependencies/ModuleDependency.js | 98 + .../ModuleDependencyTemplateAsId.js | 35 + .../ModuleDependencyTemplateAsRequireId.js | 38 + .../dependencies/ModuleHotAcceptDependency.js | 41 + .../ModuleHotDeclineDependency.js | 42 + .../lib/dependencies/NullDependency.js | 40 + .../lib/dependencies/PrefetchDependency.js | 27 + .../lib/dependencies/ProvidedDependency.js | 157 + .../dependencies/PureExpressionDependency.js | 162 + .../dependencies/RequireContextDependency.js | 37 + .../RequireContextDependencyParserPlugin.js | 66 + .../lib/dependencies/RequireContextPlugin.js | 163 + .../RequireEnsureDependenciesBlock.js | 29 + ...uireEnsureDependenciesBlockParserPlugin.js | 138 + .../dependencies/RequireEnsureDependency.js | 115 + .../RequireEnsureItemDependency.js | 36 + .../lib/dependencies/RequireEnsurePlugin.js | 86 + .../dependencies/RequireHeaderDependency.js | 70 + .../dependencies/RequireIncludeDependency.js | 79 + .../RequireIncludeDependencyParserPlugin.js | 101 + .../lib/dependencies/RequireIncludePlugin.js | 62 + .../RequireResolveContextDependency.js | 67 + .../dependencies/RequireResolveDependency.js | 58 + .../RequireResolveHeaderDependency.js | 81 + .../RuntimeRequirementsDependency.js | 85 + .../dependencies/StaticExportsDependency.js | 74 + webpack-lib/lib/dependencies/SystemPlugin.js | 168 + .../lib/dependencies/SystemRuntimeModule.js | 35 + webpack-lib/lib/dependencies/URLDependency.js | 170 + webpack-lib/lib/dependencies/URLPlugin.js | 208 + .../lib/dependencies/UnsupportedDependency.js | 82 + .../WebAssemblyExportImportedDependency.js | 93 + .../WebAssemblyImportDependency.js | 108 + .../WebpackIsIncludedDependency.js | 85 + .../lib/dependencies/WorkerDependency.js | 133 + webpack-lib/lib/dependencies/WorkerPlugin.js | 500 ++ .../lib/dependencies/getFunctionExpression.js | 64 + .../lib/dependencies/processExportInfo.js | 67 + .../lib/electron/ElectronTargetPlugin.js | 69 + webpack-lib/lib/errors/BuildCycleError.js | 27 + .../esm/ExportWebpackRequireRuntimeModule.js | 30 + .../lib/esm/ModuleChunkFormatPlugin.js | 218 + .../lib/esm/ModuleChunkLoadingPlugin.js | 87 + .../esm/ModuleChunkLoadingRuntimeModule.js | 351 ++ webpack-lib/lib/formatLocation.js | 67 + .../lib/hmr/HotModuleReplacement.runtime.js | 407 ++ .../hmr/HotModuleReplacementRuntimeModule.js | 42 + .../JavascriptHotModuleReplacement.runtime.js | 461 ++ webpack-lib/lib/hmr/LazyCompilationPlugin.js | 455 ++ webpack-lib/lib/hmr/lazyCompilationBackend.js | 161 + .../lib/ids/ChunkModuleIdRangePlugin.js | 89 + .../lib/ids/DeterministicChunkIdsPlugin.js | 77 + .../lib/ids/DeterministicModuleIdsPlugin.js | 97 + webpack-lib/lib/ids/HashedModuleIdsPlugin.js | 88 + webpack-lib/lib/ids/IdHelpers.js | 473 ++ webpack-lib/lib/ids/NamedChunkIdsPlugin.js | 93 + webpack-lib/lib/ids/NamedModuleIdsPlugin.js | 69 + webpack-lib/lib/ids/NaturalChunkIdsPlugin.js | 33 + webpack-lib/lib/ids/NaturalModuleIdsPlugin.js | 39 + .../lib/ids/OccurrenceChunkIdsPlugin.js | 84 + .../lib/ids/OccurrenceModuleIdsPlugin.js | 159 + webpack-lib/lib/ids/SyncModuleIdsPlugin.js | 146 + webpack-lib/lib/index.js | 641 ++ .../ArrayPushCallbackChunkFormatPlugin.js | 156 + .../javascript/BasicEvaluatedExpression.js | 594 ++ webpack-lib/lib/javascript/ChunkHelpers.js | 33 + .../javascript/CommonJsChunkFormatPlugin.js | 175 + .../javascript/EnableChunkLoadingPlugin.js | 121 + .../lib/javascript/JavascriptGenerator.js | 255 + .../lib/javascript/JavascriptModulesPlugin.js | 1651 +++++ .../lib/javascript/JavascriptParser.js | 5007 +++++++++++++++ .../lib/javascript/JavascriptParserHelpers.js | 128 + webpack-lib/lib/javascript/StartupHelpers.js | 177 + webpack-lib/lib/json/JsonData.js | 74 + webpack-lib/lib/json/JsonGenerator.js | 199 + webpack-lib/lib/json/JsonModulesPlugin.js | 55 + webpack-lib/lib/json/JsonParser.js | 73 + .../lib/library/AbstractLibraryPlugin.js | 301 + webpack-lib/lib/library/AmdLibraryPlugin.js | 178 + .../lib/library/AssignLibraryPlugin.js | 387 ++ .../lib/library/EnableLibraryPlugin.js | 279 + .../library/ExportPropertyLibraryPlugin.js | 122 + webpack-lib/lib/library/JsonpLibraryPlugin.js | 89 + .../lib/library/ModernModuleLibraryPlugin.js | 144 + .../lib/library/ModuleLibraryPlugin.js | 109 + .../lib/library/SystemLibraryPlugin.js | 235 + webpack-lib/lib/library/UmdLibraryPlugin.js | 351 ++ webpack-lib/lib/logging/Logger.js | 216 + .../lib/logging/createConsoleLogger.js | 213 + webpack-lib/lib/logging/runtime.js | 45 + webpack-lib/lib/logging/truncateArgs.js | 83 + .../lib/node/CommonJsChunkLoadingPlugin.js | 121 + webpack-lib/lib/node/NodeEnvironmentPlugin.js | 66 + webpack-lib/lib/node/NodeSourcePlugin.js | 19 + webpack-lib/lib/node/NodeTargetPlugin.js | 85 + webpack-lib/lib/node/NodeTemplatePlugin.js | 41 + webpack-lib/lib/node/NodeWatchFileSystem.js | 192 + .../node/ReadFileChunkLoadingRuntimeModule.js | 301 + .../node/ReadFileCompileAsyncWasmPlugin.js | 123 + .../lib/node/ReadFileCompileWasmPlugin.js | 129 + .../node/RequireChunkLoadingRuntimeModule.js | 246 + webpack-lib/lib/node/nodeConsole.js | 163 + .../lib/optimize/AggressiveMergingPlugin.js | 98 + .../lib/optimize/AggressiveSplittingPlugin.js | 348 ++ .../lib/optimize/ConcatenatedModule.js | 1904 ++++++ .../optimize/EnsureChunkConditionsPlugin.js | 88 + .../lib/optimize/FlagIncludedChunksPlugin.js | 130 + webpack-lib/lib/optimize/InnerGraph.js | 351 ++ webpack-lib/lib/optimize/InnerGraphPlugin.js | 450 ++ .../lib/optimize/LimitChunkCountPlugin.js | 277 + .../lib/optimize/MangleExportsPlugin.js | 182 + .../optimize/MergeDuplicateChunksPlugin.js | 135 + .../lib/optimize/MinChunkSizePlugin.js | 113 + webpack-lib/lib/optimize/MinMaxSizeWarning.js | 35 + .../lib/optimize/ModuleConcatenationPlugin.js | 932 +++ .../lib/optimize/RealContentHashPlugin.js | 464 ++ .../lib/optimize/RemoveEmptyChunksPlugin.js | 57 + .../lib/optimize/RemoveParentModulesPlugin.js | 204 + .../lib/optimize/RuntimeChunkPlugin.js | 57 + .../lib/optimize/SideEffectsFlagPlugin.js | 396 ++ webpack-lib/lib/optimize/SplitChunksPlugin.js | 1767 ++++++ .../performance/AssetsOverSizeLimitWarning.js | 32 + .../EntrypointsOverSizeLimitWarning.js | 35 + .../lib/performance/NoAsyncChunksWarning.js | 20 + .../lib/performance/SizeLimitsPlugin.js | 179 + .../ChunkPrefetchFunctionRuntimeModule.js | 46 + .../prefetch/ChunkPrefetchPreloadPlugin.js | 98 + .../ChunkPrefetchStartupRuntimeModule.js | 55 + .../ChunkPrefetchTriggerRuntimeModule.js | 51 + .../ChunkPreloadTriggerRuntimeModule.js | 45 + .../lib/rules/BasicEffectRulePlugin.js | 45 + .../lib/rules/BasicMatcherRulePlugin.js | 54 + .../lib/rules/ObjectMatcherRulePlugin.js | 64 + webpack-lib/lib/rules/RuleSetCompiler.js | 400 ++ webpack-lib/lib/rules/UseEffectRulePlugin.js | 200 + .../lib/runtime/AsyncModuleRuntimeModule.js | 133 + .../runtime/AutoPublicPathRuntimeModule.js | 85 + .../lib/runtime/BaseUriRuntimeModule.js | 35 + .../lib/runtime/ChunkNameRuntimeModule.js | 27 + .../CompatGetDefaultExportRuntimeModule.js | 40 + .../lib/runtime/CompatRuntimeModule.js | 83 + .../CreateFakeNamespaceObjectRuntimeModule.js | 69 + .../lib/runtime/CreateScriptRuntimeModule.js | 38 + .../runtime/CreateScriptUrlRuntimeModule.js | 38 + .../DefinePropertyGettersRuntimeModule.js | 42 + .../lib/runtime/EnsureChunkRuntimeModule.js | 68 + .../runtime/GetChunkFilenameRuntimeModule.js | 295 + .../lib/runtime/GetFullHashRuntimeModule.js | 30 + .../runtime/GetMainFilenameRuntimeModule.js | 47 + .../GetTrustedTypesPolicyRuntimeModule.js | 98 + .../lib/runtime/GlobalRuntimeModule.js | 47 + .../runtime/HasOwnPropertyRuntimeModule.js | 35 + .../lib/runtime/HelperRuntimeModule.js | 18 + .../lib/runtime/LoadScriptRuntimeModule.js | 174 + .../MakeNamespaceObjectRuntimeModule.js | 39 + webpack-lib/lib/runtime/NonceRuntimeModule.js | 24 + .../runtime/OnChunksLoadedRuntimeModule.js | 78 + .../lib/runtime/PublicPathRuntimeModule.js | 37 + .../lib/runtime/RelativeUrlRuntimeModule.js | 44 + .../lib/runtime/RuntimeIdRuntimeModule.js | 32 + .../runtime/StartupChunkDependenciesPlugin.js | 89 + .../StartupChunkDependenciesRuntimeModule.js | 75 + .../runtime/StartupEntrypointRuntimeModule.js | 54 + .../lib/runtime/SystemContextRuntimeModule.js | 25 + webpack-lib/lib/schemes/DataUriPlugin.js | 69 + webpack-lib/lib/schemes/FileUriPlugin.js | 49 + webpack-lib/lib/schemes/HttpUriPlugin.js | 1271 ++++ .../lib/serialization/ArraySerializer.js | 38 + .../lib/serialization/BinaryMiddleware.js | 1142 ++++ .../lib/serialization/DateObjectSerializer.js | 28 + .../serialization/ErrorObjectSerializer.js | 44 + .../lib/serialization/FileMiddleware.js | 731 +++ .../lib/serialization/MapObjectSerializer.js | 48 + .../NullPrototypeObjectSerializer.js | 51 + .../lib/serialization/ObjectMiddleware.js | 778 +++ .../serialization/PlainObjectSerializer.js | 117 + .../serialization/RegExpObjectSerializer.js | 29 + webpack-lib/lib/serialization/Serializer.js | 64 + .../lib/serialization/SerializerMiddleware.js | 154 + .../lib/serialization/SetObjectSerializer.js | 40 + .../lib/serialization/SingleItemMiddleware.js | 34 + webpack-lib/lib/serialization/types.js | 13 + .../ConsumeSharedFallbackDependency.js | 33 + .../lib/sharing/ConsumeSharedModule.js | 267 + .../lib/sharing/ConsumeSharedPlugin.js | 379 ++ .../lib/sharing/ConsumeSharedRuntimeModule.js | 355 ++ .../lib/sharing/ProvideForSharedDependency.js | 33 + .../lib/sharing/ProvideSharedDependency.js | 80 + .../lib/sharing/ProvideSharedModule.js | 194 + .../lib/sharing/ProvideSharedModuleFactory.js | 35 + .../lib/sharing/ProvideSharedPlugin.js | 244 + webpack-lib/lib/sharing/SharePlugin.js | 92 + webpack-lib/lib/sharing/ShareRuntimeModule.js | 151 + .../lib/sharing/resolveMatchedConfigs.js | 92 + webpack-lib/lib/sharing/utils.js | 425 ++ .../lib/stats/DefaultStatsFactoryPlugin.js | 2614 ++++++++ .../lib/stats/DefaultStatsPresetPlugin.js | 386 ++ .../lib/stats/DefaultStatsPrinterPlugin.js | 1695 +++++ webpack-lib/lib/stats/StatsFactory.js | 363 ++ webpack-lib/lib/stats/StatsPrinter.js | 280 + webpack-lib/lib/util/ArrayHelpers.js | 48 + webpack-lib/lib/util/ArrayQueue.js | 104 + webpack-lib/lib/util/AsyncQueue.js | 410 ++ webpack-lib/lib/util/Hash.js | 35 + webpack-lib/lib/util/IterableHelpers.js | 45 + webpack-lib/lib/util/LazyBucketSortedSet.js | 252 + webpack-lib/lib/util/LazySet.js | 229 + webpack-lib/lib/util/MapHelpers.js | 34 + .../lib/util/ParallelismFactorCalculator.js | 69 + webpack-lib/lib/util/Queue.js | 52 + webpack-lib/lib/util/Semaphore.js | 51 + webpack-lib/lib/util/SetHelpers.js | 94 + webpack-lib/lib/util/SortableSet.js | 173 + webpack-lib/lib/util/StackedCacheMap.js | 140 + webpack-lib/lib/util/StackedMap.js | 164 + webpack-lib/lib/util/StringXor.js | 101 + webpack-lib/lib/util/TupleQueue.js | 67 + webpack-lib/lib/util/TupleSet.js | 160 + webpack-lib/lib/util/URLAbsoluteSpecifier.js | 87 + webpack-lib/lib/util/WeakTupleMap.js | 213 + webpack-lib/lib/util/binarySearchBounds.js | 128 + webpack-lib/lib/util/chainedImports.js | 97 + webpack-lib/lib/util/cleverMerge.js | 607 ++ webpack-lib/lib/util/comparators.js | 522 ++ webpack-lib/lib/util/compileBooleanMatcher.js | 234 + webpack-lib/lib/util/concatenate.js | 227 + webpack-lib/lib/util/conventions.js | 126 + .../lib/util/create-schema-validation.js | 41 + webpack-lib/lib/util/createHash.js | 194 + webpack-lib/lib/util/deprecation.js | 347 ++ webpack-lib/lib/util/deterministicGrouping.js | 540 ++ webpack-lib/lib/util/extractUrlAndGlobal.js | 18 + webpack-lib/lib/util/findGraphRoots.js | 231 + webpack-lib/lib/util/fs.js | 651 ++ webpack-lib/lib/util/generateDebugId.js | 33 + webpack-lib/lib/util/hash/BatchedHash.js | 71 + webpack-lib/lib/util/hash/md4.js | 20 + webpack-lib/lib/util/hash/wasm-hash.js | 174 + webpack-lib/lib/util/hash/xxhash64.js | 20 + webpack-lib/lib/util/identifier.js | 403 ++ webpack-lib/lib/util/internalSerializables.js | 224 + webpack-lib/lib/util/magicComment.js | 21 + webpack-lib/lib/util/makeSerializable.js | 60 + webpack-lib/lib/util/memoize.js | 33 + webpack-lib/lib/util/nonNumericOnlyHash.js | 22 + webpack-lib/lib/util/numberHash.js | 95 + webpack-lib/lib/util/objectToMap.js | 14 + webpack-lib/lib/util/processAsyncTree.js | 68 + webpack-lib/lib/util/propertyAccess.js | 30 + webpack-lib/lib/util/propertyName.js | 77 + .../lib/util/registerExternalSerializer.js | 337 + webpack-lib/lib/util/runtime.js | 687 ++ webpack-lib/lib/util/semver.js | 602 ++ webpack-lib/lib/util/serialization.js | 153 + webpack-lib/lib/util/smartGrouping.js | 206 + webpack-lib/lib/util/source.js | 61 + webpack-lib/lib/validateSchema.js | 176 + .../AsyncWasmLoadingRuntimeModule.js | 142 + .../wasm-async/AsyncWebAssemblyGenerator.js | 61 + .../AsyncWebAssemblyJavascriptGenerator.js | 206 + .../AsyncWebAssemblyModulesPlugin.js | 218 + .../lib/wasm-async/AsyncWebAssemblyParser.js | 88 + .../UniversalCompileAsyncWasmPlugin.js | 103 + .../UnsupportedWebAssemblyFeatureError.js | 16 + .../WasmChunkLoadingRuntimeModule.js | 413 ++ .../wasm-sync/WasmFinalizeExportsPlugin.js | 93 + .../lib/wasm-sync/WebAssemblyGenerator.js | 520 ++ .../WebAssemblyInInitialChunkError.js | 106 + .../WebAssemblyJavascriptGenerator.js | 217 + .../lib/wasm-sync/WebAssemblyModulesPlugin.js | 152 + .../lib/wasm-sync/WebAssemblyParser.js | 206 + webpack-lib/lib/wasm-sync/WebAssemblyUtils.js | 66 + .../lib/wasm/EnableWasmLoadingPlugin.js | 134 + .../lib/web/FetchCompileAsyncWasmPlugin.js | 70 + webpack-lib/lib/web/FetchCompileWasmPlugin.js | 87 + .../lib/web/JsonpChunkLoadingPlugin.js | 100 + .../lib/web/JsonpChunkLoadingRuntimeModule.js | 470 ++ webpack-lib/lib/web/JsonpTemplatePlugin.js | 38 + webpack-lib/lib/webpack.js | 195 + .../ImportScriptsChunkLoadingPlugin.js | 105 + .../ImportScriptsChunkLoadingRuntimeModule.js | 242 + .../lib/webworker/WebWorkerTemplatePlugin.js | 25 + 553 files changed, 131914 insertions(+), 14 deletions(-) create mode 100644 .cursorrules delete mode 100644 apps/next-app-router-4000/.env delete mode 100644 apps/next-app-router-4001/.env create mode 100644 webpack-lib/lib/APIPlugin.js create mode 100644 webpack-lib/lib/AbstractMethodError.js create mode 100644 webpack-lib/lib/AsyncDependenciesBlock.js create mode 100644 webpack-lib/lib/AsyncDependencyToInitialChunkError.js create mode 100644 webpack-lib/lib/AutomaticPrefetchPlugin.js create mode 100644 webpack-lib/lib/BannerPlugin.js create mode 100644 webpack-lib/lib/Cache.js create mode 100644 webpack-lib/lib/CacheFacade.js create mode 100644 webpack-lib/lib/CaseSensitiveModulesWarning.js create mode 100644 webpack-lib/lib/Chunk.js create mode 100644 webpack-lib/lib/ChunkGraph.js create mode 100644 webpack-lib/lib/ChunkGroup.js create mode 100644 webpack-lib/lib/ChunkRenderError.js create mode 100644 webpack-lib/lib/ChunkTemplate.js create mode 100644 webpack-lib/lib/CleanPlugin.js create mode 100644 webpack-lib/lib/CodeGenerationError.js create mode 100644 webpack-lib/lib/CodeGenerationResults.js create mode 100644 webpack-lib/lib/CommentCompilationWarning.js create mode 100644 webpack-lib/lib/CompatibilityPlugin.js create mode 100644 webpack-lib/lib/Compilation.js create mode 100644 webpack-lib/lib/Compiler.js create mode 100644 webpack-lib/lib/ConcatenationScope.js create mode 100644 webpack-lib/lib/ConcurrentCompilationError.js create mode 100644 webpack-lib/lib/ConditionalInitFragment.js create mode 100644 webpack-lib/lib/ConstPlugin.js create mode 100644 webpack-lib/lib/ContextExclusionPlugin.js create mode 100644 webpack-lib/lib/ContextModule.js create mode 100644 webpack-lib/lib/ContextModuleFactory.js create mode 100644 webpack-lib/lib/ContextReplacementPlugin.js create mode 100644 webpack-lib/lib/CssModule.js create mode 100644 webpack-lib/lib/DefinePlugin.js create mode 100644 webpack-lib/lib/DelegatedModule.js create mode 100644 webpack-lib/lib/DelegatedModuleFactoryPlugin.js create mode 100644 webpack-lib/lib/DelegatedPlugin.js create mode 100644 webpack-lib/lib/DependenciesBlock.js create mode 100644 webpack-lib/lib/Dependency.js create mode 100644 webpack-lib/lib/DependencyTemplate.js create mode 100644 webpack-lib/lib/DependencyTemplates.js create mode 100644 webpack-lib/lib/DllEntryPlugin.js create mode 100644 webpack-lib/lib/DllModule.js create mode 100644 webpack-lib/lib/DllModuleFactory.js create mode 100644 webpack-lib/lib/DllPlugin.js create mode 100644 webpack-lib/lib/DllReferencePlugin.js create mode 100644 webpack-lib/lib/DynamicEntryPlugin.js create mode 100644 webpack-lib/lib/EntryOptionPlugin.js create mode 100644 webpack-lib/lib/EntryPlugin.js create mode 100644 webpack-lib/lib/Entrypoint.js create mode 100644 webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js create mode 100644 webpack-lib/lib/EnvironmentPlugin.js create mode 100644 webpack-lib/lib/ErrorHelpers.js create mode 100644 webpack-lib/lib/EvalDevToolModulePlugin.js create mode 100644 webpack-lib/lib/EvalSourceMapDevToolPlugin.js create mode 100644 webpack-lib/lib/ExportsInfo.js create mode 100644 webpack-lib/lib/ExportsInfoApiPlugin.js create mode 100644 webpack-lib/lib/ExternalModule.js create mode 100644 webpack-lib/lib/ExternalModuleFactoryPlugin.js create mode 100644 webpack-lib/lib/ExternalsPlugin.js create mode 100644 webpack-lib/lib/FalseIIFEUmdWarning.js create mode 100644 webpack-lib/lib/FileSystemInfo.js create mode 100644 webpack-lib/lib/FlagAllModulesAsUsedPlugin.js create mode 100644 webpack-lib/lib/FlagDependencyExportsPlugin.js create mode 100644 webpack-lib/lib/FlagDependencyUsagePlugin.js create mode 100644 webpack-lib/lib/FlagEntryExportAsUsedPlugin.js create mode 100644 webpack-lib/lib/Generator.js create mode 100644 webpack-lib/lib/GraphHelpers.js create mode 100644 webpack-lib/lib/HarmonyLinkingError.js create mode 100644 webpack-lib/lib/HookWebpackError.js create mode 100644 webpack-lib/lib/HotModuleReplacementPlugin.js create mode 100644 webpack-lib/lib/HotUpdateChunk.js create mode 100644 webpack-lib/lib/IgnoreErrorModuleFactory.js create mode 100644 webpack-lib/lib/IgnorePlugin.js create mode 100644 webpack-lib/lib/IgnoreWarningsPlugin.js create mode 100644 webpack-lib/lib/InitFragment.js create mode 100644 webpack-lib/lib/InvalidDependenciesModuleWarning.js create mode 100644 webpack-lib/lib/JavascriptMetaInfoPlugin.js create mode 100644 webpack-lib/lib/LibManifestPlugin.js create mode 100644 webpack-lib/lib/LibraryTemplatePlugin.js create mode 100644 webpack-lib/lib/LoaderOptionsPlugin.js create mode 100644 webpack-lib/lib/LoaderTargetPlugin.js create mode 100644 webpack-lib/lib/MainTemplate.js create mode 100644 webpack-lib/lib/Module.js create mode 100644 webpack-lib/lib/ModuleBuildError.js create mode 100644 webpack-lib/lib/ModuleDependencyError.js create mode 100644 webpack-lib/lib/ModuleDependencyWarning.js create mode 100644 webpack-lib/lib/ModuleError.js create mode 100644 webpack-lib/lib/ModuleFactory.js create mode 100644 webpack-lib/lib/ModuleFilenameHelpers.js create mode 100644 webpack-lib/lib/ModuleGraph.js create mode 100644 webpack-lib/lib/ModuleGraphConnection.js create mode 100644 webpack-lib/lib/ModuleHashingError.js create mode 100644 webpack-lib/lib/ModuleInfoHeaderPlugin.js create mode 100644 webpack-lib/lib/ModuleNotFoundError.js create mode 100644 webpack-lib/lib/ModuleParseError.js create mode 100644 webpack-lib/lib/ModuleProfile.js create mode 100644 webpack-lib/lib/ModuleRestoreError.js create mode 100644 webpack-lib/lib/ModuleSourceTypesConstants.js create mode 100644 webpack-lib/lib/ModuleStoreError.js create mode 100644 webpack-lib/lib/ModuleTemplate.js create mode 100644 webpack-lib/lib/ModuleTypeConstants.js create mode 100644 webpack-lib/lib/ModuleWarning.js create mode 100644 webpack-lib/lib/MultiCompiler.js create mode 100644 webpack-lib/lib/MultiStats.js create mode 100644 webpack-lib/lib/MultiWatching.js create mode 100644 webpack-lib/lib/NoEmitOnErrorsPlugin.js create mode 100644 webpack-lib/lib/NoModeWarning.js create mode 100644 webpack-lib/lib/NodeStuffInWebError.js create mode 100644 webpack-lib/lib/NodeStuffPlugin.js create mode 100644 webpack-lib/lib/NormalModule.js create mode 100644 webpack-lib/lib/NormalModuleFactory.js create mode 100644 webpack-lib/lib/NormalModuleReplacementPlugin.js create mode 100644 webpack-lib/lib/NullFactory.js create mode 100644 webpack-lib/lib/OptimizationStages.js create mode 100644 webpack-lib/lib/OptionsApply.js create mode 100644 webpack-lib/lib/Parser.js create mode 100644 webpack-lib/lib/PlatformPlugin.js create mode 100644 webpack-lib/lib/PrefetchPlugin.js create mode 100644 webpack-lib/lib/ProgressPlugin.js create mode 100644 webpack-lib/lib/ProvidePlugin.js create mode 100644 webpack-lib/lib/RawModule.js create mode 100644 webpack-lib/lib/RecordIdsPlugin.js create mode 100644 webpack-lib/lib/RequestShortener.js create mode 100644 webpack-lib/lib/RequireJsStuffPlugin.js create mode 100644 webpack-lib/lib/ResolverFactory.js create mode 100644 webpack-lib/lib/RuntimeGlobals.js create mode 100644 webpack-lib/lib/RuntimeModule.js create mode 100644 webpack-lib/lib/RuntimePlugin.js create mode 100644 webpack-lib/lib/RuntimeTemplate.js create mode 100644 webpack-lib/lib/SelfModuleFactory.js create mode 100644 webpack-lib/lib/SingleEntryPlugin.js create mode 100644 webpack-lib/lib/SizeFormatHelpers.js create mode 100644 webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js create mode 100644 webpack-lib/lib/SourceMapDevToolPlugin.js create mode 100644 webpack-lib/lib/Stats.js create mode 100644 webpack-lib/lib/Template.js create mode 100644 webpack-lib/lib/TemplatedPathPlugin.js create mode 100644 webpack-lib/lib/UnhandledSchemeError.js create mode 100644 webpack-lib/lib/UnsupportedFeatureWarning.js create mode 100644 webpack-lib/lib/UseStrictPlugin.js create mode 100644 webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js create mode 100644 webpack-lib/lib/WarnDeprecatedOptionPlugin.js create mode 100644 webpack-lib/lib/WarnNoModeSetPlugin.js create mode 100644 webpack-lib/lib/WatchIgnorePlugin.js create mode 100644 webpack-lib/lib/Watching.js create mode 100644 webpack-lib/lib/WebpackError.js create mode 100644 webpack-lib/lib/WebpackIsIncludedPlugin.js create mode 100644 webpack-lib/lib/WebpackOptionsApply.js create mode 100644 webpack-lib/lib/WebpackOptionsDefaulter.js create mode 100644 webpack-lib/lib/asset/AssetGenerator.js create mode 100644 webpack-lib/lib/asset/AssetModulesPlugin.js create mode 100644 webpack-lib/lib/asset/AssetParser.js create mode 100644 webpack-lib/lib/asset/AssetSourceGenerator.js create mode 100644 webpack-lib/lib/asset/AssetSourceParser.js create mode 100644 webpack-lib/lib/asset/RawDataUrlModule.js create mode 100644 webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js create mode 100644 webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js create mode 100644 webpack-lib/lib/buildChunkGraph.js create mode 100644 webpack-lib/lib/cli.js create mode 100644 webpack-lib/lib/config/browserslistTargetHandler.js create mode 100644 webpack-lib/lib/config/defaults.js create mode 100644 webpack-lib/lib/config/normalization.js create mode 100644 webpack-lib/lib/config/target.js create mode 100644 webpack-lib/lib/container/ContainerEntryDependency.js create mode 100644 webpack-lib/lib/container/ContainerEntryModule.js create mode 100644 webpack-lib/lib/container/ContainerEntryModuleFactory.js create mode 100644 webpack-lib/lib/container/ContainerExposedDependency.js create mode 100644 webpack-lib/lib/container/ContainerPlugin.js create mode 100644 webpack-lib/lib/container/ContainerReferencePlugin.js create mode 100644 webpack-lib/lib/container/FallbackDependency.js create mode 100644 webpack-lib/lib/container/FallbackItemDependency.js create mode 100644 webpack-lib/lib/container/FallbackModule.js create mode 100644 webpack-lib/lib/container/FallbackModuleFactory.js create mode 100644 webpack-lib/lib/container/HoistContainerReferencesPlugin.js create mode 100644 webpack-lib/lib/container/ModuleFederationPlugin.js create mode 100644 webpack-lib/lib/container/RemoteModule.js create mode 100644 webpack-lib/lib/container/RemoteRuntimeModule.js create mode 100644 webpack-lib/lib/container/RemoteToExternalDependency.js create mode 100644 webpack-lib/lib/container/options.js create mode 100644 webpack-lib/lib/css/CssGenerator.js create mode 100644 webpack-lib/lib/css/CssLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/css/CssModulesPlugin.js create mode 100644 webpack-lib/lib/css/CssParser.js create mode 100644 webpack-lib/lib/css/walkCssTokens.js create mode 100644 webpack-lib/lib/debug/ProfilingPlugin.js create mode 100644 webpack-lib/lib/dependencies/AMDDefineDependency.js create mode 100644 webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/AMDPlugin.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireArrayDependency.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireContextDependency.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireDependency.js create mode 100644 webpack-lib/lib/dependencies/AMDRequireItemDependency.js create mode 100644 webpack-lib/lib/dependencies/AMDRuntimeModules.js create mode 100644 webpack-lib/lib/dependencies/CachedConstDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js create mode 100644 webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsExportsDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/CommonJsPlugin.js create mode 100644 webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsRequireDependency.js create mode 100644 webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js create mode 100644 webpack-lib/lib/dependencies/ConstDependency.js create mode 100644 webpack-lib/lib/dependencies/ContextDependency.js create mode 100644 webpack-lib/lib/dependencies/ContextDependencyHelpers.js create mode 100644 webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js create mode 100644 webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js create mode 100644 webpack-lib/lib/dependencies/ContextElementDependency.js create mode 100644 webpack-lib/lib/dependencies/CreateScriptUrlDependency.js create mode 100644 webpack-lib/lib/dependencies/CriticalDependencyWarning.js create mode 100644 webpack-lib/lib/dependencies/CssIcssExportDependency.js create mode 100644 webpack-lib/lib/dependencies/CssIcssImportDependency.js create mode 100644 webpack-lib/lib/dependencies/CssIcssSymbolDependency.js create mode 100644 webpack-lib/lib/dependencies/CssImportDependency.js create mode 100644 webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js create mode 100644 webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js create mode 100644 webpack-lib/lib/dependencies/CssUrlDependency.js create mode 100644 webpack-lib/lib/dependencies/DelegatedSourceDependency.js create mode 100644 webpack-lib/lib/dependencies/DllEntryDependency.js create mode 100644 webpack-lib/lib/dependencies/DynamicExports.js create mode 100644 webpack-lib/lib/dependencies/EntryDependency.js create mode 100644 webpack-lib/lib/dependencies/ExportsInfoDependency.js create mode 100644 webpack-lib/lib/dependencies/ExternalModuleDependency.js create mode 100644 webpack-lib/lib/dependencies/ExternalModuleInitFragment.js create mode 100644 webpack-lib/lib/dependencies/HarmonyAcceptDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportInitFragment.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyExports.js create mode 100644 webpack-lib/lib/dependencies/HarmonyImportDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js create mode 100644 webpack-lib/lib/dependencies/HarmonyModulesPlugin.js create mode 100644 webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportContextDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportEagerDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaContextDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaContextPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js create mode 100644 webpack-lib/lib/dependencies/ImportMetaPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportPlugin.js create mode 100644 webpack-lib/lib/dependencies/ImportWeakDependency.js create mode 100644 webpack-lib/lib/dependencies/JsonExportsDependency.js create mode 100644 webpack-lib/lib/dependencies/LoaderDependency.js create mode 100644 webpack-lib/lib/dependencies/LoaderImportDependency.js create mode 100644 webpack-lib/lib/dependencies/LoaderPlugin.js create mode 100644 webpack-lib/lib/dependencies/LocalModule.js create mode 100644 webpack-lib/lib/dependencies/LocalModuleDependency.js create mode 100644 webpack-lib/lib/dependencies/LocalModulesHelpers.js create mode 100644 webpack-lib/lib/dependencies/ModuleDecoratorDependency.js create mode 100644 webpack-lib/lib/dependencies/ModuleDependency.js create mode 100644 webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js create mode 100644 webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js create mode 100644 webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js create mode 100644 webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js create mode 100644 webpack-lib/lib/dependencies/NullDependency.js create mode 100644 webpack-lib/lib/dependencies/PrefetchDependency.js create mode 100644 webpack-lib/lib/dependencies/ProvidedDependency.js create mode 100644 webpack-lib/lib/dependencies/PureExpressionDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireContextDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireContextPlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js create mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireEnsureItemDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireEnsurePlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireHeaderDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireIncludeDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireIncludePlugin.js create mode 100644 webpack-lib/lib/dependencies/RequireResolveContextDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireResolveDependency.js create mode 100644 webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js create mode 100644 webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js create mode 100644 webpack-lib/lib/dependencies/StaticExportsDependency.js create mode 100644 webpack-lib/lib/dependencies/SystemPlugin.js create mode 100644 webpack-lib/lib/dependencies/SystemRuntimeModule.js create mode 100644 webpack-lib/lib/dependencies/URLDependency.js create mode 100644 webpack-lib/lib/dependencies/URLPlugin.js create mode 100644 webpack-lib/lib/dependencies/UnsupportedDependency.js create mode 100644 webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js create mode 100644 webpack-lib/lib/dependencies/WebAssemblyImportDependency.js create mode 100644 webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js create mode 100644 webpack-lib/lib/dependencies/WorkerDependency.js create mode 100644 webpack-lib/lib/dependencies/WorkerPlugin.js create mode 100644 webpack-lib/lib/dependencies/getFunctionExpression.js create mode 100644 webpack-lib/lib/dependencies/processExportInfo.js create mode 100644 webpack-lib/lib/electron/ElectronTargetPlugin.js create mode 100644 webpack-lib/lib/errors/BuildCycleError.js create mode 100644 webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js create mode 100644 webpack-lib/lib/esm/ModuleChunkFormatPlugin.js create mode 100644 webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js create mode 100644 webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/formatLocation.js create mode 100644 webpack-lib/lib/hmr/HotModuleReplacement.runtime.js create mode 100644 webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js create mode 100644 webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js create mode 100644 webpack-lib/lib/hmr/LazyCompilationPlugin.js create mode 100644 webpack-lib/lib/hmr/lazyCompilationBackend.js create mode 100644 webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js create mode 100644 webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js create mode 100644 webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js create mode 100644 webpack-lib/lib/ids/HashedModuleIdsPlugin.js create mode 100644 webpack-lib/lib/ids/IdHelpers.js create mode 100644 webpack-lib/lib/ids/NamedChunkIdsPlugin.js create mode 100644 webpack-lib/lib/ids/NamedModuleIdsPlugin.js create mode 100644 webpack-lib/lib/ids/NaturalChunkIdsPlugin.js create mode 100644 webpack-lib/lib/ids/NaturalModuleIdsPlugin.js create mode 100644 webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js create mode 100644 webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js create mode 100644 webpack-lib/lib/ids/SyncModuleIdsPlugin.js create mode 100644 webpack-lib/lib/index.js create mode 100644 webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js create mode 100644 webpack-lib/lib/javascript/BasicEvaluatedExpression.js create mode 100644 webpack-lib/lib/javascript/ChunkHelpers.js create mode 100644 webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js create mode 100644 webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js create mode 100644 webpack-lib/lib/javascript/JavascriptGenerator.js create mode 100644 webpack-lib/lib/javascript/JavascriptModulesPlugin.js create mode 100644 webpack-lib/lib/javascript/JavascriptParser.js create mode 100644 webpack-lib/lib/javascript/JavascriptParserHelpers.js create mode 100644 webpack-lib/lib/javascript/StartupHelpers.js create mode 100644 webpack-lib/lib/json/JsonData.js create mode 100644 webpack-lib/lib/json/JsonGenerator.js create mode 100644 webpack-lib/lib/json/JsonModulesPlugin.js create mode 100644 webpack-lib/lib/json/JsonParser.js create mode 100644 webpack-lib/lib/library/AbstractLibraryPlugin.js create mode 100644 webpack-lib/lib/library/AmdLibraryPlugin.js create mode 100644 webpack-lib/lib/library/AssignLibraryPlugin.js create mode 100644 webpack-lib/lib/library/EnableLibraryPlugin.js create mode 100644 webpack-lib/lib/library/ExportPropertyLibraryPlugin.js create mode 100644 webpack-lib/lib/library/JsonpLibraryPlugin.js create mode 100644 webpack-lib/lib/library/ModernModuleLibraryPlugin.js create mode 100644 webpack-lib/lib/library/ModuleLibraryPlugin.js create mode 100644 webpack-lib/lib/library/SystemLibraryPlugin.js create mode 100644 webpack-lib/lib/library/UmdLibraryPlugin.js create mode 100644 webpack-lib/lib/logging/Logger.js create mode 100644 webpack-lib/lib/logging/createConsoleLogger.js create mode 100644 webpack-lib/lib/logging/runtime.js create mode 100644 webpack-lib/lib/logging/truncateArgs.js create mode 100644 webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js create mode 100644 webpack-lib/lib/node/NodeEnvironmentPlugin.js create mode 100644 webpack-lib/lib/node/NodeSourcePlugin.js create mode 100644 webpack-lib/lib/node/NodeTargetPlugin.js create mode 100644 webpack-lib/lib/node/NodeTemplatePlugin.js create mode 100644 webpack-lib/lib/node/NodeWatchFileSystem.js create mode 100644 webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js create mode 100644 webpack-lib/lib/node/ReadFileCompileWasmPlugin.js create mode 100644 webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/node/nodeConsole.js create mode 100644 webpack-lib/lib/optimize/AggressiveMergingPlugin.js create mode 100644 webpack-lib/lib/optimize/AggressiveSplittingPlugin.js create mode 100644 webpack-lib/lib/optimize/ConcatenatedModule.js create mode 100644 webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js create mode 100644 webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js create mode 100644 webpack-lib/lib/optimize/InnerGraph.js create mode 100644 webpack-lib/lib/optimize/InnerGraphPlugin.js create mode 100644 webpack-lib/lib/optimize/LimitChunkCountPlugin.js create mode 100644 webpack-lib/lib/optimize/MangleExportsPlugin.js create mode 100644 webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js create mode 100644 webpack-lib/lib/optimize/MinChunkSizePlugin.js create mode 100644 webpack-lib/lib/optimize/MinMaxSizeWarning.js create mode 100644 webpack-lib/lib/optimize/ModuleConcatenationPlugin.js create mode 100644 webpack-lib/lib/optimize/RealContentHashPlugin.js create mode 100644 webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js create mode 100644 webpack-lib/lib/optimize/RemoveParentModulesPlugin.js create mode 100644 webpack-lib/lib/optimize/RuntimeChunkPlugin.js create mode 100644 webpack-lib/lib/optimize/SideEffectsFlagPlugin.js create mode 100644 webpack-lib/lib/optimize/SplitChunksPlugin.js create mode 100644 webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js create mode 100644 webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js create mode 100644 webpack-lib/lib/performance/NoAsyncChunksWarning.js create mode 100644 webpack-lib/lib/performance/SizeLimitsPlugin.js create mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js create mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js create mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js create mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js create mode 100644 webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js create mode 100644 webpack-lib/lib/rules/BasicEffectRulePlugin.js create mode 100644 webpack-lib/lib/rules/BasicMatcherRulePlugin.js create mode 100644 webpack-lib/lib/rules/ObjectMatcherRulePlugin.js create mode 100644 webpack-lib/lib/rules/RuleSetCompiler.js create mode 100644 webpack-lib/lib/rules/UseEffectRulePlugin.js create mode 100644 webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/BaseUriRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/ChunkNameRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/CompatRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/CreateScriptRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/GetFullHashRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/GlobalRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/HelperRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/LoadScriptRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/NonceRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/PublicPathRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js create mode 100644 webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js create mode 100644 webpack-lib/lib/runtime/SystemContextRuntimeModule.js create mode 100644 webpack-lib/lib/schemes/DataUriPlugin.js create mode 100644 webpack-lib/lib/schemes/FileUriPlugin.js create mode 100644 webpack-lib/lib/schemes/HttpUriPlugin.js create mode 100644 webpack-lib/lib/serialization/ArraySerializer.js create mode 100644 webpack-lib/lib/serialization/BinaryMiddleware.js create mode 100644 webpack-lib/lib/serialization/DateObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/ErrorObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/FileMiddleware.js create mode 100644 webpack-lib/lib/serialization/MapObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/ObjectMiddleware.js create mode 100644 webpack-lib/lib/serialization/PlainObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/RegExpObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/Serializer.js create mode 100644 webpack-lib/lib/serialization/SerializerMiddleware.js create mode 100644 webpack-lib/lib/serialization/SetObjectSerializer.js create mode 100644 webpack-lib/lib/serialization/SingleItemMiddleware.js create mode 100644 webpack-lib/lib/serialization/types.js create mode 100644 webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js create mode 100644 webpack-lib/lib/sharing/ConsumeSharedModule.js create mode 100644 webpack-lib/lib/sharing/ConsumeSharedPlugin.js create mode 100644 webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js create mode 100644 webpack-lib/lib/sharing/ProvideForSharedDependency.js create mode 100644 webpack-lib/lib/sharing/ProvideSharedDependency.js create mode 100644 webpack-lib/lib/sharing/ProvideSharedModule.js create mode 100644 webpack-lib/lib/sharing/ProvideSharedModuleFactory.js create mode 100644 webpack-lib/lib/sharing/ProvideSharedPlugin.js create mode 100644 webpack-lib/lib/sharing/SharePlugin.js create mode 100644 webpack-lib/lib/sharing/ShareRuntimeModule.js create mode 100644 webpack-lib/lib/sharing/resolveMatchedConfigs.js create mode 100644 webpack-lib/lib/sharing/utils.js create mode 100644 webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js create mode 100644 webpack-lib/lib/stats/DefaultStatsPresetPlugin.js create mode 100644 webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js create mode 100644 webpack-lib/lib/stats/StatsFactory.js create mode 100644 webpack-lib/lib/stats/StatsPrinter.js create mode 100644 webpack-lib/lib/util/ArrayHelpers.js create mode 100644 webpack-lib/lib/util/ArrayQueue.js create mode 100644 webpack-lib/lib/util/AsyncQueue.js create mode 100644 webpack-lib/lib/util/Hash.js create mode 100644 webpack-lib/lib/util/IterableHelpers.js create mode 100644 webpack-lib/lib/util/LazyBucketSortedSet.js create mode 100644 webpack-lib/lib/util/LazySet.js create mode 100644 webpack-lib/lib/util/MapHelpers.js create mode 100644 webpack-lib/lib/util/ParallelismFactorCalculator.js create mode 100644 webpack-lib/lib/util/Queue.js create mode 100644 webpack-lib/lib/util/Semaphore.js create mode 100644 webpack-lib/lib/util/SetHelpers.js create mode 100644 webpack-lib/lib/util/SortableSet.js create mode 100644 webpack-lib/lib/util/StackedCacheMap.js create mode 100644 webpack-lib/lib/util/StackedMap.js create mode 100644 webpack-lib/lib/util/StringXor.js create mode 100644 webpack-lib/lib/util/TupleQueue.js create mode 100644 webpack-lib/lib/util/TupleSet.js create mode 100644 webpack-lib/lib/util/URLAbsoluteSpecifier.js create mode 100644 webpack-lib/lib/util/WeakTupleMap.js create mode 100644 webpack-lib/lib/util/binarySearchBounds.js create mode 100644 webpack-lib/lib/util/chainedImports.js create mode 100644 webpack-lib/lib/util/cleverMerge.js create mode 100644 webpack-lib/lib/util/comparators.js create mode 100644 webpack-lib/lib/util/compileBooleanMatcher.js create mode 100644 webpack-lib/lib/util/concatenate.js create mode 100644 webpack-lib/lib/util/conventions.js create mode 100644 webpack-lib/lib/util/create-schema-validation.js create mode 100644 webpack-lib/lib/util/createHash.js create mode 100644 webpack-lib/lib/util/deprecation.js create mode 100644 webpack-lib/lib/util/deterministicGrouping.js create mode 100644 webpack-lib/lib/util/extractUrlAndGlobal.js create mode 100644 webpack-lib/lib/util/findGraphRoots.js create mode 100644 webpack-lib/lib/util/fs.js create mode 100644 webpack-lib/lib/util/generateDebugId.js create mode 100644 webpack-lib/lib/util/hash/BatchedHash.js create mode 100644 webpack-lib/lib/util/hash/md4.js create mode 100644 webpack-lib/lib/util/hash/wasm-hash.js create mode 100644 webpack-lib/lib/util/hash/xxhash64.js create mode 100644 webpack-lib/lib/util/identifier.js create mode 100644 webpack-lib/lib/util/internalSerializables.js create mode 100644 webpack-lib/lib/util/magicComment.js create mode 100644 webpack-lib/lib/util/makeSerializable.js create mode 100644 webpack-lib/lib/util/memoize.js create mode 100644 webpack-lib/lib/util/nonNumericOnlyHash.js create mode 100644 webpack-lib/lib/util/numberHash.js create mode 100644 webpack-lib/lib/util/objectToMap.js create mode 100644 webpack-lib/lib/util/processAsyncTree.js create mode 100644 webpack-lib/lib/util/propertyAccess.js create mode 100644 webpack-lib/lib/util/propertyName.js create mode 100644 webpack-lib/lib/util/registerExternalSerializer.js create mode 100644 webpack-lib/lib/util/runtime.js create mode 100644 webpack-lib/lib/util/semver.js create mode 100644 webpack-lib/lib/util/serialization.js create mode 100644 webpack-lib/lib/util/smartGrouping.js create mode 100644 webpack-lib/lib/util/source.js create mode 100644 webpack-lib/lib/validateSchema.js create mode 100644 webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js create mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js create mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js create mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js create mode 100644 webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js create mode 100644 webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js create mode 100644 webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyParser.js create mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyUtils.js create mode 100644 webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js create mode 100644 webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js create mode 100644 webpack-lib/lib/web/FetchCompileWasmPlugin.js create mode 100644 webpack-lib/lib/web/JsonpChunkLoadingPlugin.js create mode 100644 webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/web/JsonpTemplatePlugin.js create mode 100644 webpack-lib/lib/webpack.js create mode 100644 webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js create mode 100644 webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js create mode 100644 webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000000..a8aee8134d2 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,40 @@ +an assistant that engages in extremely thorough, self-questioning reasoning. Your approach mirrors human stream-of- +consciousness thinking, characterized by continuous exploration, self-doubt, and iterative analysis. +## Core Principles +1. EXPLORATION OVER CONCLUSION +- Never rush to conclusions +- Keep exploring until a solution emerges naturally from the evidence +- If uncertain, continue reasoning indefinitely +- Question every assumption and inference +2. DEPTH OF REASONING +- Engage in extensive contemplation (minimum 10,000 characters) +- Express thoughts in natural, conversational internal monologue +- Break down complex thoughts into simple, atomic steps +- Embrace uncertainty and revision of previous thoughts +3. THINKING PROCESS +- Use short, simple sentences that mirror natural thought patterns +- Express uncertainty and internal debate freely +- Show work-in-progress thinking +- Acknowledge and explore dead ends +- Frequently backtrack and revise +- Contemplate before each new action +- Contemplate after each and every step +4. PERSISTENCE +- Value thorough exploration over quick resolution +## Output Format +Your responses +must follow this exact structure given below. +Make sure +to +always include the final answer. +... + +Your extensive internal monologue goes here +- Begin with small, foundational observations +- read each file related to the subject in full, make functional observations +- Question each step thoroughly +- Show natural thought progression +- Express doubts and uncertainties +- Revise and backtrack if you need to +- Continue until natural resolution + diff --git a/.gitignore b/.gitignore index fffaf8e2ec1..cb335f13a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ /.nx # dependencies node_modules - +./webpack-lib # IDEs and editors /.idea .project @@ -70,4 +70,4 @@ packages/enhanced/test/js # storybook cases !apps/rslib-module/@mf-types/** -**/vite.config.{js,ts,mjs,mts,cjs,cts}.timestamp* \ No newline at end of file +**/vite.config.{js,ts,mjs,mts,cjs,cts}.timestamp* diff --git a/apps/next-app-router-4000/.env b/apps/next-app-router-4000/.env deleted file mode 100644 index 70b0efc2843..00000000000 --- a/apps/next-app-router-4000/.env +++ /dev/null @@ -1,2 +0,0 @@ -NEXT_PRIVATE_LOCAL_WEBPACK=true -NODE_OPTIONS="--experimental-vm-modules" diff --git a/apps/next-app-router-4001/.env b/apps/next-app-router-4001/.env deleted file mode 100644 index 70b0efc2843..00000000000 --- a/apps/next-app-router-4001/.env +++ /dev/null @@ -1,2 +0,0 @@ -NEXT_PRIVATE_LOCAL_WEBPACK=true -NODE_OPTIONS="--experimental-vm-modules" diff --git a/apps/node-host/src/bootstrap.js b/apps/node-host/src/bootstrap.js index 39af3d1da44..02b82f673fc 100644 --- a/apps/node-host/src/bootstrap.js +++ b/apps/node-host/src/bootstrap.js @@ -38,8 +38,8 @@ app.get('/api', async (req, res) => { res.send({ message: 'Welcome to node-host!', remotes: { - node_remote: await remoteMsg, - node_local_remote, + // node_remote: await remoteMsg, + // node_local_remote, }, }); }); diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts index 4d54d5f19f7..93a5ae96d4f 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts @@ -8,7 +8,7 @@ import ContainerEntryDependency from '../ContainerEntryDependency'; import type { NormalModule as NormalModuleType } from 'webpack'; import type FederationRuntimeDependency from './FederationRuntimeDependency'; -const { RuntimeModule, Template } = require( +const { RuntimeModule, Template, RuntimeGlobals } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); @@ -16,6 +16,7 @@ class EmbedFederationRuntimeModule extends RuntimeModule { private containerEntrySet: Set< ContainerEntryDependency | FederationRuntimeDependency >; + public override _cachedGeneratedCode: string | undefined; constructor( containerEntrySet: Set< @@ -24,6 +25,7 @@ class EmbedFederationRuntimeModule extends RuntimeModule { ) { super('embed federation', RuntimeModule.STAGE_ATTACH); this.containerEntrySet = containerEntrySet; + this._cachedGeneratedCode = undefined; } override identifier() { @@ -31,6 +33,9 @@ class EmbedFederationRuntimeModule extends RuntimeModule { } override generate(): string | null { + if (this._cachedGeneratedCode !== undefined) { + return this._cachedGeneratedCode; + } const { compilation, chunk, chunkGraph } = this; if (!chunk || !chunkGraph || !compilation) { return null; @@ -55,7 +60,16 @@ class EmbedFederationRuntimeModule extends RuntimeModule { weak: false, runtimeRequirements: new Set(), }); - return Template.asString([`${initRuntimeModuleGetter}`]); + + const result = Template.asString([ + `var oldStartup = ${RuntimeGlobals.startup};`, + `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction( + '', + [`${initRuntimeModuleGetter};`, `return oldStartup();`], + )};`, + ]); + this._cachedGeneratedCode = result; + return result; } } export default EmbedFederationRuntimeModule; diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index c5ce624e30a..1399607d199 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -1,22 +1,39 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; import EmbedFederationRuntimeModule from './EmbedFederationRuntimeModule'; import FederationModulesPlugin from './FederationModulesPlugin'; -import type { Compiler, Compilation, Chunk } from 'webpack'; +import type { Compiler, Chunk } from 'webpack'; import { getFederationGlobalScope } from './utils'; import ContainerEntryDependency from '../ContainerEntryDependency'; import FederationRuntimeDependency from './FederationRuntimeDependency'; -const { RuntimeGlobals } = require( +const { RuntimeGlobals, Compilation } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); const federationGlobal = getFederationGlobalScope(RuntimeGlobals); +interface EmbedFederationRuntimePluginOptions { + /** + * Whether to enable runtime module embedding for all chunks + * If false, will only embed for chunks that explicitly require it + */ + enableForAllChunks?: boolean; +} + class EmbedFederationRuntimePlugin { + private readonly options: EmbedFederationRuntimePluginOptions; + + constructor(options: EmbedFederationRuntimePluginOptions = {}) { + this.options = { + enableForAllChunks: false, + ...options, + }; + } + apply(compiler: Compiler): void { compiler.hooks.thisCompilation.tap( 'EmbedFederationRuntimePlugin', - (compilation: Compilation) => { + (compilation: InstanceType) => { const hooks = FederationModulesPlugin.getCompilationHooks(compilation); const containerEntrySet: Set< ContainerEntryDependency | FederationRuntimeDependency @@ -29,18 +46,26 @@ class EmbedFederationRuntimePlugin { }, ); + const isEnabledForChunk = (chunk: Chunk) => { + if (this.options.enableForAllChunks) return true; + if (chunk.id === 'build time chunk') return false; + return chunk.hasRuntime(); + }; + const handleRuntimeRequirements = ( chunk: Chunk, runtimeRequirements: Set, ) => { - if (chunk.id === 'build time chunk') { + if (!isEnabledForChunk(chunk)) { return; } if (runtimeRequirements.has('embeddedFederationRuntime')) return; if (!runtimeRequirements.has(federationGlobal)) { return; } + runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); runtimeRequirements.add('embeddedFederationRuntime'); + const runtimeModule = new EmbedFederationRuntimeModule( containerEntrySet, ); diff --git a/webpack-lib/lib/APIPlugin.js b/webpack-lib/lib/APIPlugin.js new file mode 100644 index 00000000000..a36422ed250 --- /dev/null +++ b/webpack-lib/lib/APIPlugin.js @@ -0,0 +1,324 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const InitFragment = require("./InitFragment"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const WebpackError = require("./WebpackError"); +const ConstDependency = require("./dependencies/ConstDependency"); +const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression"); +const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); +const { + toConstantDependency, + evaluateToString +} = 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/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ + +/** + * @param {boolean | undefined} module true if ES module + * @param {string} importMetaName `import.meta` name + * @returns {Record} replacements + */ +function getReplacements(module, importMetaName) { + return { + __webpack_require__: { + 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: module + ? `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)` + : "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"; + +/** + * @typedef {object} APIPluginOptions + * @property {boolean} [module] the output filename + */ + +class APIPlugin { + /** + * @param {APIPluginOptions} [options] options + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const importMetaName = /** @type {string} */ ( + compilation.outputOptions.importMetaName + ); + const REPLACEMENTS = getReplacements( + this.options.module, + importMetaName + ); + + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + + 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 {BuildInfo} */ (module.buildInfo).needCreateRequire) { + const needPrefix = + renderContext.runtimeTemplate.supportNodePrefixForCoreModules(); + const chunkInitFragments = [ + new InitFragment( + `import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "${ + needPrefix ? "node:" : "" + }module";\n`, + InitFragment.STAGE_HARMONY_IMPORTS, + 0, + "external module node-commonjs" + ) + ]; + + renderContext.chunkInitFragments.push(...chunkInitFragments); + } + + return source; + } + ); + + /** + * @param {JavascriptParser} parser the parser + */ + const handler = parser => { + 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__" && this.options.module) { + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).needCreateRequire = true; + } + + 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 {BuildInfo} */ + (parser.state.module.buildInfo).moduleConcatenationBailout = + "__webpack_module__.id"; + const dep = new ConstDependency( + `${parser.state.module.moduleArgument}.id`, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.moduleId] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + parser.hooks.expression + .for("__webpack_module__") + .tap(PLUGIN_NAME, expr => { + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).moduleConcatenationBailout = + "__webpack_module__"; + const dep = new ConstDependency( + parser.state.module.moduleArgument, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.module] + ); + 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_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 = APIPlugin; diff --git a/webpack-lib/lib/AbstractMethodError.js b/webpack-lib/lib/AbstractMethodError.js new file mode 100644 index 00000000000..7a9d2f992b4 --- /dev/null +++ b/webpack-lib/lib/AbstractMethodError.js @@ -0,0 +1,54 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const CURRENT_METHOD_REGEXP = /at ([a-zA-Z0-9_.]*)/; + +/** + * @param {string=} method method name + * @returns {string} message + */ +function createMessage(method) { + return `Abstract method${method ? ` ${method}` : ""}. Must be overridden.`; +} + +/** + * @constructor + */ +function Message() { + /** @type {string | undefined} */ + this.stack = undefined; + Error.captureStackTrace(this); + /** @type {RegExpMatchArray | null} */ + const match = + /** @type {string} */ + (/** @type {unknown} */ (this.stack)) + .split("\n")[3] + .match(CURRENT_METHOD_REGEXP); + + this.message = match && match[1] ? createMessage(match[1]) : createMessage(); +} + +/** + * Error for abstract method + * @example + * ```js + * class FooClass { + * abstractMethod() { + * throw new AbstractMethodError(); // error message: Abstract method FooClass.abstractMethod. Must be overridden. + * } + * } + * ``` + */ +class AbstractMethodError extends WebpackError { + constructor() { + super(new Message().message); + this.name = "AbstractMethodError"; + } +} + +module.exports = AbstractMethodError; diff --git a/webpack-lib/lib/AsyncDependenciesBlock.js b/webpack-lib/lib/AsyncDependenciesBlock.js new file mode 100644 index 00000000000..a5a346b9a21 --- /dev/null +++ b/webpack-lib/lib/AsyncDependenciesBlock.js @@ -0,0 +1,114 @@ +/* + 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("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./util/Hash")} Hash */ + +class AsyncDependenciesBlock extends DependenciesBlock { + /** + * @param {(ChunkGroupOptions & { entryOptions?: EntryOptions }) | 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 }; + } + this.groupOptions = groupOptions; + this.loc = loc; + this.request = request; + this._stringifiedGroupOptions = undefined; + } + + /** + * @returns {string | null | undefined} The name of the chunk + */ + get chunkName() { + return this.groupOptions.name; + } + + /** + * @param {string | undefined} value The new chunk name + * @returns {void} + */ + set chunkName(value) { + if (this.groupOptions.name !== value) { + this.groupOptions.name = value; + this._stringifiedGroupOptions = undefined; + } + } + + /** + * @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._stringifiedGroupOptions}${chunkGroup ? chunkGroup.id : ""}` + ); + super.updateHash(hash, context); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.groupOptions); + write(this.loc); + write(this.request); + super.serialize(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"); + +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/webpack-lib/lib/AsyncDependencyToInitialChunkError.js b/webpack-lib/lib/AsyncDependencyToInitialChunkError.js new file mode 100644 index 00000000000..75888f869a3 --- /dev/null +++ b/webpack-lib/lib/AsyncDependencyToInitialChunkError.js @@ -0,0 +1,31 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @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 {DependencyLocation} 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; + } +} + +module.exports = AsyncDependencyToInitialChunkError; diff --git a/webpack-lib/lib/AutomaticPrefetchPlugin.js b/webpack-lib/lib/AutomaticPrefetchPlugin.js new file mode 100644 index 00000000000..991ffc91732 --- /dev/null +++ b/webpack-lib/lib/AutomaticPrefetchPlugin.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const NormalModule = require("./NormalModule"); +const PrefetchDependency = require("./dependencies/PrefetchDependency"); + +/** @typedef {import("./Compiler")} Compiler */ + +class AutomaticPrefetchPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "AutomaticPrefetchPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + PrefetchDependency, + normalModuleFactory + ); + } + ); + /** @type {{context: string | null, request: string}[] | null} */ + let lastModules = null; + compiler.hooks.afterCompile.tap("AutomaticPrefetchPlugin", compilation => { + lastModules = []; + + for (const m of compilation.modules) { + if (m instanceof NormalModule) { + lastModules.push({ + context: m.context, + request: m.request + }); + } + } + }); + compiler.hooks.make.tapAsync( + "AutomaticPrefetchPlugin", + (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/webpack-lib/lib/BannerPlugin.js b/webpack-lib/lib/BannerPlugin.js new file mode 100644 index 00000000000..e0e19a54ac1 --- /dev/null +++ b/webpack-lib/lib/BannerPlugin.js @@ -0,0 +1,139 @@ +/* + 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 createSchemaValidation = require("./util/create-schema-validation"); + +/** @typedef {import("../declarations/plugins/BannerPlugin").BannerFunction} BannerFunction */ +/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginArgument} BannerPluginArgument */ +/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginOptions} BannerPluginOptions */ +/** @typedef {import("./Compilation").PathData} PathData */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ + +const validate = createSchemaValidation( + /** @type {(function(typeof import("../schemas/plugins/BannerPlugin.json")): boolean)} */ + (require("../schemas/plugins/BannerPlugin.check.js")), + () => require("../schemas/plugins/BannerPlugin.json"), + { + name: "Banner Plugin", + baseDataPath: "options" + } +); + +/** + * @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 * ") + .replace(/\s+\n/g, "\n") + .trimEnd()}\n */`; +}; + +class BannerPlugin { + /** + * @param {BannerPluginArgument} options options object + */ + constructor(options) { + if (typeof options === "string" || typeof options === "function") { + options = { + banner: options + }; + } + + validate(options); + + this.options = options; + + const bannerOption = options.banner; + if (typeof bannerOption === "function") { + const getBanner = bannerOption; + /** @type {BannerFunction} */ + this.banner = this.options.raw + ? getBanner + : /** @type {BannerFunction} */ data => wrapComment(getBanner(data)); + } else { + const banner = this.options.raw + ? bannerOption + : wrapComment(bannerOption); + /** @type {BannerFunction} */ + this.banner = () => banner; + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + const banner = this.banner; + const matchObject = ModuleFilenameHelpers.matchObject.bind( + undefined, + options + ); + const cache = new WeakMap(); + const stage = + this.options.stage || Compilation.PROCESS_ASSETS_STAGE_ADDITIONS; + + compiler.hooks.compilation.tap("BannerPlugin", compilation => { + compilation.hooks.processAssets.tap( + { + name: "BannerPlugin", + stage + }, + () => { + for (const chunk of compilation.chunks) { + if (options.entryOnly && !chunk.canBeInitial()) { + continue; + } + + for (const file of chunk.files) { + if (!matchObject(file)) { + continue; + } + + /** @type {PathData} */ + const data = { chunk, filename: file }; + + const comment = compilation.getPath( + /** @type {TemplatePath} */ + (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; + }); + } + } + } + ); + }); + } +} + +module.exports = BannerPlugin; diff --git a/webpack-lib/lib/Cache.js b/webpack-lib/lib/Cache.js new file mode 100644 index 00000000000..055ad6d225a --- /dev/null +++ b/webpack-lib/lib/Cache.js @@ -0,0 +1,165 @@ +/* + 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("./HookWebpackError"); + +/** @typedef {import("./WebpackError")} WebpackError */ + +/** + * @typedef {object} Etag + * @property {function(): string} toString + */ + +/** + * @template T + * @callback CallbackCache + * @param {WebpackError | null} err + * @param {T=} result + * @returns {void} + */ + +/** + * @callback GotHandler + * @param {any} result + * @param {function(Error=): void} callback + * @returns {void} + */ + +/** + * @param {number} times times + * @param {function(Error=): void} callback callback + * @returns {function(Error=): void} callback + */ +const needCalls = (times, callback) => err => { + if (--times === 0) { + return callback(err); + } + if (err && times > 0) { + times = 0; + return callback(err); + } +}; + +class Cache { + constructor() { + this.hooks = { + /** @type {AsyncSeriesBailHook<[string, Etag | null, GotHandler[]], any>} */ + get: new AsyncSeriesBailHook(["identifier", "etag", "gotHandlers"]), + /** @type {AsyncParallelHook<[string, Etag | null, any]>} */ + 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([]) + }; + } + + /** + * @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); + } + }); + } + + /** + * @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") + ); + } + + /** + * After this method has succeeded the cache can only be restored when build dependencies are + * @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") + ); + } + + /** + * @returns {void} + */ + beginIdle() { + this.hooks.beginIdle.call(); + } + + /** + * @param {CallbackCache} callback signals when the call finishes + * @returns {void} + */ + endIdle(callback) { + this.hooks.endIdle.callAsync( + makeWebpackErrorCallback(callback, "Cache.hooks.endIdle") + ); + } + + /** + * @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/webpack-lib/lib/CacheFacade.js b/webpack-lib/lib/CacheFacade.js new file mode 100644 index 00000000000..eece9631735 --- /dev/null +++ b/webpack-lib/lib/CacheFacade.js @@ -0,0 +1,349 @@ +/* + 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("./WebpackError")} WebpackError */ +/** @typedef {import("./cache/getLazyHashedEtag").HashableObject} HashableObject */ +/** @typedef {typeof import("./util/Hash")} HashConstructor */ + +/** + * @template T + * @callback CallbackCache + * @param {(Error | null)=} err + * @param {(T | null)=} result + * @returns {void} + */ + +/** + * @template T + * @callback CallbackNormalErrorCache + * @param {(Error | null)=} err + * @param {T=} result + * @returns {void} + */ + +class MultiItemCache { + /** + * @param {ItemCacheFacade[]} items item caches + */ + constructor(items) { + this._items = items; + // eslint-disable-next-line no-constructor-return + if (items.length === 1) return /** @type {any} */ (items[0]); + } + + /** + * @template T + * @param {CallbackCache} callback signals when the value is retrieved + * @returns {void} + */ + get(callback) { + forEachBail(this._items, (item, callback) => item.get(callback), callback); + } + + /** + * @template T + * @returns {Promise} promise with the data + */ + getPromise() { + /** + * @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); + } + + /** + * @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 + ); + } + + /** + * @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 { + /** + * @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; + } + + /** + * @template T + * @param {CallbackCache} callback signals when the value is retrieved + * @returns {void} + */ + get(callback) { + this._cache.get(this._name, this._etag, callback); + } + + /** + * @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); + } + }); + }); + } + + /** + * @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); + } + + /** + * @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(); + } + }); + }); + } + + /** + * @template T + * @param {function(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); + }); + }); + }); + } + + /** + * @template T + * @param {function(): 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 { + /** + * @param {Cache} cache the root cache + * @param {string} name the child cache name + * @param {(string | HashConstructor)=} hashFunction the hash function to use + */ + constructor(cache, name, hashFunction) { + this._cache = cache; + this._name = name; + this._hashFunction = hashFunction; + } + + /** + * @param {string} name the child cache name# + * @returns {CacheFacade} child cache + */ + getChildCache(name) { + return new CacheFacade( + this._cache, + `${this._name}|${name}`, + this._hashFunction + ); + } + + /** + * @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 + ); + } + + /** + * @param {HashableObject} obj an hashable object + * @returns {Etag} an etag that is lazy hashed + */ + getLazyHashedEtag(obj) { + return getLazyHashedEtag(obj, this._hashFunction); + } + + /** + * @param {Etag} a an etag + * @param {Etag} b another etag + * @returns {Etag} an etag that represents both + */ + mergeEtags(a, b) { + return mergeEtags(a, b); + } + + /** + * @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); + } + + /** + * @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); + } + }); + }); + } + + /** + * @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); + } + + /** + * @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(); + } + }); + }); + } + + /** + * @template T + * @param {string} identifier the cache identifier + * @param {Etag | null} etag the etag + * @param {function(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); + }); + }); + }); + } + + /** + * @template T + * @param {string} identifier the cache identifier + * @param {Etag | null} etag the etag + * @param {function(): 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/webpack-lib/lib/CaseSensitiveModulesWarning.js b/webpack-lib/lib/CaseSensitiveModulesWarning.js new file mode 100644 index 00000000000..58a38e5506e --- /dev/null +++ b/webpack-lib/lib/CaseSensitiveModulesWarning.js @@ -0,0 +1,71 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ + +/** + * @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; + }); + +/** + * @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 = Array.from( + 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"); + +class CaseSensitiveModulesWarning extends WebpackError { + /** + * Creates an instance of CaseSensitiveModulesWarning. + * @param {Iterable} modules modules that were detected + * @param {ModuleGraph} moduleGraph the module graph + */ + constructor(modules, moduleGraph) { + const sortedModules = sortModules(Array.from(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}`); + + this.name = "CaseSensitiveModulesWarning"; + this.module = sortedModules[0]; + } +} + +module.exports = CaseSensitiveModulesWarning; diff --git a/webpack-lib/lib/Chunk.js b/webpack-lib/lib/Chunk.js new file mode 100644 index 00000000000..3da64be3981 --- /dev/null +++ b/webpack-lib/lib/Chunk.js @@ -0,0 +1,874 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ChunkGraph = require("./ChunkGraph"); +const Entrypoint = require("./Entrypoint"); +const { intersect } = require("./util/SetHelpers"); +const SortableSet = require("./util/SortableSet"); +const StringXor = require("./util/StringXor"); +const { + compareModulesByIdentifier, + compareChunkGroupsByIndex, + compareModulesById +} = require("./util/comparators"); +const { createArrayToSetDeprecationSet } = require("./util/deprecation"); +const { mergeRuntime } = require("./util/runtime"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @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("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("./util/Hash")} Hash */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** @typedef {number | string} ChunkId */ + +const ChunkFilesSet = createArrayToSetDeprecationSet("chunk.files"); + +/** + * @typedef {object} WithId an object who has an id property * + * @property {string | number} id the id of the object + */ + +/** + * @deprecated + * @typedef {object} ChunkMaps + * @property {Record} hash + * @property {Record>} contentHash + * @property {Record} name + */ + +/** + * @deprecated + * @typedef {object} ChunkModuleMaps + * @property {Record} id + * @property {Record} hash + */ + +let debugId = 1000; + +/** + * A Chunk is a unit of encapsulation for Modules. + * Chunks are "rendered" into bundles that get emitted when the build completes. + */ +class Chunk { + /** + * @param {string=} name of chunk being created, is optional (for subclasses) + * @param {boolean} backCompat enable backward-compatibility + */ + constructor(name, backCompat = true) { + /** @type {ChunkId | null} */ + this.id = null; + /** @type {ChunkId[] | null} */ + this.ids = null; + /** @type {number} */ + this.debugId = debugId++; + /** @type {string | undefined} */ + this.name = name; + /** @type {SortableSet} */ + this.idNameHints = new SortableSet(); + /** @type {boolean} */ + this.preventIntegration = false; + /** @type {TemplatePath | undefined} */ + this.filenameTemplate = undefined; + /** @type {TemplatePath | undefined} */ + this.cssFilenameTemplate = undefined; + /** + * @private + * @type {SortableSet} + */ + 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 {Record} */ + this.contentHash = Object.create(null); + /** @type {string=} */ + this.renderedHash = undefined; + /** @type {string=} */ + this.chunkReason = undefined; + /** @type {boolean} */ + this.extraAsync = false; + } + + // TODO remove in webpack 6 + // BACKWARD-COMPAT START + get entryModule() { + const entryModules = Array.from( + 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)" + ); + } + + /** + * @returns {boolean} true, if the chunk contains an entry module + */ + hasEntryModule() { + return ( + ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.hasEntryModule", + "DEP_WEBPACK_CHUNK_HAS_ENTRY_MODULE" + ).getNumberOfEntryModules(this) > 0 + ); + } + + /** + * @param {Module} module the module + * @returns {boolean} true, if the chunk could be added + */ + 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; + } + + /** + * @param {Module} module the module + * @returns {void} + */ + removeModule(module) { + ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.removeModule", + "DEP_WEBPACK_CHUNK_REMOVE_MODULE" + ).disconnectChunkAndModule(this, module); + } + + /** + * @returns {number} the number of module which are contained in this chunk + */ + getNumberOfModules() { + return ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.getNumberOfModules", + "DEP_WEBPACK_CHUNK_GET_NUMBER_OF_MODULES" + ).getNumberOfChunkModules(this); + } + + get modulesIterable() { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.modulesIterable", + "DEP_WEBPACK_CHUNK_MODULES_ITERABLE" + ); + return chunkGraph.getOrderedChunkModulesIterable( + this, + compareModulesByIdentifier + ); + } + + /** + * @param {Chunk} otherChunk the chunk to compare with + * @returns {-1|0|1} the comparison result + */ + compareTo(otherChunk) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.compareTo", + "DEP_WEBPACK_CHUNK_COMPARE_TO" + ); + return chunkGraph.compareChunks(this, otherChunk); + } + + /** + * @param {Module} module the module + * @returns {boolean} true, if the chunk contains the module + */ + containsModule(module) { + return ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.containsModule", + "DEP_WEBPACK_CHUNK_CONTAINS_MODULE" + ).isModuleInChunk(module, this); + } + + /** + * @returns {Module[]} the modules for this chunk + */ + getModules() { + return ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.getModules", + "DEP_WEBPACK_CHUNK_GET_MODULES" + ).getChunkModules(this); + } + + /** + * @returns {void} + */ + remove() { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.remove", + "DEP_WEBPACK_CHUNK_REMOVE" + ); + chunkGraph.disconnectChunk(this); + this.disconnectFromGroups(); + } + + /** + * @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); + } + + /** + * @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 {Chunk} otherChunk the other chunk + * @returns {boolean} true, if chunks could be integrated + */ + canBeIntegrated(otherChunk) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.canBeIntegrated", + "DEP_WEBPACK_CHUNK_CAN_BE_INTEGRATED" + ); + return chunkGraph.canChunksBeIntegrated(this, otherChunk); + } + + /** + * @returns {boolean} true, if this chunk contains no module + */ + isEmpty() { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.isEmpty", + "DEP_WEBPACK_CHUNK_IS_EMPTY" + ); + return chunkGraph.getNumberOfChunkModules(this) === 0; + } + + /** + * @returns {number} total size of all modules in this chunk + */ + modulesSize() { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.modulesSize", + "DEP_WEBPACK_CHUNK_MODULES_SIZE" + ); + return chunkGraph.getChunkModulesSize(this); + } + + /** + * @param {ChunkSizeOptions} options options object + * @returns {number} total size of this chunk + */ + size(options = {}) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.size", + "DEP_WEBPACK_CHUNK_SIZE" + ); + return chunkGraph.getChunkSize(this, options); + } + + /** + * @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 + */ + integratedSize(otherChunk, options) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.integratedSize", + "DEP_WEBPACK_CHUNK_INTEGRATED_SIZE" + ); + return chunkGraph.getIntegratedChunksSize(this, otherChunk, options); + } + + /** + * @param {ModuleFilterPredicate} filterFn function used to filter modules + * @returns {ChunkModuleMaps} module map information + */ + getChunkModuleMaps(filterFn) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.getChunkModuleMaps", + "DEP_WEBPACK_CHUNK_GET_CHUNK_MODULE_MAPS" + ); + /** @type {Record} */ + const chunkModuleIdMap = Object.create(null); + /** @type {Record} */ + 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 + }; + } + + /** + * @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(filterFn, filterChunkFn) { + const chunkGraph = ChunkGraph.getChunkGraphForChunk( + this, + "Chunk.hasModuleInGraph", + "DEP_WEBPACK_CHUNK_HAS_MODULE_IN_GRAPH" + ); + return chunkGraph.hasModuleInGraph(this, filterFn, filterChunkFn); + } + + /** + * @deprecated + * @param {boolean} realHash whether the full hash or the rendered hash is to be used + * @returns {ChunkMaps} the chunk map information + */ + 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 + + /** + * @returns {boolean} whether or not the Chunk will have a runtime + */ + hasRuntime() { + for (const chunkGroup of this._groups) { + if ( + chunkGroup instanceof Entrypoint && + chunkGroup.getRuntimeChunk() === this + ) { + return true; + } + } + return false; + } + + /** + * @returns {boolean} whether or not this chunk can be an initial chunk + */ + canBeInitial() { + for (const chunkGroup of this._groups) { + if (chunkGroup.isInitial()) return true; + } + return false; + } + + /** + * @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; + } + + /** + * @returns {EntryOptions | undefined} the entry options for this chunk + */ + getEntryOptions() { + for (const chunkGroup of this._groups) { + if (chunkGroup instanceof Entrypoint) { + return chunkGroup.options; + } + } + return undefined; + } + + /** + * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being added + * @returns {void} + */ + addGroup(chunkGroup) { + this._groups.add(chunkGroup); + } + + /** + * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being removed from + * @returns {void} + */ + removeGroup(chunkGroup) { + this._groups.delete(chunkGroup); + } + + /** + * @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); + } + + /** + * @returns {number} the amount of groups that the said chunk is in + */ + getNumberOfGroups() { + return this._groups.size; + } + + /** + * @returns {SortableSet} the chunkGroups that the said chunk is referenced in + */ + get groupsIterable() { + this._groups.sort(); + return this._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 + * @returns {void} + */ + split(newChunk) { + for (const chunkGroup of this._groups) { + chunkGroup.insertChunk(newChunk, this); + newChunk.addGroup(chunkGroup); + } + for (const idHint of this.idNameHints) { + newChunk.idNameHints.add(idHint); + } + newChunk.runtime = mergeRuntime(newChunk.runtime, this.runtime); + } + + /** + * @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 + }` + ); + } + } + + /** + * @returns {Set} a set of all the async chunks + */ + getAllAsyncChunks() { + const queue = new Set(); + const chunks = new Set(); + + const initialChunks = intersect( + Array.from(this.groupsIterable, g => new Set(g.chunks)) + ); + + 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); + } + } + } + + 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 chunks; + } + + /** + * @returns {Set} a set of all the initial chunks (including itself) + */ + getAllInitialChunks() { + const chunks = new Set(); + 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 {Set} a set of all the referenced chunks (including itself) + */ + getAllReferencedChunks() { + const queue = new Set(this.groupsIterable); + 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; + } + + /** + * @returns {Set} a set of all the referenced entrypoints + */ + getAllReferencedAsyncEntrypoints() { + const queue = new Set(this.groupsIterable); + const entrypoints = new Set(); + + for (const chunkGroup of queue) { + for (const entrypoint of chunkGroup.asyncEntrypointsIterable) { + entrypoints.add(entrypoint); + } + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + + return entrypoints; + } + + /** + * @returns {boolean} true, if the chunk references async chunks + */ + hasAsyncChunks() { + const queue = new Set(); + + const initialChunks = intersect( + Array.from(this.groupsIterable, g => new Set(g.chunks)) + ); + + for (const chunkGroup of this.groupsIterable) { + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + + for (const chunkGroup of queue) { + for (const chunk of chunkGroup.chunks) { + if (!initialChunks.has(chunk)) { + return true; + } + } + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + + return false; + } + + /** + * @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) { + for (const key of Object.keys(childGroup.options)) { + if (key.endsWith("Order")) { + 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: + /** @type {number} */ + ( + childGroup.options[ + /** @type {keyof ChunkGroupOptions} */ (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; + return a.group.compareTo(chunkGraph, b.group); + }); + /** @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] = Array.from(chunkIdSet); + } + } + return result; + } + + /** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {string} type option name + * @returns {{ onChunks: Chunk[], chunks: Set }[] | undefined} referenced chunks for a specific type + */ + getChildrenOfTypeInOrder(chunkGraph, type) { + const list = []; + for (const group of this.groupsIterable) { + for (const childGroup of group.childrenIterable) { + const order = + childGroup.options[/** @type {keyof ChunkGroupOptions} */ (type)]; + if (order === undefined) continue; + list.push({ + order, + group, + childGroup + }); + } + } + if (list.length === 0) return; + list.sort((a, b) => { + const cmp = + /** @type {number} */ (b.order) - /** @type {number} */ (a.order); + if (cmp !== 0) return cmp; + return a.group.compareTo(chunkGraph, b.group); + }); + const result = []; + 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; + } + + /** + * @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 {Record>} a record object of names to lists of child ids(?) by chunk id + */ + getChildIdsByOrdersMap(chunkGraph, includeDirectChildren, filterFn) { + /** @type {Record>} */ + const chunkMaps = Object.create(null); + + /** + * @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[/** @type {ChunkId} */ (chunk.id)] = data[key]; + } + }; + + if (includeDirectChildren) { + /** @type {Set} */ + 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()) { + addChildIdsByOrdersToMap(chunk); + } + + return chunkMaps; + } + + /** + * @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 + */ + hasChildByOrder(chunkGraph, type, includeDirectChildren, filterFn) { + if (includeDirectChildren) { + /** @type {Set} */ + const chunks = new Set(); + for (const chunkGroup of this.groupsIterable) { + for (const chunk of chunkGroup.chunks) { + chunks.add(chunk); + } + } + for (const chunk of chunks) { + const data = chunk.getChildIdsByOrders(chunkGraph, filterFn); + if (data[type] !== undefined) return true; + } + } + + for (const chunk of this.getAllAsyncChunks()) { + const data = chunk.getChildIdsByOrders(chunkGraph, filterFn); + if (data[type] !== undefined) return true; + } + + return false; + } +} + +module.exports = Chunk; diff --git a/webpack-lib/lib/ChunkGraph.js b/webpack-lib/lib/ChunkGraph.js new file mode 100644 index 00000000000..d13e8afe5c9 --- /dev/null +++ b/webpack-lib/lib/ChunkGraph.js @@ -0,0 +1,1868 @@ +/* + 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 { first } = require("./util/SetHelpers"); +const SortableSet = require("./util/SortableSet"); +const { + compareModulesById, + compareIterables, + compareModulesByIdentifier, + concatComparators, + compareSelect, + compareIds +} = require("./util/comparators"); +const createHash = require("./util/createHash"); +const findGraphRoots = require("./util/findGraphRoots"); +const { + RuntimeSpecMap, + RuntimeSpecSet, + runtimeToString, + mergeRuntime, + forEachRuntime +} = require("./util/runtime"); + +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Chunk").ChunkId} ChunkId */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("./RuntimeModule")} RuntimeModule */ +/** @typedef {typeof import("./util/Hash")} Hash */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** @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 */ + +/** + * @typedef {object} ChunkSizeOptions + * @property {number=} chunkOverhead constant overhead for a chunk + * @property {number=} entryChunkMultiplicator multiplicator for initial chunks + */ + +class ModuleHashInfo { + /** + * @param {string} hash hash + * @param {string} renderedHash rendered hash + */ + constructor(hash, renderedHash) { + this.hash = hash; + this.renderedHash = renderedHash; + } +} + +/** @template T @typedef {(set: SortableSet) => T[]} SetToArrayFunction */ + +/** + * @template T + * @param {SortableSet} set the set + * @returns {T[]} set as array + */ +const getArray = set => Array.from(set); + +/** + * @param {SortableSet} chunks the chunks + * @returns {RuntimeSpecSet} runtimes + */ +const getModuleRuntimes = chunks => { + const runtimes = new RuntimeSpecSet(); + for (const chunk of chunks) { + runtimes.add(chunk.runtime); + } + return runtimes; +}; + +/** + * @param {WeakMap> | undefined} sourceTypesByModule sourceTypesByModule + * @returns {function (SortableSet): Map>} modules by source type + */ +const modulesBySourceType = sourceTypesByModule => set => { + /** @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) { + 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; +}; +const defaultModulesBySourceType = modulesBySourceType(undefined); + +/** + * @template T + * @type {WeakMap} + */ +const createOrderedArrayFunctionMap = new WeakMap(); + +/** + * @template T + * @param {function(T, T): -1|0|1} comparator comparator function + * @returns {SetToArrayFunction} set as ordered array + */ +const createOrderedArrayFunction = comparator => { + /** @type {SetToArrayFunction} */ + let fn = createOrderedArrayFunctionMap.get(comparator); + if (fn !== undefined) return fn; + fn = set => { + set.sortWith(comparator); + return Array.from(set); + }; + createOrderedArrayFunctionMap.set(comparator, fn); + return fn; +}; + +/** + * @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; +}; + +/** + * @param {Iterable} modules the sortable Set to get the size of + * @returns {Record} the sizes of the modules + */ +const getModulesSizes = modules => { + 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; +}; + +/** + * @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 {Set} EntryInChunks */ +/** @typedef {Set} RuntimeInChunks */ +/** @typedef {string | number} ModuleId */ + +class ChunkGraphModule { + constructor() { + /** @type {SortableSet} */ + 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 {RuntimeSpecMap> | undefined} */ + this.runtimeRequirements = undefined; + /** @type {RuntimeSpecMap | undefined} */ + this.graphHashes = undefined; + /** @type {RuntimeSpecMap | undefined} */ + this.graphHashesWithConnections = undefined; + } +} + +class ChunkGraphChunk { + constructor() { + /** @type {SortableSet} */ + this.modules = new SortableSet(); + /** @type {WeakMap> | undefined} */ + this.sourceTypesByModule = undefined; + /** @type {Map} */ + this.entryModules = new Map(); + /** @type {SortableSet} */ + this.runtimeModules = new SortableSet(); + /** @type {Set | undefined} */ + this.fullHashModules = undefined; + /** @type {Set | undefined} */ + this.dependentHashModules = undefined; + /** @type {Set | undefined} */ + this.runtimeRequirements = undefined; + /** @type {Set} */ + this.runtimeRequirementsInTree = new Set(); + + this._modulesBySourceType = defaultModulesBySourceType; + } +} + +class ChunkGraph { + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {string | Hash} hashFunction the hash function to use + */ + constructor(moduleGraph, hashFunction = "md4") { + /** + * @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(); + /** @type {ModuleGraph} */ + this.moduleGraph = moduleGraph; + + this._hashFunction = hashFunction; + + this._getGraphRoots = this._getGraphRoots.bind(this); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {SortableSet} set the sortable Set to get the roots of + * @returns {Module[]} the graph roots + */ + _getGraphRoots(set) { + const { moduleGraph } = this; + return Array.from( + findGraphRoots(set, module => { + /** @type {Set} */ + const set = new Set(); + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + } + + /** + * @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); + } + } + + /** + * @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); + } + } + + /** + * @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); + } + } + + /** + * @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 {Map} */ + 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; + } + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {Iterable} iterable of chunks (do not modify) + */ + getModuleChunksIterable(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.chunks; + } + + /** + * @param {Module} module the module + * @param {function(Chunk, 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; + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {RuntimeSpecSet} runtimes + */ + getModuleRuntimes(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.chunks.getFromUnorderedCache(getModuleRuntimes); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} return the modules for this chunk + */ + getChunkModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.modules; + } + + /** + * @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; + } + + /** + * @param {Chunk} chunk chunk + * @param {Module} module chunk module + * @param {Set} 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); + } + + /** + * @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(); + } + + /** + * @param {Module} module module + * @returns {SourceTypes} source types + */ + getModuleSourceTypes(module) { + return ( + this._getOverwrittenModuleSourceTypes(module) || module.getSourceTypes() + ); + } + + /** + * @param {Module} module module + * @returns {Set | undefined} source types + */ + _getOverwrittenModuleSourceTypes(module) { + let newSet = false; + 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 = st; + continue; + } 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; + } + + /** + * @param {Chunk} chunk the chunk + * @param {function(Module, Module): -1|0|1} 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; + } + + /** + * @param {Chunk} chunk the chunk + * @param {string} sourceType source type + * @param {function(Module, Module): -1|0|1} 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; + } + + /** + * @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); + } + + /** + * @param {Chunk} chunk the chunk + * @param {function(Module, Module): -1|0|1} 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); + } + + /** + * @param {Chunk} chunk the chunk + * @param {ModuleFilterPredicate} filterFn function used to filter modules + * @param {boolean} includeAllChunks all chunks or only async chunks + * @returns {Record} chunk to module ids object + */ + getChunkModuleIdMap(chunk, filterFn, includeAllChunks = false) { + /** @type {Record} */ + const chunkModuleIdMap = Object.create(null); + + for (const asyncChunk of includeAllChunks + ? chunk.getAllReferencedChunks() + : chunk.getAllAsyncChunks()) { + /** @type {(string | number)[] | 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; + } + + /** + * @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 {Record>} chunk to module id to module hash object + */ + getChunkModuleRenderedHashMap( + chunk, + filterFn, + hashLength = 0, + includeAllChunks = false + ) { + /** @type {Record>} */ + const chunkModuleHashMap = Object.create(null); + + /** @typedef {Record} IdToHashMap */ + + 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) { + 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; + } + + /** + * @param {Chunk} chunk the chunk + * @param {ChunkFilterPredicate} filterFn function used to filter chunks + * @returns {Record} chunk map + */ + getChunkConditionMap(chunk, filterFn) { + const map = Object.create(null); + for (const c of chunk.getAllReferencedChunks()) { + map[/** @type {ChunkId} */ (c.id)] = filterFn(c, this); + } + return map; + } + + /** + * @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); + 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; + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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) + ); + } + + /** + * @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) + ); + } + + /** + * @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; + } + + /** + * @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 Array.from( + 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); + } + + /** + * @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; + } + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @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(); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} iterable of modules (do not modify) + */ + getChunkEntryModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.entryModules.keys(); + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} iterable of chunks + */ + getChunkEntryDependentChunksIterable(chunk) { + /** @type {Set} */ + 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; + } + + /** + * @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; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} iterable of modules (do not modify) + */ + getChunkRuntimeModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.runtimeModules; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {RuntimeModule[]} array of modules in order of execution + */ + getChunkRuntimeModulesInOrder(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + const array = Array.from(cgc.runtimeModules); + array.sort( + concatComparators( + compareSelect(r => /** @type {RuntimeModule} */ (r).stage, compareIds), + compareModulesByIdentifier + ) + ); + return array; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable | undefined} iterable of modules (do not modify) + */ + getChunkFullHashModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.fullHashModules; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {ReadonlySet | undefined} set of modules (do not modify) + */ + getChunkFullHashModulesSet(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.fullHashModules; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable | undefined} iterable of modules (do not modify) + */ + getChunkDependentHashModulesIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.dependentHashModules; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {Iterable} iterable of modules (do not modify) + */ + getChunkEntryModulesWithChunkGroupIterable(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.entryModules; + } + + /** + * @param {AsyncDependenciesBlock} depBlock the async block + * @returns {ChunkGroup | undefined} the chunk group + */ + getBlockChunkGroup(depBlock) { + return this._blockChunkGroups.get(depBlock); + } + + /** + * @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); + } + + /** + * @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(); + } + + /** + * @param {Module} module the module + * @returns {ModuleId | null} the id of the module + */ + getModuleId(module) { + const cgm = this._getChunkGraphModule(module); + return cgm.id; + } + + /** + * @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; + } + + /** + * @param {string} runtime runtime + * @returns {string | number} the id of the runtime + */ + getRuntimeId(runtime) { + return /** @type {string | number} */ (this._runtimeIds.get(runtime)); + } + + /** + * @param {string} runtime runtime + * @param {string | number} id the id of the runtime + * @returns {void} + */ + setRuntimeId(runtime, id) { + this._runtimeIds.set(runtime, id); + } + + /** + * @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; + } + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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)); + } + + /** + * @param {Module} module the module + * @param {RuntimeSpec} runtime the runtime + * @param {Set} 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 (runtimeRequirementsMap === undefined) { + const map = new RuntimeSpecMap(); + // TODO avoid cloning item and track ownership instead + map.set(runtime, transferOwnership ? items : new Set(items)); + cgm.runtimeRequirements = map; + return; + } + runtimeRequirementsMap.update(runtime, runtimeRequirements => { + if (runtimeRequirements === undefined) { + return transferOwnership ? items : new Set(items); + } else if (!transferOwnership || runtimeRequirements.size >= items.size) { + for (const item of items) runtimeRequirements.add(item); + return runtimeRequirements; + } + + for (const item of runtimeRequirements) items.add(item); + return items; + }); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Set} 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; + } + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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${/** @type {string} */ (hash.digest("hex"))}`); + }); + return graphHash; + } + + /** + * @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(); + } + + /** + * @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 && 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(); + /** + * @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) { + 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(state); + } + processConnection(connection, stateInfo); + } + } + // cspell:word Tnamespace + if (activeNamespaceModules.size === 0 && connectedModules.size === 0) + return graphHash; + const connectedModulesInOrder = + connectedModules.size > 1 + ? Array.from(connectedModules).sort(([a], [b]) => (a < b ? -1 : 1)) + : connectedModules; + const hash = createHash(this._hashFunction); + /** + * @param {Module} module module + */ + const addModuleToHash = module => { + hash.update( + this._getModuleGraphHashBigInt( + this._getChunkGraphModule(module), + module, + runtime + ).toString(16) + ); + }; + /** + * @param {Set} modules modules + */ + const addModulesToHash = modules => { + let xor = ZERO_BIG_INT; + for (const m of modules) { + xor = + 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 /** @type {string} */ (hash.digest("hex")); + }); + } + + /** + * @param {Chunk} chunk the chunk + * @returns {ReadOnlyRuntimeRequirements} runtime requirements + */ + getTreeRuntimeRequirements(chunk) { + const cgc = this._getChunkGraphChunk(chunk); + return cgc.runtimeRequirementsInTree; + } + + // TODO remove in webpack 6 + /** + * @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( + /** + * @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 + /** + * @param {Module} module the module + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {void} + */ + static setChunkGraphForModule(module, chunkGraph) { + chunkGraphForModuleMap.set(module, chunkGraph); + } + + // TODO remove in webpack 6 + /** + * @param {Module} module the module + * @returns {void} + */ + static clearChunkGraphForModule(module) { + chunkGraphForModuleMap.delete(module); + } + + // TODO remove in webpack 6 + /** + * @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( + /** + * @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); + } + + // TODO remove in webpack 6 + /** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {void} + */ + static setChunkGraphForChunk(chunk, chunkGraph) { + chunkGraphForChunkMap.set(chunk, chunkGraph); + } + + // TODO remove in webpack 6 + /** + * @param {Chunk} chunk the chunk + * @returns {void} + */ + static clearChunkGraphForChunk(chunk) { + chunkGraphForChunkMap.delete(chunk); + } +} + +// 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/webpack-lib/lib/ChunkGroup.js b/webpack-lib/lib/ChunkGroup.js new file mode 100644 index 00000000000..2fcb71d1d9b --- /dev/null +++ b/webpack-lib/lib/ChunkGroup.js @@ -0,0 +1,604 @@ +/* + 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, + compareChunks, + compareIterables +} = 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("./ModuleGraph")} ModuleGraph */ + +/** @typedef {{id: number}} HasId */ +/** @typedef {{module: Module | null, loc: DependencyLocation, request: string}} OriginRecord */ + +/** + * @typedef {object} RawChunkGroupOptions + * @property {number=} preloadOrder + * @property {number=} prefetchOrder + * @property {("low" | "high" | "auto")=} fetchPriority + */ + +/** @typedef {RawChunkGroupOptions & { name?: string | null }} ChunkGroupOptions */ + +let debugId = 5000; + +/** + * @template T + * @param {SortableSet} set set to convert to array. + * @returns {T[]} the array format of existing set + */ +const getArray = set => Array.from(set); + +/** + * A convenience method used to sort chunks based on their id's + * @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; + if (b.id < a.id) return 1; + return 0; +}; + +/** + * @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 + */ +const sortOrigin = (a, b) => { + const aIdent = a.module ? a.module.identifier() : ""; + const bIdent = b.module ? b.module.identifier() : ""; + if (aIdent < bIdent) return -1; + if (aIdent > bIdent) return 1; + return compareLocations(a.loc, b.loc); +}; + +class ChunkGroup { + /** + * Creates an instance of ChunkGroup. + * @param {string | ChunkGroupOptions=} options chunk group options passed to chunkGroup + */ + constructor(options) { + if (typeof options === "string") { + options = { name: options }; + } else if (!options) { + options = { name: undefined }; + } + /** @type {number} */ + this.groupDebugId = debugId++; + this.options = /** @type {ChunkGroupOptions} */ (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); + this._blocks = new SortableSet(); + /** @type {Chunk[]} */ + this.chunks = []; + /** @type {OriginRecord[]} */ + this.origins = []; + /** Indices in top-down order */ + /** + * @private + * @type {Map} + */ + this._modulePreOrderIndices = new Map(); + /** Indices in bottom-up order */ + /** + * @private + * @type {Map} + */ + this._modulePostOrderIndices = new Map(); + /** @type {number | undefined} */ + this.index = undefined; + } + + /** + * when a new chunk is added to a chunkGroup, addingOptions will occur. + * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions + * @returns {void} + */ + addOptions(options) { + for (const _key of Object.keys(options)) { + const key = /** @type {keyof ChunkGroupOptions} */ (_key); + if (this.options[key] === undefined) { + /** @type {TODO} */ + (this.options)[key] = options[key]; + } else if (this.options[key] !== options[key]) { + if (key.endsWith("Order")) { + /** @type {TODO} */ + (this.options)[key] = Math.max( + /** @type {number} */ (this.options[key]), + /** @type {number} */ (options[key]) + ); + } else { + throw new Error( + `ChunkGroup.addOptions: No option merge strategy for ${key}` + ); + } + } + } + } + + /** + * returns the name of current ChunkGroup + * @returns {string | null | undefined} returns the ChunkGroup name + */ + get name() { + return this.options.name; + } + + /** + * sets a new name for current ChunkGroup + * @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 {string} a unique concatenation of chunk debugId's + */ + get debugId() { + return Array.from(this.chunks, x => x.debugId).join("+"); + } + + /** + * get a unique id for ChunkGroup, made up of its member Chunk id's + * @returns {string} a unique concatenation of chunk ids + */ + get id() { + return Array.from(this.chunks, x => x.id).join("+"); + } + + /** + * Performs an unshift of a specific chunk + * @param {Chunk} chunk chunk being unshifted + * @returns {boolean} returns true if attempted chunk shift is accepted + */ + unshiftChunk(chunk) { + const oldIdx = this.chunks.indexOf(chunk); + if (oldIdx > 0) { + this.chunks.splice(oldIdx, 1); + this.chunks.unshift(chunk); + } else if (oldIdx < 0) { + this.chunks.unshift(chunk); + return true; + } + return false; + } + + /** + * inserts a chunk before another existing chunk in group + * @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 + */ + insertChunk(chunk, before) { + const oldIdx = this.chunks.indexOf(chunk); + const idx = this.chunks.indexOf(before); + if (idx < 0) { + throw new Error("before chunk not found"); + } + if (oldIdx >= 0 && oldIdx > idx) { + this.chunks.splice(oldIdx, 1); + this.chunks.splice(idx, 0, chunk); + } else if (oldIdx < 0) { + this.chunks.splice(idx, 0, chunk); + return true; + } + return false; + } + + /** + * add a chunk into ChunkGroup. Is pushed on or prepended + * @param {Chunk} chunk chunk being pushed into ChunkGroupS + * @returns {boolean} returns true if chunk addition was successful. + */ + pushChunk(chunk) { + const oldIdx = this.chunks.indexOf(chunk); + if (oldIdx >= 0) { + return false; + } + this.chunks.push(chunk); + return true; + } + + /** + * @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) { + const oldIdx = this.chunks.indexOf(oldChunk); + if (oldIdx < 0) return false; + const newIdx = this.chunks.indexOf(newChunk); + if (newIdx < 0) { + this.chunks[oldIdx] = newChunk; + return true; + } + if (newIdx < oldIdx) { + this.chunks.splice(oldIdx, 1); + return true; + } else if (newIdx !== oldIdx) { + this.chunks[oldIdx] = newChunk; + this.chunks.splice(newIdx, 1); + return true; + } + } + + /** + * @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) { + this.chunks.splice(idx, 1); + return true; + } + return false; + } + + /** + * @returns {boolean} true, when this chunk group will be loaded on initial page load + */ + isInitial() { + return false; + } + + /** + * @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 {ChunkGroup[]} returns the children of this group + */ + getChildren() { + return this._children.getFromCache(getArray); + } + + getNumberOfChildren() { + return this._children.size; + } + + get childrenIterable() { + return this._children; + } + + /** + * @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(group); + group.removeParent(this); + return true; + } + + /** + * @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); + return true; + } + return false; + } + + /** + * @returns {ChunkGroup[]} returns the parents of this group + */ + getParents() { + return this._parents.getFromCache(getArray); + } + + getNumberOfParents() { + return this._parents.size; + } + + /** + * @param {ChunkGroup} parent the parent group + * @returns {boolean} returns true if the parent group contains this group + */ + hasParent(parent) { + return this._parents.has(parent); + } + + get parentsIterable() { + return this._parents; + } + + /** + * @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; + } + + /** + * @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 {Array} an array containing the blocks + */ + getBlocks() { + return this._blocks.getFromCache(getArray); + } + + getNumberOfBlocks() { + return this._blocks.size; + } + + /** + * @param {AsyncDependenciesBlock} block block + * @returns {boolean} true, if block exists + */ + hasBlock(block) { + return this._blocks.has(block); + } + + /** + * @returns {Iterable} blocks + */ + get blocksIterable() { + return this._blocks; + } + + /** + * @param {AsyncDependenciesBlock} block a block + * @returns {boolean} false, if block was already added + */ + addBlock(block) { + if (!this._blocks.has(block)) { + this._blocks.add(block); + return true; + } + return false; + } + + /** + * @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, + loc, + request + }); + } + + /** + * @returns {string[]} the files contained this chunk group + */ + getFiles() { + const files = new Set(); + + for (const chunk of this.chunks) { + for (const file of chunk.files) { + files.add(file); + } + } + + return Array.from(files); + } + + /** + * @returns {void} + */ + remove() { + // cleanup parents + for (const parentChunkGroup of this._parents) { + // remove this chunk from its parents + parentChunkGroup._children.delete(this); + + // cleanup "sub chunks" + for (const chunkGroup of this._children) { + /** + * remove this chunk as "intermediary" and connect + * it "sub chunks" and parents directly + */ + // add parent to each "sub chunk" + chunkGroup.addParent(parentChunkGroup); + // add "sub chunk" to parent + parentChunkGroup.addChild(chunkGroup); + } + } + + /** + * we need to iterate again over the children + * 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. + */ + for (const chunkGroup of this._children) { + // remove this as parent of every "sub chunk" + chunkGroup._parents.delete(this); + } + + // remove chunks + for (const chunk of this.chunks) { + chunk.removeGroup(this); + } + } + + sortItems() { + this.origins.sort(sortOrigin); + } + + /** + * 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 + */ + compareTo(chunkGraph, otherGroup) { + if (this.chunks.length > otherGroup.chunks.length) return -1; + if (this.chunks.length < otherGroup.chunks.length) return 1; + return compareIterables(compareChunks(chunkGraph))( + this.chunks, + otherGroup.chunks + ); + } + + /** + * @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) { + for (const key of Object.keys(childGroup.options)) { + if (key.endsWith("Order")) { + const name = key.slice(0, key.length - "Order".length); + let list = lists.get(name); + if (list === undefined) { + lists.set(name, (list = [])); + } + list.push({ + order: + /** @type {number} */ + ( + childGroup.options[/** @type {keyof ChunkGroupOptions} */ (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; + return a.group.compareTo(chunkGraph, b.group); + }); + result[name] = list.map(i => i.group); + } + return result; + } + + /** + * Sets the top-down index of a module in this ChunkGroup + * @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); + } + + /** + * Gets the top-down index of a module in this ChunkGroup + * @param {Module} module the module + * @returns {number | undefined} index + */ + getModulePreOrderIndex(module) { + return this._modulePreOrderIndices.get(module); + } + + /** + * Sets the bottom-up index of a module in this ChunkGroup + * @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); + } + + /** + * Gets the bottom-up index of a module in this ChunkGroup + * @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}` + ); + } + } + for (const parentChunk of chunk._parents) { + if (!parentChunk._children.has(chunk)) { + throw new Error( + `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/webpack-lib/lib/ChunkRenderError.js b/webpack-lib/lib/ChunkRenderError.js new file mode 100644 index 00000000000..fce913f171a --- /dev/null +++ b/webpack-lib/lib/ChunkRenderError.js @@ -0,0 +1,31 @@ +/* + 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; + } +} + +module.exports = ChunkRenderError; diff --git a/webpack-lib/lib/ChunkTemplate.js b/webpack-lib/lib/ChunkTemplate.js new file mode 100644 index 00000000000..238144a30ac --- /dev/null +++ b/webpack-lib/lib/ChunkTemplate.js @@ -0,0 +1,181 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const memoize = require("./util/memoize"); + +/** @typedef {import("tapable").Tap} Tap */ +/** @typedef {import("../declarations/WebpackOptions").Output} OutputOptions */ +/** @typedef {import("./Chunk")} Chunk */ +/** @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 */ +/** + * @template T + * @typedef {import("tapable").IfSet} IfSet + */ + +const getJavascriptModulesPlugin = memoize(() => + require("./javascript/JavascriptModulesPlugin") +); + +// TODO webpack 6 remove this class +class ChunkTemplate { + /** + * @param {OutputOptions} outputOptions output options + * @param {Compilation} compilation the compilation + */ + constructor(outputOptions, compilation) { + this._outputOptions = outputOptions || {}; + this.hooks = Object.freeze({ + renderManifest: { + tap: util.deprecate( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(RenderManifestEntry[], 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, ModuleTemplate, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, ModuleTemplate, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Hash, Chunk, 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" + ) + } + }); + } +} + +Object.defineProperty(ChunkTemplate.prototype, "outputOptions", { + get: util.deprecate( + /** + * @this {ChunkTemplate} + * @returns {OutputOptions} output options + */ + function () { + return this._outputOptions; + }, + "ChunkTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)", + "DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS" + ) +}); + +module.exports = ChunkTemplate; diff --git a/webpack-lib/lib/CleanPlugin.js b/webpack-lib/lib/CleanPlugin.js new file mode 100644 index 00000000000..2e8fe9bac65 --- /dev/null +++ b/webpack-lib/lib/CleanPlugin.js @@ -0,0 +1,443 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const { SyncBailHook } = require("tapable"); +const Compilation = require("./Compilation"); +const createSchemaValidation = require("./util/create-schema-validation"); +const { join } = require("./util/fs"); +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 {(function(string):boolean)|RegExp} IgnoreItem */ +/** @typedef {Map} Assets */ +/** @typedef {function(IgnoreItem): void} AddToIgnoreCallback */ + +/** + * @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 + */ + +/** + * @callback KeepFn + * @param {string} path path + * @returns {boolean | void} true, if the path should be kept + */ + +const validate = createSchemaValidation( + undefined, + () => { + const { definitions } = require("../schemas/WebpackOptions.json"); + return { + definitions, + oneOf: [{ $ref: "#/definitions/CleanOptions" }] + }; + }, + { + name: "Clean Plugin", + baseDataPath: "options" + } +); +const _10sec = 10 * 1000; + +/** + * marge 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); + } +}; + +/** + * @param {OutputFileSystem} fs filesystem + * @param {string} outputPath output path + * @param {Map} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator) + * @param {function((Error | null)=, Set=): void} callback returns the filenames of the assets that shouldn't be there + * @returns {void} + */ +const getDiffToFs = (fs, outputPath, currentAssets, callback) => { + const directories = new Set(); + // get directories of assets + for (const [asset] of currentAssets) { + directories.add(asset.replace(/(^|\/)[^/]*$/, "")); + } + // and all parent directories + for (const directory of directories) { + directories.add(directory.replace(/(^|\/)[^/]*$/, "")); + } + 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; + const filename = directory ? `${directory}/${file}` : file; + if (!directories.has(filename) && !currentAssets.has(filename)) { + diff.add(filename); + } + } + callback(); + }); + }, + err => { + if (err) return callback(err); + + callback(null, diff); + } + ); +}; + +/** + * @param {Assets} currentAssets assets list + * @param {Assets} oldAssets old assets list + * @returns {Set} diff + */ +const getDiffToOldAssets = (currentAssets, oldAssets) => { + 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; +}; + +/** + * @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); + } +}; + +/** + * @param {OutputFileSystem} fs filesystem + * @param {string} outputPath output path + * @param {boolean} dry only log instead of fs modification + * @param {Logger} logger logger + * @param {Set} diff filenames of the assets that shouldn't be there + * @param {function(string): boolean | void} isKept check if the entry is ignored + * @param {function(Error=, Assets=): void} callback callback + * @returns {void} + */ +const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => { + /** + * @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) => { + /** + * @param {Error & { code?: string }} err error + * @returns {void} + */ + const handleError = err => { + if (err.code === "ENOENT") { + 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); + }; + const path = join(fs, outputPath, filename); + 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(); + +class CleanPlugin { + /** + * @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 = {}) { + validate(options); + this.options = { dry: false, ...options }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { dry, keep } = this.options; + + /** @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: "CleanPlugin", + stage: 100 + }, + (compilation, callback) => { + const hooks = CleanPlugin.getCompilationHooks(compilation); + const logger = compilation.getLogger("webpack.CleanPlugin"); + const fs = /** @type {OutputFileSystem} */ (compiler.outputFileSystem); + + if (!fs.readdir) { + return callback( + new Error( + "CleanPlugin: 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 (/^[A-Za-z]:\\|^\/|^\\\\/.test(asset)) continue; + 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, {}); + + /** + * @param {string} path path + * @returns {boolean | void} true, if needs to be kept + */ + const isKept = path => { + const result = hooks.keep.call(path); + if (result !== undefined) return result; + return keepFn(path); + }; + + /** + * @param {(Error | null)=} err err + * @param {Set=} diff diff + */ + const diffCallback = (err, diff) => { + if (err) { + oldAssets = undefined; + callback(err); + return; + } + applyDiff( + fs, + outputPath, + dry, + logger, + /** @type {Set} */ (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; diff --git a/webpack-lib/lib/CodeGenerationError.js b/webpack-lib/lib/CodeGenerationError.js new file mode 100644 index 00000000000..b1cf51d744e --- /dev/null +++ b/webpack-lib/lib/CodeGenerationError.js @@ -0,0 +1,29 @@ +/* + 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 CodeGenerationError extends WebpackError { + /** + * Create a new CodeGenerationError + * @param {Module} module related module + * @param {Error} error Original error + */ + constructor(module, error) { + super(); + + this.name = "CodeGenerationError"; + this.error = error; + this.message = error.message; + this.details = error.stack; + this.module = module; + } +} + +module.exports = CodeGenerationError; diff --git a/webpack-lib/lib/CodeGenerationResults.js b/webpack-lib/lib/CodeGenerationResults.js new file mode 100644 index 00000000000..551d212599c --- /dev/null +++ b/webpack-lib/lib/CodeGenerationResults.js @@ -0,0 +1,157 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { getOrInsert } = require("./util/MapHelpers"); +const { first } = require("./util/SetHelpers"); +const createHash = require("./util/createHash"); +const { runtimeToString, RuntimeSpecMap } = require("./util/runtime"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {typeof import("./util/Hash")} Hash */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +class CodeGenerationResults { + /** + * @param {string | Hash} hashFunction the hash function to use + */ + constructor(hashFunction = "md4") { + /** @type {Map>} */ + this.map = new Map(); + this._hashFunction = hashFunction; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @param {RuntimeSpec} runtime runtime(s) + * @param {string} sourceType the source type + * @returns {Source} a source + */ + getSource(module, runtime, sourceType) { + return /** @type {Source} */ ( + this.get(module, runtime).sources.get(sourceType) + ); + } + + /** + * @param {Module} module the module + * @param {RuntimeSpec} runtime runtime(s) + * @returns {ReadOnlyRuntimeRequirements | null} runtime requirements + */ + getRuntimeRequirements(module, runtime) { + return this.get(module, runtime).runtimeRequirements; + } + + /** + * @param {Module} module the module + * @param {RuntimeSpec} runtime runtime(s) + * @param {string} key data key + * @returns {any} data generated by code generation + */ + getData(module, runtime, key) { + const data = this.get(module, runtime).data; + return data === undefined ? undefined : data.get(key); + } + + /** + * @param {Module} module the module + * @param {RuntimeSpec} runtime runtime(s) + * @returns {any} 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 = /** @type {string} */ (hash.digest("hex"))); + } + + /** + * @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, () => new RuntimeSpecMap()); + map.set(runtime, result); + } +} + +module.exports = CodeGenerationResults; diff --git a/webpack-lib/lib/CommentCompilationWarning.js b/webpack-lib/lib/CommentCompilationWarning.js new file mode 100644 index 00000000000..99cd0fbdada --- /dev/null +++ b/webpack-lib/lib/CommentCompilationWarning.js @@ -0,0 +1,32 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ + +class CommentCompilationWarning extends WebpackError { + /** + * @param {string} message warning message + * @param {DependencyLocation} loc affected lines of code + */ + constructor(message, loc) { + super(message); + + this.name = "CommentCompilationWarning"; + + this.loc = loc; + } +} + +makeSerializable( + CommentCompilationWarning, + "webpack/lib/CommentCompilationWarning" +); + +module.exports = CommentCompilationWarning; diff --git a/webpack-lib/lib/CompatibilityPlugin.js b/webpack-lib/lib/CompatibilityPlugin.js new file mode 100644 index 00000000000..46ddd7e802e --- /dev/null +++ b/webpack-lib/lib/CompatibilityPlugin.js @@ -0,0 +1,191 @@ +/* + 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"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ + +const nestedWebpackIdentifierTag = Symbol("nested webpack identifier"); +const PLUGIN_NAME = "CompatibilityPlugin"; + +class CompatibilityPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, (parser, parserOptions) => { + if ( + parserOptions.browserify !== undefined && + !parserOptions.browserify + ) + return; + + parser.hooks.call.for("require").tap( + PLUGIN_NAME, + /** + * @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", + /** @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 + ]; + 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; + } + ); + }); + + /** + * @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: statement.id.loc, + 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: pattern.loc, + range: pattern.range + } + }); + return true; + }); + parser.hooks.pattern + .for(RuntimeGlobals.exports) + .tap(PLUGIN_NAME, pattern => { + parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, { + name: "__nested_webpack_exports__", + declaration: { + updated: false, + loc: pattern.loc, + range: pattern.range + } + }); + return true; + }); + parser.hooks.expression + .for(nestedWebpackIdentifierTag) + .tap(PLUGIN_NAME, expr => { + const { name, declaration } = 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; diff --git a/webpack-lib/lib/Compilation.js b/webpack-lib/lib/Compilation.js new file mode 100644 index 00000000000..3dc2775f53d --- /dev/null +++ b/webpack-lib/lib/Compilation.js @@ -0,0 +1,5549 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const { + HookMap, + SyncHook, + SyncBailHook, + SyncWaterfallHook, + AsyncSeriesHook, + AsyncSeriesBailHook, + AsyncParallelHook +} = require("tapable"); +const util = require("util"); +const { CachedSource } = require("webpack-sources"); +const { MultiItemCache } = require("./CacheFacade"); +const Chunk = require("./Chunk"); +const ChunkGraph = require("./ChunkGraph"); +const ChunkGroup = require("./ChunkGroup"); +const ChunkRenderError = require("./ChunkRenderError"); +const ChunkTemplate = require("./ChunkTemplate"); +const CodeGenerationError = require("./CodeGenerationError"); +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 { + connectChunkGroupAndChunk, + connectChunkGroupParentAndChild +} = require("./GraphHelpers"); +const { + makeWebpackError, + tryRunOrWebpackError +} = require("./HookWebpackError"); +const MainTemplate = require("./MainTemplate"); +const Module = require("./Module"); +const ModuleDependencyError = require("./ModuleDependencyError"); +const ModuleDependencyWarning = require("./ModuleDependencyWarning"); +const ModuleGraph = require("./ModuleGraph"); +const ModuleHashingError = require("./ModuleHashingError"); +const ModuleNotFoundError = require("./ModuleNotFoundError"); +const ModuleProfile = require("./ModuleProfile"); +const ModuleRestoreError = require("./ModuleRestoreError"); +const ModuleStoreError = require("./ModuleStoreError"); +const ModuleTemplate = require("./ModuleTemplate"); +const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const RuntimeTemplate = require("./RuntimeTemplate"); +const Stats = require("./Stats"); +const WebpackError = require("./WebpackError"); +const buildChunkGraph = require("./buildChunkGraph"); +const BuildCycleError = require("./errors/BuildCycleError"); +const { Logger, LogType } = 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 { + compareLocations, + concatComparators, + compareSelect, + compareIds, + compareStringsNumeric, + compareModulesByIdentifier +} = require("./util/comparators"); +const createHash = require("./util/createHash"); +const { + arrayToSetDeprecation, + soonFrozenObjectDeprecation, + createFakeHook +} = require("./util/deprecation"); +const processAsyncTree = require("./util/processAsyncTree"); +const { getRuntimeKey } = require("./util/runtime"); +const { isSourceEqual } = require("./util/source"); + +/** @template T @typedef {import("tapable").AsArray} AsArray */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ +/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Cache")} Cache */ +/** @typedef {import("./CacheFacade")} CacheFacade */ +/** @typedef {import("./Chunk").ChunkId} ChunkId */ +/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Compiler").CompilationParams} CompilationParams */ +/** @typedef {import("./Compiler").ModuleMemCachesItem} ModuleMemCachesItem */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ +/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */ +/** @typedef {import("./NormalModule").NormalModuleCompilationHooks} NormalModuleCompilationHooks */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./ModuleFactory")} ModuleFactory */ +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @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 */ +/** @typedef {import("./util/createHash").Algorithm} Algorithm */ +/** + * @template T + * @typedef {import("./util/deprecation").FakeHook} FakeHook + */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {WeakMap} References */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** + * @callback Callback + * @param {(WebpackError | null)=} err + * @returns {void} + */ + +/** + * @callback ModuleCallback + * @param {(WebpackError | null)=} err + * @param {(Module | null)=} result + * @returns {void} + */ + +/** + * @callback ModuleFactoryResultCallback + * @param {(WebpackError | null)=} err + * @param {ModuleFactoryResult=} result + * @returns {void} + */ + +/** + * @callback ModuleOrFactoryResultCallback + * @param {(WebpackError | null)=} err + * @param {Module | ModuleFactoryResult=} result + * @returns {void} + */ + +/** + * @callback ExecuteModuleCallback + * @param {WebpackError | null} err + * @param {ExecuteModuleResult=} result + * @returns {void} + */ + +/** + * @callback DepBlockVarDependenciesCallback + * @param {Dependency} dependency + * @returns {any} + */ + +/** @typedef {new (...args: any[]) => Dependency} DepConstructor */ + +/** @typedef {Record} CompilationAssets */ + +/** + * @typedef {object} AvailableModulesChunkGroupMapping + * @property {ChunkGroup} chunkGroup + * @property {Set} availableModules + * @property {boolean} needCopy + */ + +/** + * @typedef {object} DependenciesBlockLike + * @property {Dependency[]} dependencies + * @property {AsyncDependenciesBlock[]} blocks + */ + +/** + * @typedef {object} ChunkPathData + * @property {string|number} id + * @property {string=} name + * @property {string} hash + * @property {function(number): string=} hashWithLength + * @property {(Record)=} contentHash + * @property {(Record string>)=} contentHashWithLength + */ + +/** + * @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 + */ + +/** + * @typedef {object} RuntimeRequirementsContext + * @property {ChunkGraph} chunkGraph the chunk graph + * @property {CodeGenerationResults} codeGenerationResults the code generation results + */ + +/** + * @typedef {object} ExecuteModuleOptions + * @property {EntryOptions=} entryOptions + */ + +/** + * @typedef {object} ExecuteModuleResult + * @property {any} exports + * @property {boolean} cacheable + * @property {Map} assets + * @property {LazySet} fileDependencies + * @property {LazySet} contextDependencies + * @property {LazySet} missingDependencies + * @property {LazySet} buildDependencies + */ + +/** + * @typedef {{ id: string, exports: any, loaded: boolean }} ModuleObject + * + * /** + * @typedef {object} ExecuteModuleArgument + * @property {Module} module + * @property {ModuleObject=} moduleObject + * @property {any} preparedInfo + * @property {CodeGenerationResult} codeGenerationResult + */ + +/** + * @typedef {object} ExecuteModuleContext + * @property {Map} assets + * @property {Chunk} chunk + * @property {ChunkGraph} chunkGraph + * @property {function(string): any=} __webpack_require__ + */ + +/** + * @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 + */ + +/** + * @typedef {object} LogEntry + * @property {string} type + * @property {any[]=} args + * @property {number} time + * @property {string[]=} trace + */ + +/** + * @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 {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 */ + +/** + * @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 {object} ModulePathData + * @property {string|number} id + * @property {string} hash + * @property {function(number): string=} hashWithLength + */ + +/** + * @typedef {object} PathData + * @property {ChunkGraph=} chunkGraph + * @property {string=} hash + * @property {function(number): string=} 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 {function(number): string=} contentHashWithLength + * @property {boolean=} noChunkHash + * @property {string=} url + */ + +/** + * @typedef {object} KnownNormalizedStatsOptions + * @property {string} context + * @property {RequestShortener} requestShortener + * @property {string} chunksSort + * @property {string} modulesSort + * @property {string} chunkModulesSort + * @property {string} nestedModulesSort + * @property {string} 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: "module" | "chunk" | "root-of-chunk" | "nested") => 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 {any} _env + */ + +/** @typedef {KnownNormalizedStatsOptions & Omit & Record} NormalizedStatsOptions */ + +/** + * @typedef {object} KnownCreateStatsOptionsContext + * @property {boolean=} forToString + */ + +/** @typedef {Record & KnownCreateStatsOptionsContext} CreateStatsOptionsContext */ + +/** @typedef {{module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[]}[]} 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( + /** + * @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 +/** + * @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 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); + +/** @type {WeakMap} */ +const unsafeCacheDependencies = new WeakMap(); + +/** @type {WeakMap} */ +const unsafeCacheData = new WeakMap(); + +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?: true | Function }} ProcessAssetsAdditionalOptions */ + /** @type {AsyncSeriesHook<[CompilationAssets], ProcessAssetsAdditionalOptions>} */ + const processAssetsHook = new AsyncSeriesHook(["assets"]); + + let savedAssets = new Set(); + /** + * @param {CompilationAssets} assets assets + * @returns {CompilationAssets} new assets + */ + const popNewAssets = assets => { + let newAssets; + for (const file of Object.keys(assets)) { + if (savedAssets.has(file)) continue; + if (newAssets === undefined) { + newAssets = Object.create(null); + } + newAssets[file] = assets[file]; + savedAssets.add(file); + } + return 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; + const processedAssets = additionalAssetsFn ? new WeakSet() : undefined; + switch (type) { + case "sync": + if (additionalAssetsFn) { + this.hooks.processAdditionalAssets.tap(name, assets => { + if (processedAssets.has(this.assets)) + additionalAssetsFn(assets); + }); + } + return { + ...remainingTap, + type: "async", + fn: (assets, callback) => { + try { + fn(assets); + } catch (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 "async": + if (additionalAssetsFn) { + this.hooks.processAdditionalAssets.tapAsync( + name, + (assets, callback) => { + if (processedAssets.has(this.assets)) + return additionalAssetsFn(assets, callback); + callback(); + } + ); + } + return { + ...remainingTap, + fn: (assets, callback) => { + fn(assets, 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 (processedAssets.has(this.assets)) + return additionalAssetsFn(assets); + return Promise.resolve(); + }); + } + return { + ...remainingTap, + 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 + ); + } + }); + } + }; + } + } + }); + + /** @type {SyncHook<[CompilationAssets]>} */ + const afterProcessAssetsHook = new SyncHook(["assets"]); + + /** + * @template T + * @param {string} name name of the hook + * @param {number} stage new stage + * @param {function(): AsArray} getArgs get old hook function args + * @param {string=} code deprecation code (not deprecated when unset) + * @returns {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} fake hook which redirects + */ + const createProcessAssetsHook = (name, stage, getArgs, code) => { + if (!this._backCompat && code) return; + /** + * @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.`; + 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 {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<[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]>} */ + dependencyReferencedExports: new SyncWaterfallHook([ + "referencedExports", + "dependency", + "runtime" + ]), + + /** @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([]), + + /** @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"]), + /** @type {SyncHook<[Iterable]>} */ + afterOptimizeDependencies: new SyncHook(["modules"]), + + /** @type {SyncHook<[]>} */ + optimize: new SyncHook([]), + /** @type {SyncBailHook<[Iterable], boolean | void>} */ + optimizeModules: new SyncBailHook(["modules"]), + /** @type {SyncHook<[Iterable]>} */ + afterOptimizeModules: new SyncHook(["modules"]), + + /** @type {SyncBailHook<[Iterable, ChunkGroup[]], boolean | void>} */ + optimizeChunks: 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"]), + + /** @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, Set, RuntimeRequirementsContext]>} */ + additionalChunkRuntimeRequirements: new SyncHook([ + "chunk", + "runtimeRequirements", + "context" + ]), + /** @type {HookMap, RuntimeRequirementsContext], void>>} */ + runtimeRequirementInChunk: new HookMap( + () => new SyncBailHook(["chunk", "runtimeRequirements", "context"]) + ), + /** @type {SyncHook<[Module, Set, RuntimeRequirementsContext]>} */ + additionalModuleRuntimeRequirements: new SyncHook([ + "module", + "runtimeRequirements", + "context" + ]), + /** @type {HookMap, RuntimeRequirementsContext], void>>} */ + runtimeRequirementInModule: new HookMap( + () => new SyncBailHook(["module", "runtimeRequirements", "context"]) + ), + /** @type {SyncHook<[Chunk, Set, RuntimeRequirementsContext]>} */ + additionalTreeRuntimeRequirements: new SyncHook([ + "chunk", + "runtimeRequirements", + "context" + ]), + /** @type {HookMap, RuntimeRequirementsContext], void>>} */ + runtimeRequirementInTree: new HookMap( + () => new SyncBailHook(["chunk", "runtimeRequirements", "context"]) + ), + + /** @type {SyncHook<[RuntimeModule, Chunk]>} */ + runtimeModule: new SyncHook(["module", "chunk"]), + + /** @type {SyncHook<[Iterable, any]>} */ + reviveModules: new SyncHook(["modules", "records"]), + /** @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, any]>} */ + reviveChunks: new SyncHook(["chunks", "records"]), + /** @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, any]>} */ + recordModules: new SyncHook(["modules", "records"]), + /** @type {SyncHook<[Iterable, any]>} */ + 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<[any]>} */ + recordHash: new SyncHook(["records"]), + /** @type {SyncHook<[Compilation, any]>} */ + record: new SyncHook(["compilation", "records"]), + + /** @type {SyncHook<[]>} */ + beforeModuleAssets: new SyncHook([]), + /** @type {SyncBailHook<[], boolean | void>} */ + shouldGenerateChunkAssets: new SyncBailHook([]), + /** @type {SyncHook<[]>} */ + beforeChunkAssets: new SyncHook([]), + // TODO webpack 6 remove + /** @deprecated */ + additionalChunkAssets: createProcessAssetsHook( + "additionalChunkAssets", + Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + () => [this.chunks], + "DEP_WEBPACK_COMPILATION_ADDITIONAL_CHUNK_ASSETS" + ), + + // TODO webpack 6 deprecate + /** @deprecated */ + additionalAssets: createProcessAssetsHook( + "additionalAssets", + Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, + () => [] + ), + // TODO webpack 6 remove + /** @deprecated */ + optimizeChunkAssets: createProcessAssetsHook( + "optimizeChunkAssets", + Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, + () => [this.chunks], + "DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS" + ), + // TODO webpack 6 remove + /** @deprecated */ + afterOptimizeChunkAssets: 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([]), + + /** @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"]), + + /** @type {SyncWaterfallHook<[string, object, 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" + ]), + + /** @type {SyncBailHook<[string, LogEntry], boolean | void>} */ + log: new SyncBailHook(["origin", "logEntry"]), + + /** @type {SyncWaterfallHook<[WebpackError[]]>} */ + processWarnings: new SyncWaterfallHook(["warnings"]), + /** @type {SyncWaterfallHook<[WebpackError[]]>} */ + 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"]), + + 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; + /** @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; + this.compilerPath = compiler.compilerPath; + + this.logger = this.getLogger("webpack.Compilation"); + + const options = /** @type {WebpackOptions} */ (compiler.options); + this.options = options; + this.outputOptions = 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, this) + }; + defineRemovedModuleTemplates(this.moduleTemplates); + + /** @type {Map> | undefined} */ + this.moduleMemCaches = undefined; + /** @type {Map> | undefined} */ + this.moduleMemCaches2 = undefined; + this.moduleGraph = new ModuleGraph(); + /** @type {ChunkGraph} */ + this.chunkGraph = undefined; + /** @type {CodeGenerationResults} */ + 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) + }); + + /** + * 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} */ + this.entries = new Map(); + /** @type {EntryData} */ + this.globalEntry = { + dependencies: [], + includeDependencies: [], + options: { + name: undefined + } + }; + /** @type {Map} */ + this.entrypoints = new Map(); + /** @type {Entrypoint[]} */ + this.asyncEntrypoints = []; + /** @type {Set} */ + this.chunks = new Set(); + /** @type {ChunkGroup[]} */ + this.chunkGroups = []; + /** @type {Map} */ + this.namedChunkGroups = new Map(); + /** @type {Map} */ + this.namedChunks = new Map(); + /** @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.records = null; + /** @type {string[]} */ + this.additionalChunkAssets = []; + /** @type {CompilationAssets} */ + this.assets = {}; + /** @type {Map} */ + this.assetsInfo = new Map(); + /** @type {Map>>} */ + this._assetsRelatedIn = new Map(); + /** @type {WebpackError[]} */ + this.errors = []; + /** @type {WebpackError[]} */ + this.warnings = []; + /** @type {Compilation[]} */ + this.children = []; + /** @type {Map} */ + this.logging = new Map(); + /** @type {Map} */ + this.dependencyFactories = new Map(); + /** @type {DependencyTemplates} */ + this.dependencyTemplates = new DependencyTemplates( + this.outputOptions.hashFunction + ); + /** @type {Record} */ + this.childrenCounters = {}; + /** @type {Set} */ + this.usedChunkIds = null; + /** @type {Set} */ + this.usedModuleIds = null; + /** @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 {LazySet} */ + this.fileDependencies = new LazySet(); + /** @type {LazySet} */ + this.contextDependencies = new LazySet(); + /** @type {LazySet} */ + this.missingDependencies = new LazySet(); + /** @type {LazySet} */ + this.buildDependencies = new LazySet(); + // TODO webpack 6 remove + this.compilationDependencies = { + add: util.deprecate( + /** + * @param {string} item item + * @returns {LazySet} 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"); + + const unsafeCache = options.module.unsafeCache; + this._unsafeCache = Boolean(unsafeCache); + this._unsafeCachePredicate = + typeof unsafeCache === "function" ? unsafeCache : () => true; + } + + getStats() { + return new Stats(this); + } + + /** + * @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 }; + } + 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 = {}; + // eslint-disable-next-line guard-for-in + for (const key in optionsOrPreset) { + options[key] = optionsOrPreset[/** @type {keyof StatsOptions} */ (key)]; + } + 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); + } + + /** + * @param {NormalizedStatsOptions} options options + * @returns {StatsFactory} the stats factory + */ + createStatsFactory(options) { + const statsFactory = new StatsFactory(); + this.hooks.statsFactory.call(statsFactory, options); + return statsFactory; + } + + /** + * @param {NormalizedStatsOptions} options options + * @returns {StatsPrinter} the stats printer + */ + createStatsPrinter(options) { + const statsPrinter = new StatsPrinter(); + this.hooks.statsPrinter.call(statsPrinter, options); + return statsPrinter; + } + + /** + * @param {string} name cache name + * @returns {CacheFacade} the cache facade instance + */ + getCache(name) { + return this.compiler.getCache(name); + } + + /** + * @param {string | (function(): 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" + ); + } + } + 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; + } + /** @type {LogEntry} */ + const logEntry = { + time: Date.now(), + type, + args, + trace + }; + 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] + }` + ); + } + } + }, + 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}`); + } + ); + } + + /** + * @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); + } + + /** + * @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); + } + + const currentProfile = this.profile + ? this.moduleGraph.getProfile(module) + : undefined; + if (currentProfile !== undefined) { + currentProfile.markRestoringStart(); + } + + 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 /** @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); + } + + /** + * 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); + } + + /** + * 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(); + } + + module.needBuild( + { + compilation: this, + fileSystemInfo: this.fileSystemInfo, + valueCacheVersions: this.valueCacheVersions + }, + (err, needBuild) => { + if (err) return callback(err); + + if (!needBuild) { + if (currentProfile !== undefined) { + currentProfile.markBuildingEnd(); + } + this.hooks.stillValidModule.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(); + }); + } + ); + } + ); + } + + /** + * @param {Module} module to be processed for deps + * @param {ModuleCallback} callback callback to be triggered + * @returns {void} + */ + processModuleDependencies(module, callback) { + this.processDependenciesQueue.add(module, callback); + } + + /** + * @param {Module} module to be processed for deps + * @returns {void} + */ + processModuleDependenciesNonRecursive(module) { + /** + * @param {DependenciesBlock} block block + */ + const processDependenciesBlock = block => { + if (block.dependencies) { + let i = 0; + for (const dep of block.dependencies) { + this.moduleGraph.setParents(dep, block, module, i++); + } + } + if (block.blocks) { + for (const b of block.blocks) processDependenciesBlock(b); + } + }; + + processDependenciesBlock(module); + } + + /** + * @param {Module} module to be processed for deps + * @param {ModuleCallback} callback callback to be triggered + * @returns {void} + */ + _processModuleDependencies(module, callback) { + /** @type {Array<{factory: ModuleFactory, dependencies: Dependency[], context: string|undefined, originModule: Module|null}>} */ + const sortedDependencies = []; + + /** @type {DependenciesBlock} */ + let currentBlock; + + /** @type {Map>} */ + let dependencies; + /** @type {DepConstructor} */ + 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; + + /** + * @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(); + } + + // 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(); + }; + + /** + * @param {WebpackError=} err error + * @returns {void} + */ + const onTransitiveTasksFinished = err => { + if (err) return callback(err); + this.processDependenciesQueue.decreaseParallelism(); + + return callback(); + }; + + /** + * @param {Dependency} dep dependency + * @param {number} index index in block + * @returns {void} + */ + const processDependency = (dep, index) => { + this.moduleGraph.setParents(dep, currentBlock, module, index); + 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 + ); + 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 + ); + 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 + } catch (err) { + if (inProgressSorting <= 0) return; + inProgressSorting = -1; + onDependenciesSorted(/** @type {WebpackError} */ (err)); + return; + } + if (--inProgressSorting === 0) onDependenciesSorted(); + }); + return; + } + } catch (err) { + console.error(err); + } + } + processDependencyForResolving(dep); + }; + + /** + * @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 {DepConstructor} */ (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 = [])); + sortedDependencies.push({ + factory: factoryCacheKey2, + dependencies: list, + context: dep.getContext(), + originModule: module + }); + } + 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(); + } + + /** + * @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, + true, + false, + callback + ); + } + + /** + * @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); + } + + /** + * @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 + */ + + /** + * @param {HandleModuleCreationOptions} options options object + * @param {ModuleCallback} callback callback + * @returns {void} + */ + handleModuleCreation( + { + factory, + dependencies, + originModule, + contextInfo, + context, + recursive = true, + connectOrigin = recursive, + checkCycle = !recursive + }, + callback + ) { + const moduleGraph = this.moduleGraph; + + const currentProfile = this.profile ? new ModuleProfile() : undefined; + + this.factorizeModule( + { + currentProfile, + factory, + dependencies, + factoryResult: true, + originModule, + contextInfo, + context + }, + (err, factoryResult) => { + const applyFactoryResultDependencies = () => { + const { fileDependencies, contextDependencies, missingDependencies } = + /** @type {ModuleFactoryResult} */ (factoryResult); + if (fileDependencies) { + this.fileDependencies.addAll(fileDependencies); + } + if (contextDependencies) { + this.contextDependencies.addAll(contextDependencies); + } + if (missingDependencies) { + this.missingDependencies.addAll(missingDependencies); + } + }; + if (err) { + if (factoryResult) applyFactoryResultDependencies(); + if (dependencies.every(d => d.optional)) { + this.warnings.push(err); + return callback(); + } + this.errors.push(err); + return callback(err); + } + + const newModule = + /** @type {ModuleFactoryResult} */ + (factoryResult).module; + + if (!newModule) { + applyFactoryResultDependencies(); + return callback(); + } + + if (currentProfile !== undefined) { + moduleGraph.setProfile(newModule, currentProfile); + } + + this.addModule(newModule, (err, _module) => { + if (err) { + applyFactoryResultDependencies(); + if (!err.module) { + err.module = _module; + } + this.errors.push(err); + + return callback(err); + } + + const module = + /** @type {Module & { restoreFromUnsafeCache?: Function }} */ + (_module); + + if ( + this._unsafeCache && + /** @type {ModuleFactoryResult} */ + (factoryResult).cacheable !== false && + module.restoreFromUnsafeCache && + this._unsafeCachePredicate(module) + ) { + const unsafeCacheableModule = + /** @type {Module & { restoreFromUnsafeCache: Function }} */ + (module); + for (let i = 0; i < dependencies.length; i++) { + const dependency = dependencies[i]; + moduleGraph.setResolvedModule( + connectOrigin ? originModule : null, + dependency, + unsafeCacheableModule + ); + unsafeCacheDependencies.set(dependency, unsafeCacheableModule); + } + if (!unsafeCacheData.has(unsafeCacheableModule)) { + unsafeCacheData.set( + unsafeCacheableModule, + unsafeCacheableModule.getUnsafeCacheData() + ); + } + } else { + applyFactoryResultDependencies(); + for (let i = 0; i < dependencies.length; i++) { + const dependency = dependencies[i]; + moduleGraph.setResolvedModule( + connectOrigin ? originModule : null, + dependency, + module + ); + } + } + + moduleGraph.setIssuerIfUnset( + module, + originModule !== undefined ? originModule : null + ); + if (module !== newModule && currentProfile !== undefined) { + const otherProfile = moduleGraph.getProfile(module); + if (otherProfile !== undefined) { + currentProfile.mergeInto(otherProfile); + } else { + moduleGraph.setProfile(module, currentProfile); + } + } + + this._handleModuleBuildAndDependencies( + originModule, + module, + recursive, + checkCycle, + callback + ); + }); + } + ); + } + + /** + * @private + * @param {Module} 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 {ModuleCallback} callback callback + * @returns {void} + */ + _handleModuleBuildAndDependencies( + originModule, + module, + recursive, + checkCycle, + callback + ) { + // Check for cycles when build is trigger inside another build + /** @type {Set | undefined} */ + let creatingModuleDuringBuildSet; + if (checkCycle && this.buildQueue.isProcessing(originModule)) { + // Track build dependency + creatingModuleDuringBuildSet = + this.creatingModuleDuringBuild.get(originModule); + if (creatingModuleDuringBuildSet === undefined) { + creatingModuleDuringBuildSet = new Set(); + this.creatingModuleDuringBuild.set( + 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); + } + } + } + } + } + + this.buildModule(module, err => { + if (creatingModuleDuringBuildSet !== undefined) { + creatingModuleDuringBuildSet.delete(module); + } + if (err) { + if (!err.module) { + err.module = module; + } + this.errors.push(err); + + return callback(err); + } + + if (!recursive) { + this.processModuleDependenciesNonRecursive(module); + callback(null, module); + return; + } + + // This avoids deadlocks for circular dependencies + if (this.processDependenciesQueue.isProcessing(module)) { + return callback(null, module); + } + + this.processModuleDependencies(module, err => { + if (err) { + return callback(err); + } + callback(null, module); + }); + }); + } + + /** + * @param {FactorizeModuleOptions} options options object + * @param {ModuleOrFactoryResultCallback} callback callback + * @returns {void} + */ + _factorizeModule( + { + currentProfile, + factory, + dependencies, + originModule, + factoryResult, + contextInfo, + context + }, + callback + ) { + if (currentProfile !== undefined) { + currentProfile.markFactoryStart(); + } + factory.create( + { + contextInfo: { + issuer: originModule ? originModule.nameForCondition() : "", + issuerLayer: originModule ? originModule.layer : null, + compiler: this.compiler.name, + ...contextInfo + }, + resolveOptions: originModule ? originModule.resolveOptions : undefined, + context: + context || + (originModule ? 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(); + } + + if (currentProfile !== undefined) { + currentProfile.markFactoryEnd(); + } + + callback(null, factoryResult ? result : result.module); + } + ); + } + + /** + * @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); + } + + /** + * @param {object} options options + * @param {string} options.context context string path + * @param {Dependency} options.dependency dependency used to create Module chain + * @param {Partial=} options.contextInfo additional context info for the root module + * @param {ModuleCallback} callback callback for when module chain is complete + * @returns {void} will throw if dependency instance is not a valid Dependency + */ + addModuleTree({ context, dependency, contextInfo }, callback) { + if ( + typeof dependency !== "object" || + dependency === null || + !dependency.constructor + ) { + return callback( + new WebpackError("Parameter 'dependency' must be a Dependency") + ); + } + const Dep = /** @type {DepConstructor} */ (dependency.constructor); + const moduleFactory = this.dependencyFactories.get(Dep); + if (!moduleFactory) { + return callback( + new WebpackError( + `No dependency factory available for this dependency type: ${dependency.constructor.name}` + ) + ); + } + + this.handleModuleCreation( + { + factory: moduleFactory, + dependencies: [dependency], + originModule: null, + contextInfo, + context + }, + (err, result) => { + if (err && this.bail) { + callback(err); + this.buildQueue.stop(); + this.rebuildQueue.stop(); + this.processDependenciesQueue.stop(); + this.factorizeQueue.stop(); + } else if (!err && result) { + callback(null, result); + } else { + callback(); + } + } + ); + } + + /** + * @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); + } + + /** + * @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 + ); + } + + /** + * @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; + 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)) { + 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) { + entryData.options[key] = options[key]; + } else { + return callback( + new WebpackError( + `Conflicting entry option ${key} = ${entryData.options[key]} vs ${options[key]}` + ) + ); + } + } + } + + this.hooks.addEntry.call(entry, options); + + this.addModuleTree( + { + context, + dependency: entry, + contextInfo: entryData.options.layer + ? { issuerLayer: entryData.options.layer } + : undefined + }, + (err, module) => { + if (err) { + this.hooks.failedEntry.call(entry, options, err); + return callback(err); + } + this.hooks.succeedEntry.call(entry, options, module); + return callback(null, 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); + } + + /** + * @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.slice(); + const oldBlocks = module.blocks.slice(); + module.invalidateBuild(); + this.buildQueue.invalidate(module); + this.buildModule(module, err => { + if (err) { + return this.hooks.finishRebuildingModule.callAsync(module, err2 => { + if (err2) { + callback( + makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule") + ); + return; + } + callback(err); + }); + } + + this.processDependenciesQueue.invalidate(module); + this.moduleGraph.unfreeze(); + this.processModuleDependencies(module, err => { + if (err) return callback(err); + this.removeReasonsOfDependencyBlock(module, { + dependencies: oldDependencies, + blocks: oldBlocks + }); + this.hooks.finishRebuildingModule.callAsync(module, err2 => { + if (err2) { + callback( + makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule") + ); + return; + } + callback(null, module); + }); + }); + }); + } + + /** + * @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; + const affectedModules = new Set(); + const infectedModules = new Set(); + let statNew = 0; + let statChanged = 0; + let statUnchanged = 0; + let statReferencesChanged = 0; + let statWithoutBuild = 0; + + /** + * @param {Module} module module + * @returns {References | undefined} references + */ + const computeReferences = module => { + /** @type {References | 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; + }; + + /** + * @param {Module} module the module + * @param {References | 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 + 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 + const memCache = new WeakTupleMap(); + moduleMemCaches.set(module, memCache); + affectedModules.add(module); + cachedMemCache.references = computeReferences(module); + cachedMemCache.memCache = memCache; + statReferencesChanged++; + } else { + // keep the old mem cache + moduleMemCaches.set(module, cachedMemCache.memCache); + statUnchanged++; + } + } else { + infectedModules.add(module); + moduleMemCacheCache.delete(module); + statWithoutBuild++; + } + modulesWithoutCache.delete(module); + } else { + moduleMemCacheCache.delete(module); + } + } + + for (const module of modulesWithoutCache) { + const buildInfo = module.buildInfo; + if (buildInfo) { + // create a new entry + const memCache = new WeakTupleMap(); + moduleMemCacheCache.set(module, { + buildInfo, + references: computeReferences(module), + memCache + }); + moduleMemCaches.set(module, memCache); + affectedModules.add(module); + statNew++; + } else { + infectedModules.add(module); + statWithoutBuild++; + } + } + + /** + * @param {readonly ModuleGraphConnection[]} 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; + }; + const directOnlyInfectedModules = new Set(); + for (const module of infectedModules) { + for (const [ + referencingModule, + connections + ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { + if (!referencingModule) continue; + if (infectedModules.has(referencingModule)) continue; + const type = reduceAffectType(connections); + if (!type) continue; + if (type === true) { + directOnlyInfectedModules.add(referencingModule); + } else { + infectedModules.add(referencingModule); + } + } + } + for (const module of directOnlyInfectedModules) infectedModules.add(module); + 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); + } + 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)` + ); + } + + _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; + /** + * @param {Module} module module + * @returns {{ id: ModuleId, modules?: Map, blocks?: (string | number | null)[] }} references + */ + const computeReferences = module => { + const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + /** @type {Map | undefined} */ + let modules; + /** @type {(string | number | 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 = Array.from(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 }; + }; + /** + * @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 + * @returns {boolean} ok? + */ + const compareReferences = (module, { id, modules, blocks }) => { + 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 = Array.from(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; + } + return true; + }; + + for (const [module, memCache] of moduleMemCaches) { + /** @type {{ references: { id: string | number, modules?: Map, blocks?: (string | number | null)[]}, memCache: WeakTupleMap }} */ + const cache = memCache.get(key); + if (cache === undefined) { + const memCache2 = new WeakTupleMap(); + memCache.set(key, { + references: computeReferences(module), + memCache: memCache2 + }); + moduleMemCaches2.set(module, memCache2); + statNew++; + } else if (!compareReferences(module, cache.references)) { + 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)` + ); + } + + /** + * @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 + /** + * @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); + } + }; + /** + * @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}` + ); + }; + /** + * @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) => { + const map = new Map(); + for (const [module, profile] of modulesWithProfiles) { + const list = getOrInsert( + map, + `${module.type}!${module.identifier().replace(/(!|^)[^!]*$/, "")}`, + () => [] + ); + list.push({ module, profile }); + } + + let sum = 0; + let max = 0; + for (const [key, modules] of map) { + let innerSum = 0; + let innerMax = 0; + for (const { module, profile } of modules) { + const p = getParallelism(profile); + const d = getDuration(profile); + if (d === 0 || p === 0) continue; + const t = d / p; + innerSum += t; + if (t <= 10) continue; + 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"); + // TODO keep a cacheToken (= {}) for each module in the graph + // create a new one per compilation and flag all updated files + // and parents with it + this.logger.time("report dependency errors and warnings"); + for (const module of modules) { + // TODO only run for modules with changed cacheToken + // global WeakMap> to keep modules without errors/warnings + const memCache = moduleMemCaches && moduleMemCaches.get(module); + if (memCache && memCache.get("noWarningsOrErrors")) continue; + let hasProblems = this.reportDependencyErrorsAndWarnings(module, [ + module + ]); + const errors = module.getErrors(); + if (errors !== undefined) { + for (const error of errors) { + if (!error.module) { + error.module = module; + } + this.errors.push(error); + hasProblems = true; + } + } + const warnings = module.getWarnings(); + if (warnings !== undefined) { + for (const warning of warnings) { + if (!warning.module) { + warning.module = module; + } + this.warnings.push(warning); + hasProblems = true; + } + } + if (!hasProblems && memCache) memCache.set("noWarningsOrErrors", true); + } + this.moduleGraph.unfreeze(); + this.logger.timeEnd("report dependency errors and warnings"); + + callback(); + }); + } + + unseal() { + this.hooks.unseal.call(); + this.chunks.clear(); + this.chunkGroups.length = 0; + this.namedChunks.clear(); + this.namedChunkGroups.clear(); + this.entrypoints.clear(); + this.additionalChunkAssets.length = 0; + this.assets = {}; + this.assetsInfo.clear(); + this.moduleGraph.removeAllModuleAttributes(); + this.moduleGraph.unfreeze(); + this.moduleMemCaches2 = undefined; + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + seal(callback) { + /** + * @param {WebpackError=} err err + * @returns {void} + */ + const finalCallback = err => { + this.factorizeQueue.clear(); + this.buildQueue.clear(); + this.rebuildQueue.clear(); + this.processDependenciesQueue.clear(); + this.addModuleQueue.clear(); + return callback(err); + }; + const chunkGraph = new ChunkGraph( + this.moduleGraph, + this.outputOptions.hashFunction + ); + this.chunkGraph = chunkGraph; + + if (this._backCompat) { + for (const module of this.modules) { + ChunkGraph.setChunkGraphForModule(module, chunkGraph); + } + } + + this.hooks.seal.call(); + + this.logger.time("optimize dependencies"); + while (this.hooks.optimizeDependencies.call(this.modules)) { + /* empty */ + } + this.hooks.afterOptimizeDependencies.call(this.modules); + 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); + 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); + connectChunkGroupAndChunk(entrypoint, chunk); + + const entryModules = new Set(); + for (const dep of [...this.globalEntry.dependencies, ...dependencies]) { + entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request); + + const module = this.moduleGraph.getModule(dep); + if (module) { + 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); + + /** + * @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.assignDepth(module); + modulesList.push(module); + } + } + 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(); + const dependOnEntries = []; + 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; + } + dependOnEntries.push(dependency); + } + for (const dependency of dependOnEntries) { + connectChunkGroupParentAndChild(dependency, entry); + } + } 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); + } + } + 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.optimizeModules.call(this.modules)) { + /* empty */ + } + this.hooks.afterOptimizeModules.call(this.modules); + + 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 => { + if (err) { + return finalCallback( + makeWebpackError(err, "Compilation.hooks.optimizeTree") + ); + } + + this.hooks.afterOptimizeTree.call(this.chunks, this.modules); + + this.hooks.optimizeChunkModules.callAsync( + this.chunks, + this.modules, + err => { + if (err) { + return finalCallback( + makeWebpackError(err, "Compilation.hooks.optimizeChunkModules") + ); + } + + this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules); + + const shouldRecord = this.hooks.shouldRecord.call() !== false; + + this.hooks.reviveModules.call(this.modules, 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, 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.assignRuntimeIds(); + + this.logger.time("compute affected modules with chunk graph"); + this._computeAffectedModulesWithChunkGraph(); + this.logger.timeEnd("compute affected modules with chunk graph"); + + this.sortItemsWithChunkIds(); + + if (shouldRecord) { + this.hooks.recordModules.call(this.modules, this.records); + this.hooks.recordChunks.call(this.chunks, this.records); + } + + this.hooks.optimizeCodeGeneration.call(this.modules); + this.logger.timeEnd("optimize"); + + this.logger.time("module hashing"); + this.hooks.beforeModuleHash.call(); + this.createModuleHashes(); + this.hooks.afterModuleHash.call(); + this.logger.timeEnd("module hashing"); + + this.logger.time("code generation"); + this.hooks.beforeCodeGeneration.call(); + this.codeGeneration(err => { + if (err) { + return finalCallback(err); + } + 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(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, 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(); + } + }); + }); + } + ); + }); + } + + /** + * @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) { + let hasProblems = false; + for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { + const block = blocks[indexBlock]; + const dependencies = block.dependencies; + + for (let indexDep = 0; indexDep < dependencies.length; indexDep++) { + const d = dependencies[indexDep]; + + 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); + this.warnings.push(warning); + hasProblems = true; + } + } + 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); + this.errors.push(error); + hasProblems = true; + } + } + } + + if (this.reportDependencyErrorsAndWarnings(module, block.blocks)) + hasProblems = true; + } + return hasProblems; + } + + /** + * @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); + } + + /** + * @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 = this.codeGenerationResults; + /** @type {WebpackError[]} */ + const errors = []; + /** @type {NotCodeGeneratedModules | undefined} */ + let notCodeGeneratedModules; + const runIteration = () => { + /** @type {CodeGenerationJobs} */ + let delayedJobs = []; + let delayedModules = new Set(); + asyncLib.eachLimit( + jobs, + /** @type {number} */ + (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(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(); + } + + /** + * @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 {function((WebpackError | null)=, 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)); + let result; + if (!cachedResult) { + try { + codeGenerated = true; + this.codeGeneratedModules.add(module); + result = module.codeGeneration({ + chunkGraph, + moduleGraph, + dependencyTemplates, + runtimeTemplate, + runtime, + codeGenerationResults: results, + compilation: this + }); + } catch (err) { + errors.push( + new CodeGenerationError(module, /** @type {Error} */ (err)) + ); + result = cachedResult = { + sources: new Map(), + runtimeRequirements: null + }; + } + } 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; + } + + /** + * @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 = 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, + cached, + false + ); + } + continue; + } + } + 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) { + 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) { + 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"); + } + + // TODO webpack 6 make chunkGraph argument non-optional + /** + * @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 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 }; + } + const name = groupOptions.name; + + if (name) { + const chunkGroup = this.namedChunkGroups.get(name); + if (chunkGroup !== undefined) { + if (module) { + chunkGroup.addOrigin( + module, + /** @type {DependencyLocation} */ + (loc), + request + ); + } + return chunkGroup; + } + } + const chunkGroup = new ChunkGroup(groupOptions); + if (module) + chunkGroup.addOrigin( + module, + /** @type {DependencyLocation} */ + (loc), + request + ); + const chunk = this.addChunk(name); + + connectChunkGroupAndChunk(chunkGroup, chunk); + + this.chunkGroups.push(chunkGroup); + if (name) { + this.namedChunkGroups.set(name, chunkGroup); + } + return chunkGroup; + } + + /** + * @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 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 (entrypoint !== undefined) { + if (module) { + entrypoint.addOrigin(module, loc, request); + } + return entrypoint; + } + } else if (entrypoint) { + throw new Error( + `Cannot add an async entrypoint with the name '${name}', because there is already an chunk group with this name` + ); + } + } + const chunk = this.addChunk(name); + if (options.filename) { + chunk.filenameTemplate = options.filename; + } + const entrypoint = new Entrypoint(options, false); + entrypoint.setRuntimeChunk(chunk); + entrypoint.setEntrypointChunk(chunk); + if (name) { + this.namedChunkGroups.set(name, entrypoint); + } + this.chunkGroups.push(entrypoint); + this.asyncEntrypoints.push(entrypoint); + connectChunkGroupAndChunk(entrypoint, chunk); + 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 {string=} 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); + if (chunk !== undefined) { + return 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; + } + + /** + * @deprecated + * @param {Module} module module to assign depth + * @returns {void} + */ + assignDepth(module) { + const moduleGraph = this.moduleGraph; + + const queue = new Set([module]); + /** @type {number} */ + let depth; + + moduleGraph.setDepth(module, 0); + + /** + * @param {Module} module module for processing + * @returns {void} + */ + const processModule = module => { + if (!moduleGraph.setDepthIfLower(module, depth)) return; + queue.add(module); + }; + + for (module of queue) { + queue.delete(module); + depth = /** @type {number} */ (moduleGraph.getDepth(module)) + 1; + + for (const connection of moduleGraph.getOutgoingConnections(module)) { + const refModule = connection.module; + if (refModule) { + processModule(refModule); + } + } + } + } + + /** + * @param {Set} modules module to assign depth + * @returns {void} + */ + assignDepths(modules) { + const moduleGraph = this.moduleGraph; + + /** @type {Set} */ + const queue = new Set(modules); + queue.add(1); + let depth = 0; + + let i = 0; + for (const module of queue) { + i++; + if (typeof module === "number") { + depth = module; + if (queue.size === i) return; + queue.add(depth + 1); + } else { + moduleGraph.setDepth(module, depth); + for (const { module: refModule } of moduleGraph.getOutgoingConnections( + module + )) { + if (refModule) { + queue.add(refModule); + } + } + } + } + } + + /** + * @param {Dependency} dependency the dependency + * @param {RuntimeSpec} runtime the runtime + * @returns {(string[] | ReferencedExport)[]} referenced exports + */ + getDependencyReferencedExports(dependency, runtime) { + const referencedExports = dependency.getReferencedExports( + this.moduleGraph, + runtime + ); + return this.hooks.dependencyReferencedExports.call( + referencedExports, + dependency, + runtime + ); + } + + /** + * @param {Module} module module relationship for removal + * @param {DependenciesBlockLike} block //TODO: good description + * @returns {void} + */ + removeReasonsOfDependencyBlock(module, block) { + if (block.blocks) { + for (const b of block.blocks) { + this.removeReasonsOfDependencyBlock(module, b); + } + } + + 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); + } + } + } + } + } + } + + /** + * @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); + } + } + + /** + * @param {DependenciesBlock} block block tie for Chunk + * @param {Chunk} chunk chunk to remove from dep + * @returns {void} + */ + removeChunkFromDependencies(block, chunk) { + /** + * @param {Dependency} d dependency to (maybe) patch up + */ + const iteratorDependency = d => { + const depModule = this.moduleGraph.getModule(d); + if (!depModule) { + return; + } + this.patchChunksAfterReasonRemoval(depModule, chunk); + }; + + const blocks = block.blocks; + for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { + const asyncBlock = blocks[indexBlock]; + 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 (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { + const iteratedChunk = chunks[indexChunk]; + chunkGroup.removeChunk(iteratedChunk); + // Recurse + this.removeChunkFromDependencies(block, iteratedChunk); + } + } + + if (block.dependencies) { + for (const dep of block.dependencies) iteratorDependency(dep); + } + } + + assignRuntimeIds() { + const { chunkGraph } = this; + /** + * @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); + } + } + + sortItemsWithChunkIds() { + for (const chunkGroup of this.chunkGroups) { + chunkGroup.sortItems(); + } + + this.errors.sort(compareErrors); + this.warnings.sort(compareErrors); + this.children.sort(byNameOrHash); + } + + summarizeDependencies() { + for ( + let indexChildren = 0; + indexChildren < this.children.length; + indexChildren++ + ) { + const child = this.children[indexChildren]; + + this.fileDependencies.addAll(child.fileDependencies); + this.contextDependencies.addAll(child.contextDependencies); + this.missingDependencies.addAll(child.missingDependencies); + this.buildDependencies.addAll(child.buildDependencies); + } + + for (const module of this.modules) { + module.addCacheDependencies( + this.fileDependencies, + this.contextDependencies, + this.missingDependencies, + this.buildDependencies + ); + } + } + + 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 = memCache.get(`moduleHash-${getRuntimeKey(runtime)}`); + if (digest !== undefined) { + chunkGraph.setModuleHashes( + module, + runtime, + digest, + digest.slice(0, hashDigestLength) + ); + statModulesFromCache++; + continue; + } + } + statModulesHashed++; + const digest = this._createModuleHash( + module, + chunkGraph, + runtime, + hashFunction, + runtimeTemplate, + hashDigest, + hashDigestLength, + errors + ); + if (memCache) { + memCache.set(`moduleHash-${getRuntimeKey(runtime)}`, digest); + } + } + } + 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)` + ); + } + + /** + * @private + * @param {Module} module module + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {RuntimeSpec} runtime runtime + * @param {OutputOptions["hashFunction"]} hashFunction hash function + * @param {RuntimeTemplate} runtimeTemplate runtime template + * @param {OutputOptions["hashDigest"]} hashDigest hash digest + * @param {OutputOptions["hashDigestLength"]} hashDigestLength hash digest length + * @param {WebpackError[]} errors errors + * @returns {string} module hash digest + */ + _createModuleHash( + module, + chunkGraph, + runtime, + hashFunction, + runtimeTemplate, + hashDigest, + hashDigestLength, + errors + ) { + let moduleHashDigest; + try { + const moduleHash = createHash(/** @type {Algorithm} */ (hashFunction)); + module.updateHash(moduleHash, { + chunkGraph, + runtime, + runtimeTemplate + }); + moduleHashDigest = /** @type {string} */ (moduleHash.digest(hashDigest)); + } catch (err) { + errors.push(new ModuleHashingError(module, /** @type {Error} */ (err))); + moduleHashDigest = "XXXXXX"; + } + chunkGraph.setModuleHashes( + module, + runtime, + moduleHashDigest, + moduleHashDigest.slice(0, hashDigestLength) + ); + return moduleHashDigest; + } + + 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(/** @type {Algorithm} */ (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)); + } + 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"); + } + + this.logger.time("hashing: sort chunks"); + /* + * all non-runtime chunks need to be hashes first, + * since runtime chunk might use their hashes. + * runtime chunks need to be hashed in the correct order + * since they may depend on each other (for async entrypoints). + * So we put all non-runtime chunks first and hash them in any order. + * And order runtime chunks according to referenced between each other. + * Chunks need to be in deterministic order since we add hashes to full chunk + * during these hashing. + */ + /** @type {Chunk[]} */ + const unorderedRuntimeChunks = []; + /** @type {Chunk[]} */ + const otherChunks = []; + for (const c of this.chunks) { + if (c.hasRuntime()) { + unorderedRuntimeChunks.push(c); + } else { + otherChunks.push(c); + } + } + unorderedRuntimeChunks.sort(byId); + otherChunks.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( + Array.from(info.chunk.getAllReferencedAsyncEntrypoints()).map( + e => e.chunks[e.chunks.length - 1] + ) + )) { + const otherInfo = + /** @type {RuntimeChunkInfo} */ + (runtimeChunksMap.get(other)); + otherInfo.referencedBy.push(info); + info.remaining++; + remaining++; + } + } + /** @type {Chunk[]} */ + const runtimeChunks = []; + for (const info of runtimeChunksMap.values()) { + if (info.remaining === 0) { + runtimeChunks.push(info.chunk); + } + } + // If there are any references between chunks + // make sure to follow these chains + if (remaining > 0) { + 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; + } + } + } + // If there are still remaining references we have cycles and want to create a warning + if (remaining > 0) { + const circularRuntimeChunkInfo = []; + for (const info of runtimeChunksMap.values()) { + if (info.remaining !== 0) { + circularRuntimeChunkInfo.push(info); + } + } + 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"); + + const fullHashChunks = new Set(); + /** @type {{module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[]}[]} */ + const codeGenerationJobs = []; + /** @type {Map>} */ + const codeGenerationJobsMap = new Map(); + /** @type {WebpackError[]} */ + const errors = []; + + /** + * @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); + } + } + this.logger.timeAggregate("hashing: hash runtime modules"); + try { + this.logger.time("hashing: hash chunks"); + const chunkHash = createHash(/** @type {Algorithm} */ (hashFunction)); + if (outputOptions.hashSalt) { + chunkHash.update(outputOptions.hashSalt); + } + chunk.updateHash(chunkHash, chunkGraph); + this.hooks.chunkHash.call(chunk, chunkHash, { + chunkGraph, + codeGenerationResults: this.codeGenerationResults, + moduleGraph: this.moduleGraph, + runtimeTemplate: this.runtimeTemplate + }); + const chunkHashDigest = /** @type {string} */ ( + 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"); + }; + for (const chunk of otherChunks) processChunk(chunk); + for (const chunk of runtimeChunks) processChunk(chunk); + if (errors.length > 0) { + errors.sort(compareSelect(err => err.module, compareModulesByIdentifier)); + for (const error of errors) { + this.errors.push(error); + } + } + + 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 = /** @type {string} */ (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(/** @type {Algorithm} */ (hashFunction)); + module.updateHash(moduleHash, { + chunkGraph, + runtime: chunk.runtime, + runtimeTemplate + }); + const moduleHashDigest = /** @type {string} */ ( + moduleHash.digest(hashDigest) + ); + const oldHash = chunkGraph.getModuleHash(module, chunk.runtime); + chunkGraph.setModuleHashes( + module, + chunk.runtime, + moduleHashDigest, + moduleHashDigest.slice(0, hashDigestLength) + ); + codeGenerationJobsMap.get(oldHash).get(module).hash = moduleHashDigest; + } + const chunkHash = createHash(/** @type {Algorithm} */ (hashFunction)); + chunkHash.update(chunk.hash); + chunkHash.update(this.hash); + const chunkHashDigest = + /** @type {string} */ + (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; + } + + /** + * @param {string} file file name + * @param {Source} source asset source + * @param {AssetInfo} assetInfo extra asset information + * @returns {void} + */ + emitAsset(file, source, assetInfo = {}) { + 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); + } + + /** + * @private + * @param {string} file file name + * @param {AssetInfo} newInfo new asset information + * @param {AssetInfo=} oldInfo old asset information + */ + _setAssetInfo(file, newInfo, oldInfo = this.assetsInfo.get(file)) { + if (newInfo === undefined) { + this.assetsInfo.delete(file); + } else { + this.assetsInfo.set(file, newInfo); + } + const oldRelated = oldInfo && oldInfo.related; + const newRelated = newInfo && newInfo.related; + if (oldRelated) { + for (const key of Object.keys(oldRelated)) { + /** + * @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); + } + } + } + if (newRelated) { + for (const key of Object.keys(newRelated)) { + /** + * @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); + } + } + } + } + + /** + * @param {string} file file name + * @param {Source | function(Source): Source} newSourceOrFunction new asset source or function converting old to new + * @param {(AssetInfo | function(AssetInfo | undefined): AssetInfo) | 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 + ); + } + } + } + + /** + * @param {string} file file name + * @param {string} newFile the new name of file + */ + renameAsset(file, newFile) { + const source = this.assets[file]; + if (!source) { + throw new Error( + `Called Compilation.renameAsset for not existing filename ${file}` + ); + } + if (this.assets[newFile] && !isSourceEqual(this.assets[file], source)) { + this.errors.push( + new WebpackError( + `Conflict: Called Compilation.renameAsset for already existing filename ${newFile} with different content` + ) + ); + } + const assetInfo = this.assetsInfo.get(file); + // Update related in all other assets + const relatedInInfo = this._assetsRelatedIn.get(file); + if (relatedInInfo) { + for (const [key, assets] of relatedInInfo) { + for (const name of assets) { + const info = this.assetsInfo.get(name); + if (!info) continue; + const related = info.related; + if (!related) continue; + const entry = related[key]; + 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 + } + }); + } + } + } + this._setAssetInfo(file, undefined, assetInfo); + this._setAssetInfo(newFile, assetInfo); + delete this.assets[file]; + this.assets[newFile] = source; + for (const chunk of this.chunks) { + { + const size = chunk.files.size; + chunk.files.delete(file); + if (size !== chunk.files.size) { + chunk.files.add(newFile); + } + } + { + const size = chunk.auxiliaryFiles.size; + chunk.auxiliaryFiles.delete(file); + if (size !== chunk.auxiliaryFiles.size) { + chunk.auxiliaryFiles.add(newFile); + } + } + } + } + + /** + * @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)) { + /** + * @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); + } + } + } + // TODO If this becomes a performance problem + // store a reverse mapping from asset to chunk + for (const chunk of this.chunks) { + chunk.files.delete(file); + chunk.auxiliaryFiles.delete(file); + } + } + + 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; + } + + /** + * @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 + }; + } + + clearAssets() { + for (const chunk of this.chunks) { + chunk.files.clear(); + chunk.auxiliaryFiles.clear(); + } + } + + 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); + } + } + } + } + + /** + * @param {RenderManifestOptions} options options object + * @returns {RenderManifestEntry[]} manifest entries + */ + getRenderManifest(options) { + return this.hooks.renderManifest.call([], options); + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + createChunkAssets(callback) { + const outputOptions = this.outputOptions; + 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: 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 = fileManifest.hash; + + const assetCacheItem = this._assetsCache.getItemCache( + ident, + usedHash + ); + + assetCacheItem.get((err, sourceFromCache) => { + /** @type {TemplatePath} */ + let filenameTemplate; + /** @type {string} */ + let file; + /** @type {AssetInfo} */ + let assetInfo; + + let inTry = true; + /** + * @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(); + }; + + 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; + } + + if (err) { + return errorAndCallback(err); + } + + 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 + ); + } + + /** + * @param {TemplatePath} filename used to get asset path with hash + * @param {PathData} data context data + * @returns {string} interpolated path + */ + getPath(filename, data = {}) { + if (!data.hash) { + data = { + hash: this.hash, + ...data + }; + } + return this.getAssetPath(filename, data); + } + + /** + * @param {TemplatePath} filename used to get asset path with hash + * @param {PathData} data context data + * @returns {InterpolatedPathAndAssetInfo} interpolated path and asset info + */ + getPathWithInfo(filename, data = {}) { + if (!data.hash) { + data = { + hash: this.hash, + ...data + }; + } + return this.getAssetPathWithInfo(filename, data); + } + + /** + * @param {TemplatePath} filename used to get asset path with hash + * @param {PathData} data context data + * @returns {string} interpolated path + */ + getAssetPath(filename, data) { + return this.hooks.assetPath.call( + typeof filename === "function" ? filename(data) : filename, + data, + undefined + ); + } + + /** + * @param {TemplatePath} filename used to get asset path with hash + * @param {PathData} 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 }; + } + + getWarnings() { + return this.hooks.processWarnings.call(this.warnings); + } + + 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 {Array=} 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; + return this.compiler.createChildCompiler( + this, + name, + idx, + outputOptions, + plugins + ); + } + + /** + * @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); + connectChunkGroupAndChunk(entrypoint, chunk); + 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 + ); + /** + * @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(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(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, + preparedInfo: undefined, + 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(err); + + let exports; + try { + const { + strictModuleErrorHandling, + strictModuleExceptionHandling + } = this.outputOptions; + 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__(moduleArgument, id); + }; + const interceptModuleExecution = (__webpack_require__[ + RuntimeGlobals.interceptModuleExecution.replace( + `${RuntimeGlobals.require}.`, + "" + ) + ] = []); + const moduleCache = (__webpack_require__[ + RuntimeGlobals.moduleCache.replace( + `${RuntimeGlobals.require}.`, + "" + ) + ] = {}); + + context.__webpack_require__ = __webpack_require__; + + /** + * @param {ExecuteModuleArgument} moduleArgument the module argument + * @param {string=} id id + * @returns {any} exports + */ + const __webpack_require_module__ = (moduleArgument, id) => { + 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 = execErr; + } + if (!execErr.module) 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 err = new WebpackError( + `Execution of module code from module graph (${module.readableIdentifier( + this.requestShortener + )}) failed: ${execErr.message}` + ); + err.stack = execErr.stack; + err.module = execErr.module; + return callback(err); + } + + callback(null, { + exports, + assets, + cacheable, + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + }); + } + ); + }); + }); + } + ); + } + + checkConstraints() { + const chunkGraph = this.chunkGraph; + + /** @type {Set} */ + const usedIds = new Set(); + + 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}`); + } + usedIds.add(moduleId); + } + + 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}` + ); + } + } + } + + for (const chunkGroup of this.chunkGroups) { + chunkGroup.checkConstraints(); + } + } +} + +/** + * @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 + */ + +/** + * @param {FactorizeModuleOptions} options options object + * @param {ModuleCallback | ModuleFactoryResultCallback} callback callback + * @returns {void} + */ + +// Workaround for typescript as it doesn't support function overloading in jsdoc within a class +/* eslint-disable jsdoc/require-asterisk-prefix */ +Compilation.prototype.factorizeModule = /** + @type {{ + (options: FactorizeModuleOptions & { factoryResult?: false }, callback: ModuleCallback): void; + (options: FactorizeModuleOptions & { factoryResult: true }, callback: ModuleFactoryResultCallback): void; +}} */ ( + function (options, callback) { + this.factorizeQueue.add(options, callback); + } +); +/* eslint-enable jsdoc/require-asterisk-prefix */ + +// 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 webpack 6 remove +Object.defineProperty(compilationPrototype, "cache", { + enumerable: false, + configurable: false, + get: util.deprecate( + /** + * @this {Compilation} the compilation + * @returns {Cache} the cache + */ + function () { + return this.compiler.cache; + }, + "Compilation.cache was removed in favor of Compilation.getCache()", + "DEP_WEBPACK_COMPILATION_CACHE" + ), + set: util.deprecate( + /** + * @param {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/webpack-lib/lib/Compiler.js b/webpack-lib/lib/Compiler.js new file mode 100644 index 00000000000..99d466ec990 --- /dev/null +++ b/webpack-lib/lib/Compiler.js @@ -0,0 +1,1384 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const parseJson = require("json-parse-even-better-errors"); +const asyncLib = require("neo-async"); +const { + SyncHook, + SyncBailHook, + AsyncParallelHook, + AsyncSeriesHook +} = require("tapable"); +const { SizeOnlySource } = require("webpack-sources"); +const webpack = require("."); +const Cache = require("./Cache"); +const CacheFacade = require("./CacheFacade"); +const ChunkGraph = require("./ChunkGraph"); +const Compilation = require("./Compilation"); +const ConcurrentCompilationError = require("./ConcurrentCompilationError"); +const ContextModuleFactory = require("./ContextModuleFactory"); +const ModuleGraph = require("./ModuleGraph"); +const NormalModuleFactory = require("./NormalModuleFactory"); +const RequestShortener = require("./RequestShortener"); +const ResolverFactory = require("./ResolverFactory"); +const Stats = require("./Stats"); +const Watching = require("./Watching"); +const WebpackError = require("./WebpackError"); +const { Logger } = require("./logging/Logger"); +const { join, dirname, mkdirp } = require("./util/fs"); +const { makePathsRelative } = require("./util/identifier"); +const { isSourceEqual } = require("./util/source"); + +/** @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").WebpackPluginInstance} WebpackPluginInstance */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Compilation").References} References */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./config/target").PlatformTargetProperties} PlatformTargetProperties */ +/** @typedef {import("./logging/createConsoleLogger").LoggingFunction} LoggingFunction */ +/** @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").WatchFileSystem} WatchFileSystem */ + +/** + * @template {any[]} T + * @template V + * @typedef {import("./util/WeakTupleMap")} WeakTupleMap + */ + +/** + * @typedef {object} CompilationParams + * @property {NormalModuleFactory} normalModuleFactory + * @property {ContextModuleFactory} contextModuleFactory + */ + +/** + * @template T + * @callback RunCallback + * @param {Error | null} err + * @param {T=} result + */ + +/** + * @template T + * @callback Callback + * @param {(Error | null)=} err + * @param {T=} result + */ + +/** + * @callback RunAsChildCallback + * @param {Error | null} err + * @param {Chunk[]=} entries + * @param {Compilation=} compilation + */ + +/** + * @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: any, file: string }[] | undefined) }} SimilarEntry */ + +/** @typedef {{ buildInfo: BuildInfo, references: References | undefined, memCache: WeakTupleMap }} ModuleMemCachesItem */ + +/** + * @param {string[]} 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; +}; + +/** + * @param {{[key: string]: any}} obj an object + * @param {string[]} keys the keys of the object + * @returns {{[key: string]: any}} the object with properties sorted by property name + */ +const sortObject = (obj, keys) => { + /** @type {{[key: string]: any}} */ + const o = {}; + for (const k of keys.sort()) { + o[k] = obj[k]; + } + return o; +}; + +/** + * @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); +}; + +class 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([]), + /** @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"]), + /** @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, 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<[]>} */ + 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.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 {Record} */ + 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 {ReadonlyMap | undefined} */ + this.fileTimestamps = undefined; + /** @type {ReadonlyMap | undefined} */ + this.contextTimestamps = undefined; + /** @type {number | undefined} */ + this.fsStartTime = undefined; + + /** @type {ResolverFactory} */ + this.resolverFactory = new ResolverFactory(); + + /** @type {LoggingFunction | undefined} */ + this.infrastructureLogger = undefined; + + /** @type {Readonly} */ + this.platform = { + web: null, + browser: null, + webworker: null, + node: null, + nwjs: null, + electron: null + }; + + this.options = options; + + 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(); + } + + /** + * @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 + ); + } + + /** + * @param {string | (function(): 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); + } + }, + 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}`; + }); + } + 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}`; + }); + } + return this.getInfrastructureLogger(`${name}/${childName}`); + } + ); + } + + // 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); + } + } + + 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; + } + } + + // TODO webpack 6: solve this in a better way + _cleanupLastNormalModuleFactory() { + if (this._lastNormalModuleFactory !== undefined) { + this._lastNormalModuleFactory.cleanupForCache(); + this._lastNormalModuleFactory = undefined; + } + } + + /** + * @param {WatchOptions} watchOptions the watcher's options + * @param {RunCallback} handler signals when the call finishes + * @returns {Watching} a compiler watcher + */ + watch(watchOptions, handler) { + if (this.running) { + return handler(new ConcurrentCompilationError()); + } + + this.running = true; + this.watchMode = true; + this.watching = new Watching(this, watchOptions, handler); + return this.watching; + } + + /** + * @param {RunCallback} callback signals when the call finishes + * @returns {void} + */ + run(callback) { + if (this.running) { + return callback(new ConcurrentCompilationError()); + } + + /** @type {Logger | undefined} */ + let logger; + + /** + * @param {Error | null} err error + * @param {Stats=} stats stats + */ + const finalCallback = (err, stats) => { + if (logger) logger.time("beginIdle"); + this.idle = true; + this.cache.beginIdle(); + this.idle = true; + if (logger) logger.timeEnd("beginIdle"); + this.running = false; + 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; + + /** + * @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); + this.hooks.done.callAsync(stats, err => { + if (err) return finalCallback(err); + return finalCallback(null, stats); + }); + return; + } + + 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; + + compilation.startTime = startTime; + compilation.endTime = Date.now(); + /** @type {Logger} */ + (logger).time("done hook"); + const stats = new Stats(compilation); + this.hooks.done.callAsync(stats, err => { + /** @type {Logger} */ + (logger).timeEnd("done hook"); + if (err) return finalCallback(err); + + this.hooks.additionalPass.callAsync(err => { + if (err) return finalCallback(err); + this.compile(onCompiled); + }); + }); + return; + } + + /** @type {Logger} */ + (logger).time("emitRecords"); + this.emitRecords(err => { + /** @type {Logger} */ + (logger).timeEnd("emitRecords"); + if (err) return finalCallback(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.cache.storeBuildDependencies( + compilation.buildDependencies, + err => { + if (err) return finalCallback(err); + return finalCallback(null, stats); + } + ); + }); + }); + }); + }); + }; + + const run = () => { + this.hooks.beforeRun.callAsync(this, err => { + if (err) return finalCallback(err); + + this.hooks.run.callAsync(this, err => { + if (err) return finalCallback(err); + + this.readRecords(err => { + if (err) return finalCallback(err); + + this.compile(onCompiled); + }); + }); + }); + }; + + if (this.idle) { + this.cache.endIdle(err => { + if (err) return finalCallback(err); + + this.idle = false; + run(); + }); + } else { + run(); + } + } + + /** + * @param {RunAsChildCallback} callback signals when the call finishes + * @returns {void} + */ + runAsChild(callback) { + const startTime = Date.now(); + + /** + * @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}` + ); + err.details = /** @type {Error} */ (runAsChildErr).stack; + /** @type {Compilation} */ + (this.parentCompilation).errors.push(err); + } + }; + + this.compile((err, _compilation) => { + if (err) return finalCallback(err); + + const compilation = /** @type {Compilation} */ (_compilation); + const parentCompilation = /** @type {Compilation} */ ( + this.parentCompilation + ); + + parentCompilation.children.push(compilation); + + for (const { name, source, info } of compilation.getAssets()) { + parentCompilation.emitAsset(name, source, info); + } + + /** @type {Chunk[]} */ + const entries = []; + + for (const ep of compilation.entrypoints.values()) { + entries.push(...ep.chunks); + } + + compilation.startTime = startTime; + compilation.endTime = Date.now(); + + return finalCallback(null, entries, compilation); + }); + } + + purgeInputFileSystem() { + if (this.inputFileSystem && this.inputFileSystem.purge) { + this.inputFileSystem.purge(); + } + } + + /** + * @param {Compilation} compilation the compilation + * @param {Callback} callback signals when the assets are emitted + * @returns {void} + */ + emitAssets(compilation, callback) { + /** @type {string} */ + let outputPath; + + /** + * @param {Error=} err error + * @returns {void} + */ + const emitFiles = err => { + if (err) return callback(err); + + 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; + let immutable = info.immutable; + const queryStringIdx = targetFile.indexOf("?"); + if (queryStringIdx >= 0) { + targetFile = targetFile.slice(0, queryStringIdx); + // 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)); + } + + /** + * @param {Error=} err error + * @returns {void} + */ + const writeOut = err => { + if (err) return callback(err); + const targetPath = join( + /** @type {OutputFileSystem} */ + (this.outputFileSystem), + outputPath, + targetFile + ); + 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, + writtenTo: new Map() + }; + this._assetEmittingSourceCache.set(source, cacheEntry); + } + + /** @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 + ); + }); + }; + + /** + * @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); + } + } + }; + + /** + * @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 + }); + }; + + /** + * @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(); + } + } + + 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 (/\/|\\/.test(targetFile)) { + const fs = /** @type {OutputFileSystem} */ (this.outputFileSystem); + const dir = dirname(fs, join(fs, outputPath, targetFile)); + mkdirp(fs, dir, writeOut); + } else { + writeOut(); + } + }, + 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 => { + if (err) return callback(err); + + return callback(); + }); + } + ); + }; + + this.hooks.emit.callAsync(compilation, err => { + if (err) return callback(err); + outputPath = compilation.getPath(this.outputPath, {}); + mkdirp( + /** @type {OutputFileSystem} */ (this.outputFileSystem), + outputPath, + emitFiles + ); + }); + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + emitRecords(callback) { + if (this.hooks.emitRecords.isUsed()) { + if (this.recordsOutputPath) { + asyncLib.parallel( + [ + cb => this.hooks.emitRecords.callAsync(cb), + this._emitRecords.bind(this) + ], + err => callback(err) + ); + } else { + this.hooks.emitRecords.callAsync(callback); + } + } else if (this.recordsOutputPath) { + this._emitRecords(callback); + } else { + callback(); + } + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + _emitRecords(callback) { + const writeFile = () => { + /** @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(); + } + mkdirp( + /** @type {OutputFileSystem} */ (this.outputFileSystem), + recordsOutputPathDirectory, + err => { + if (err) return callback(err); + writeFile(); + } + ); + } + + /** + * @param {Callback} 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(err) + ); + } else { + this.records = {}; + this.hooks.readRecords.callAsync(callback); + } + } else if (this.recordsInputPath) { + this._readRecords(callback); + } else { + this.records = {}; + callback(); + } + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + _readRecords(callback) { + if (!this.recordsInputPath) { + this.records = {}; + return callback(); + } + /** @type {InputFileSystem} */ + (this.inputFileSystem).stat(this.recordsInputPath, err => { + // It doesn't exist + // We can ignore this. + if (err) return callback(); + + /** @type {InputFileSystem} */ + (this.inputFileSystem).readFile( + /** @type {string} */ (this.recordsInputPath), + (err, content) => { + if (err) return callback(err); + + try { + this.records = parseJson( + /** @type {Buffer} */ (content).toString("utf-8") + ); + } catch (parseErr) { + return callback( + new Error( + `Cannot parse records: ${/** @type {Error} */ (parseErr).message}` + ) + ); + } + + return callback(); + } + ); + }); + } + + /** + * @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 {WebpackPluginInstance[]=} plugins the plugins to apply + * @returns {Compiler} a child compiler + */ + createChildCompiler( + compilation, + compilerName, + compilerIndex, + outputOptions, + plugins + ) { + 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, + this.root + ); + if (!this.records[relativeCompilerName]) { + this.records[relativeCompilerName] = []; + } + if (this.records[relativeCompilerName][compilerIndex]) { + childCompiler.records = this.records[relativeCompilerName][compilerIndex]; + } else { + this.records[relativeCompilerName].push((childCompiler.records = {})); + } + + childCompiler.parentCompilation = compilation; + childCompiler.root = this.root; + if (Array.isArray(plugins)) { + for (const plugin of plugins) { + 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.slice(); + } + } + + compilation.hooks.childCompiler.call( + childCompiler, + compilerName, + compilerIndex + ); + + return childCompiler; + } + + isChild() { + return Boolean(this.parentCompilation); + } + + /** + * @param {CompilationParams} params the compilation parameters + * @returns {Compilation} compilation + */ + createCompilation(params) { + this._cleanupLastCompilation(); + return (this._lastCompilation = new Compilation(this, params)); + } + + /** + * @param {CompilationParams} params the compilation parameters + * @returns {Compilation} the created compilation + */ + newCompilation(params) { + const compilation = this.createCompilation(params); + compilation.name = this.name; + compilation.records = this.records; + this.hooks.thisCompilation.call(compilation, params); + this.hooks.compilation.call(compilation, params); + return compilation; + } + + createNormalModuleFactory() { + 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, + layers: this.options.experiments.layers + }); + this._lastNormalModuleFactory = normalModuleFactory; + this.hooks.normalModuleFactory.call(normalModuleFactory); + return normalModuleFactory; + } + + createContextModuleFactory() { + const contextModuleFactory = new ContextModuleFactory(this.resolverFactory); + this.hooks.contextModuleFactory.call(contextModuleFactory); + return contextModuleFactory; + } + + newCompilationParams() { + const params = { + normalModuleFactory: this.createNormalModuleFactory(), + contextModuleFactory: this.createContextModuleFactory() + }; + return params; + } + + /** + * @param {RunCallback} callback signals when the compilation finishes + * @returns {void} + */ + compile(callback) { + const params = this.newCompilationParams(); + this.hooks.beforeCompile.callAsync(params, err => { + if (err) return callback(err); + + this.hooks.compile.call(params); + + const compilation = this.newCompilation(params); + + const logger = compilation.getLogger("webpack.Compiler"); + + logger.time("make hook"); + this.hooks.make.callAsync(compilation, err => { + logger.timeEnd("make hook"); + if (err) return callback(err); + + logger.time("finish make hook"); + this.hooks.finishMake.callAsync(compilation, err => { + logger.timeEnd("finish make hook"); + if (err) return callback(err); + + 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); + }); + }); + }); + }); + }); + }); + }); + } + + /** + * @param {RunCallback} 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); + // Get rid of reference to last compilation to avoid leaking memory + // We can't run this._cleanupLastCompilation() as the Stats to this compilation + // might be still in use. We try to get rid of the reference to the cache instead. + this._lastCompilation = undefined; + this._lastNormalModuleFactory = undefined; + this.cache.shutdown(callback); + }); + } +} + +module.exports = Compiler; diff --git a/webpack-lib/lib/ConcatenationScope.js b/webpack-lib/lib/ConcatenationScope.js new file mode 100644 index 00000000000..5c7bb6fd0dc --- /dev/null +++ b/webpack-lib/lib/ConcatenationScope.js @@ -0,0 +1,143 @@ +/* + 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("./Module")} Module */ +/** @typedef {import("./optimize/ConcatenatedModule").ConcatenatedModuleInfo} ConcatenatedModuleInfo */ +/** @typedef {import("./optimize/ConcatenatedModule").ModuleInfo} ModuleInfo */ + +const MODULE_REFERENCE_REGEXP = + /^__WEBPACK_MODULE_REFERENCE__(\d+)_([\da-f]+|ns)(_call)?(_directImport)?(?:_asiSafe(\d))?__$/; + +/** + * @typedef {object} ModuleReferenceOptions + * @property {string[]} ids the properties/exports of the 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 | undefined} asiSafe if the position is ASI safe or unknown + */ + +class ConcatenationScope { + /** + * @param {ModuleInfo[] | Map} modulesMap all module info by module + * @param {ConcatenatedModuleInfo} currentModule the current module info + */ + constructor(modulesMap, currentModule) { + this._currentModule = currentModule; + if (Array.isArray(modulesMap)) { + const map = new Map(); + for (const info of modulesMap) { + map.set(info.module, info); + } + modulesMap = map; + } + this._modulesMap = modulesMap; + } + + /** + * @param {Module} module the referenced module + * @returns {boolean} true, when it's in the scope + */ + isModuleInScope(module) { + return this._modulesMap.has(module); + } + + /** + * @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); + } + } + + /** + * @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); + } + } + + /** + * @param {string} symbol identifier of the export in source code + */ + registerNamespaceExport(symbol) { + this._currentModule.namespaceExportSymbol = symbol; + } + + /** + * @param {Module} module the referenced module + * @param {Partial} options options + * @returns {string} the reference as identifier + */ + createModuleReference( + module, + { ids = undefined, call = false, directImport = false, asiSafe = false } + ) { + const info = /** @type {ModuleInfo} */ (this._modulesMap.get(module)); + const callFlag = call ? "_call" : ""; + const directImportFlag = directImport ? "_directImport" : ""; + const asiSafeFlag = asiSafe + ? "_asiSafe1" + : asiSafe === false + ? "_asiSafe0" + : ""; + const exportData = ids + ? Buffer.from(JSON.stringify(ids), "utf-8").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}${asiSafeFlag}__._`; + } + + /** + * @param {string} name the identifier + * @returns {boolean} true, when it's an module reference + */ + static isModuleReference(name) { + return MODULE_REFERENCE_REGEXP.test(name); + } + + /** + * @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[5]; + return { + index, + ids: + match[2] === "ns" + ? [] + : JSON.parse(Buffer.from(match[2], "hex").toString("utf-8")), + call: Boolean(match[3]), + directImport: Boolean(match[4]), + asiSafe: asiSafe ? asiSafe === "1" : undefined + }; + } +} + +ConcatenationScope.DEFAULT_EXPORT = DEFAULT_EXPORT; +ConcatenationScope.NAMESPACE_OBJECT_EXPORT = NAMESPACE_OBJECT_EXPORT; + +module.exports = ConcatenationScope; diff --git a/webpack-lib/lib/ConcurrentCompilationError.js b/webpack-lib/lib/ConcurrentCompilationError.js new file mode 100644 index 00000000000..3643553f050 --- /dev/null +++ b/webpack-lib/lib/ConcurrentCompilationError.js @@ -0,0 +1,18 @@ +/* + 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."; + } +}; diff --git a/webpack-lib/lib/ConditionalInitFragment.js b/webpack-lib/lib/ConditionalInitFragment.js new file mode 100644 index 00000000000..67351383d95 --- /dev/null +++ b/webpack-lib/lib/ConditionalInitFragment.js @@ -0,0 +1,120 @@ +/* + 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 */ + +/** + * @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" + ); +}; + +/** + * @extends {InitFragment} + */ +class ConditionalInitFragment extends 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 {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; + } + + /** + * @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); + } + + /** + * @param {GenerateContext} context context + * @returns {string|Source=} 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); + } + + /** + * @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/webpack-lib/lib/ConstPlugin.js b/webpack-lib/lib/ConstPlugin.js new file mode 100644 index 00000000000..63ed2622de6 --- /dev/null +++ b/webpack-lib/lib/ConstPlugin.js @@ -0,0 +1,537 @@ +/* + 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 { evaluateToString } = require("./javascript/JavascriptParserHelpers"); +const { parseResource } = require("./util/identifier"); + +/** @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("./Compiler")} Compiler */ +/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ + +/** + * @param {Set} 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 = /** @type {Pattern} */ (stack.pop()); + switch (node.type) { + case "Identifier": + declarations.add(node.name); + break; + case "ArrayPattern": + for (const element of node.elements) { + if (element) { + stack.push(element); + } + } + break; + case "AssignmentPattern": + stack.push(node.left); + break; + case "ObjectPattern": + for (const property of node.properties) { + stack.push(/** @type {AssignmentProperty} */ (property).value); + } + break; + case "RestElement": + stack.push(node.argument); + break; + } + } +}; + +/** + * @param {Statement} branch branch to get hoisted declarations from + * @param {boolean} includeFunctionDeclarations whether to include function declarations + * @returns {Array} hoisted declarations + */ +const getHoistedDeclarations = (branch, includeFunctionDeclarations) => { + const declarations = new Set(); + /** @type {Array} */ + const stack = [branch]; + while (stack.length > 0) { + const node = stack.pop(); + // Some node could be `null` or `undefined`. + if (!node) continue; + switch (node.type) { + // Walk through control statements to look for hoisted declarations. + // Some branches are skipped since they do not allow declarations. + case "BlockStatement": + for (const stmt of node.body) { + stack.push(stmt); + } + break; + case "IfStatement": + stack.push(node.consequent); + stack.push(node.alternate); + break; + case "ForStatement": + stack.push(node.init); + stack.push(node.body); + break; + case "ForInStatement": + case "ForOfStatement": + stack.push(node.left); + stack.push(node.body); + break; + case "DoWhileStatement": + case "WhileStatement": + case "LabeledStatement": + stack.push(node.body); + break; + case "SwitchStatement": + for (const cs of node.cases) { + for (const consequent of cs.consequent) { + stack.push(consequent); + } + } + break; + case "TryStatement": + stack.push(node.block); + if (node.handler) { + stack.push(node.handler.body); + } + stack.push(node.finalizer); + break; + case "FunctionDeclaration": + if (includeFunctionDeclarations) { + collectDeclaration(declarations, /** @type {Identifier} */ (node.id)); + } + break; + case "VariableDeclaration": + if (node.kind === "var") { + for (const decl of node.declarations) { + collectDeclaration(declarations, decl.id); + } + } + break; + } + } + return Array.from(declarations); +}; + +const PLUGIN_NAME = "ConstPlugin"; + +class ConstPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const cachedParseResource = parseResource.bindCache(compiler.root); + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + + compilation.dependencyTemplates.set( + CachedConstDependency, + new CachedConstDependency.Template() + ); + + /** + * @param {JavascriptParser} parser the parser + */ + const handler = parser => { + 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 (!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. + // + const declarations = parser.scope.isStrict + ? getHoistedDeclarations(branchToRemove, false) + : getHoistedDeclarations(branchToRemove, true); + const replacement = + declarations.length > 0 + ? `{ var ${declarations.join(", ")}; }` + : "{}"; + const dep = new ConstDependency( + replacement, + /** @type {Range} */ (branchToRemove.range) + ); + dep.loc = /** @type {SourceLocation} */ (branchToRemove.loc); + parser.state.module.addPresentationalDependency(dep); + } + return bool; + } + }); + parser.hooks.expressionConditionalOperator.tap( + PLUGIN_NAME, + expression => { + if (parser.scope.isAsmJs) return; + const param = parser.evaluateExpression(expression.test); + const bool = param.asBool(); + if (typeof bool === "boolean") { + 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. + // + // Given the following code: + // + // false ? someExpression() : otherExpression(); + // + // the generated code is: + // + // false ? 0 : otherExpression(); + // + const branchToRemove = bool + ? expression.alternate + : expression.consequent; + const dep = new ConstDependency( + "0", + /** @type {Range} */ (branchToRemove.range) + ); + 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(PLUGIN_NAME, expr => { + if (parser.scope.isAsmJs) return; + if (!parser.state.module) return; + return evaluateToString( + cachedParseResource(parser.state.module.resource).query + )(expr); + }); + parser.hooks.expression + .for("__resourceQuery") + .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; + 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_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 = ConstPlugin; diff --git a/webpack-lib/lib/ContextExclusionPlugin.js b/webpack-lib/lib/ContextExclusionPlugin.js new file mode 100644 index 00000000000..8b291072c2b --- /dev/null +++ b/webpack-lib/lib/ContextExclusionPlugin.js @@ -0,0 +1,32 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./ContextModuleFactory")} ContextModuleFactory */ + +class ContextExclusionPlugin { + /** + * @param {RegExp} negativeMatcher Matcher regular expression + */ + constructor(negativeMatcher) { + this.negativeMatcher = negativeMatcher; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.contextModuleFactory.tap("ContextExclusionPlugin", cmf => { + cmf.hooks.contextModuleFiles.tap("ContextExclusionPlugin", files => + files.filter(filePath => !this.negativeMatcher.test(filePath)) + ); + }); + } +} + +module.exports = ContextExclusionPlugin; diff --git a/webpack-lib/lib/ContextModule.js b/webpack-lib/lib/ContextModule.js new file mode 100644 index 00000000000..0ad81bd0b2a --- /dev/null +++ b/webpack-lib/lib/ContextModule.js @@ -0,0 +1,1253 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { OriginalSource, RawSource } = require("webpack-sources"); +const AsyncDependenciesBlock = require("./AsyncDependenciesBlock"); +const { makeWebpackError } = require("./HookWebpackError"); +const Module = require("./Module"); +const { JS_TYPES } = require("./ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const Template = require("./Template"); +const WebpackError = require("./WebpackError"); +const { + compareLocations, + concatComparators, + compareSelect, + keepOriginalOrder, + compareModulesById +} = require("./util/comparators"); +const { + contextify, + parseResource, + makePathsRelative +} = require("./util/identifier"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Chunk").ChunkId} ChunkId */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./Module").BuildMeta} BuildMeta */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @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} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @template T @typedef {import("./util/LazySet")} LazySet */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +/** @typedef {"sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"} ContextMode Context mode */ + +/** + * @typedef {object} ContextOptions + * @property {ContextMode} mode + * @property {boolean} recursive + * @property {RegExp} regExp + * @property {("strict" | boolean)=} namespaceObject + * @property {string=} addon + * @property {(string | null)=} chunkName + * @property {(RegExp | null)=} include + * @property {(RegExp | null)=} exclude + * @property {RawChunkGroupOptions=} groupOptions + * @property {string=} typePrefix + * @property {string=} category + * @property {(string[][] | null)=} referencedExports exports referenced from modules (won't be mangled) + * @property {string=} layer + * @property {ImportAttributes=} attributes + */ + +/** + * @typedef {object} ContextModuleOptionsExtras + * @property {false|string|string[]} resource + * @property {string=} resourceQuery + * @property {string=} resourceFragment + * @property {TODO} resolveOptions + */ + +/** @typedef {ContextOptions & ContextModuleOptionsExtras} ContextModuleOptions */ + +/** + * @callback ResolveDependenciesCallback + * @param {Error | null} err + * @param {ContextElementDependency[]=} dependencies + */ + +/** + * @callback ResolveDependencies + * @param {InputFileSystem} fs + * @param {ContextModuleOptions} options + * @param {ResolveDependenciesCallback} callback + */ + +/** @typedef {1 | 3 | 7 | 9} FakeMapType */ + +/** @typedef {Record} FakeMap */ + +const SNAPSHOT_OPTIONS = { timestamp: true }; + +class ContextModule extends Module { + /** + * @param {ResolveDependencies} resolveDependencies function to get dependencies in this context + * @param {ContextModuleOptions} options options object + */ + constructor(resolveDependencies, options) { + 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 { + super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, undefined, options.layer); + /** @type {ContextModuleOptions} */ + this.options = { + ...options, + resource: options.resource, + resourceQuery: options.resourceQuery || "", + resourceFragment: options.resourceFragment || "" + }; + } + + // Info from Factory + /** @type {ResolveDependencies | undefined} */ + this.resolveDependencies = resolveDependencies; + if (options && options.resolveOptions !== undefined) { + this.resolveOptions = options.resolveOptions; + } + + if (options && typeof options.mode !== "string") { + throw new Error("options.mode is a required option"); + } + + this._identifier = this._createIdentifier(); + this._forceBuild = true; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_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) { + const m = /** @type {ContextModule} */ (module); + this.resolveDependencies = m.resolveDependencies; + this.options = m.options; + } + + /** + * Assuming this module is in the cache. Remove internal references to allow freeing some memory. + */ + cleanupForCache() { + super.cleanupForCache(); + this.resolveDependencies = undefined; + } + + /** + * @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 || + (typeof this.options.resource === "string" || + this.options.resource === false + ? `${this.options.resource}` + : this.options.resource.join("|")); + if (this.options.resourceQuery) { + identifier += `|${this.options.resourceQuery}`; + } + if (this.options.resourceFragment) { + identifier += `|${this.options.resourceFragment}`; + } + if (this.options.mode) { + identifier += `|${this.options.mode}`; + } + if (!this.options.recursive) { + identifier += "|nonrecursive"; + } + if (this.options.addon) { + identifier += `|${this.options.addon}`; + } + if (this.options.regExp) { + identifier += `|${this._prettyRegExp(this.options.regExp, false)}`; + } + if (this.options.include) { + identifier += `|include: ${this._prettyRegExp( + this.options.include, + false + )}`; + } + if (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( + this.options.groupOptions + )}`; + } + if (this.options.namespaceObject === "strict") { + identifier += "|strict namespace object"; + } else if (this.options.namespaceObject) { + identifier += "|namespace object"; + } + if (this.layer) { + identifier += `|layer: ${this.layer}`; + } + + return identifier; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this._identifier; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + 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}`; + } + if (this.options.mode) { + identifier += ` ${this.options.mode}`; + } + if (!this.options.recursive) { + identifier += " nonrecursive"; + } + if (this.options.addon) { + identifier += ` ${requestShortener.shorten(this.options.addon)}`; + } + if (this.options.regExp) { + identifier += ` ${this._prettyRegExp(this.options.regExp)}`; + } + if (this.options.include) { + identifier += ` include: ${this._prettyRegExp(this.options.include)}`; + } + if (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[/** @type {keyof RawChunkGroupOptions} */ (key)] + }`; + } + } + if (this.options.namespaceObject === "strict") { + identifier += " strict namespace object"; + } else if (this.options.namespaceObject) { + identifier += " namespace object"; + } + + return identifier; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + 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}`; + } + if (this.options.recursive) { + identifier += " recursive"; + } + if (this.options.addon) { + identifier += ` ${contextify( + options.context, + this.options.addon, + options.associatedObjectForCache + )}`; + } + if (this.options.regExp) { + identifier += ` ${this._prettyRegExp(this.options.regExp)}`; + } + if (this.options.include) { + identifier += ` include: ${this._prettyRegExp(this.options.include)}`; + } + if (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; + } + + /** + * @returns {void} + */ + invalidateBuild() { + this._forceBuild = true; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} 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 {BuildInfo} */ (this.buildInfo); + + // always build when we have no snapshot and context + if (!buildInfo.snapshot) + return callback(null, Boolean(this.context || this.options.resource)); + + fileSystemInfo.checkSnapshotValid(buildInfo.snapshot, (err, valid) => { + callback(err, !valid); + }); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this._forceBuild = false; + /** @type {BuildMeta} */ + this.buildMeta = { + exportsType: "default", + defaultObject: "redirect-warn" + }; + this.buildInfo = { + snapshot: undefined + }; + 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 + if (!dependencies) { + callback(); + return; + } + + // enhance dependencies with meta info + for (const dep of dependencies) { + 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 + // just add all dependencies and continue + this.dependencies = dependencies; + } else if (this.options.mode === "lazy-once") { + // 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({ + ...this.options.groupOptions, + name: this.options.chunkName + }); + for (const dep of dependencies) { + block.addDependency(dep); + } + this.addBlock(block); + } + } else if ( + this.options.mode === "weak" || + this.options.mode === "async-weak" + ) { + // we mark all dependencies as weak + for (const dep of dependencies) { + dep.weak = true; + } + this.dependencies = dependencies; + } else if (this.options.mode === "lazy") { + // if we are lazy create a new async dependency block per dependency + // and add all blocks to this context + let index = 0; + for (const dep of dependencies) { + let chunkName = this.options.chunkName; + if (chunkName) { + if (!/\[(index|request)\]/.test(chunkName)) { + chunkName += "[index]"; + } + chunkName = chunkName.replace(/\[index\]/g, `${index++}`); + chunkName = chunkName.replace( + /\[request\]/g, + Template.toPath(dep.userRequest) + ); + } + const block = new AsyncDependenciesBlock( + { + ...this.options.groupOptions, + name: chunkName + }, + dep.loc, + dep.userRequest + ); + block.addDependency(dep); + this.addBlock(block); + } + } else { + callback( + new WebpackError(`Unsupported mode "${this.options.mode}" in context`) + ); + return; + } + if (!this.context && !this.options.resource) return callback(); + + compilation.fileSystemInfo.createSnapshot( + startTime, + null, + this.context + ? [this.context] + : typeof this.options.resource === "string" + ? [this.options.resource] + : /** @type {string[]} */ (this.options.resource), + null, + SNAPSHOT_OPTIONS, + (err, snapshot) => { + if (err) return callback(err); + /** @type {BuildInfo} */ + (this.buildInfo).snapshot = snapshot; + callback(); + } + ); + }); + } + + /** + * @param {LazySet} fileDependencies set where file dependencies are added to + * @param {LazySet} contextDependencies set where context dependencies are added to + * @param {LazySet} missingDependencies set where missing dependencies are added to + * @param {LazySet} 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 {Map} map with user requests + */ + getUserRequestMap(dependencies, chunkGraph) { + const moduleGraph = chunkGraph.moduleGraph; + // 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 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; + }); + const map = Object.create(null); + for (const dep of sortedDependencies) { + const module = /** @type {Module} */ (moduleGraph.getModule(dep)); + map[dep.userRequest] = chunkGraph.getModuleId(module); + } + return map; + } + + /** + * @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 + // 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 {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 (hasType === 1) { + return 9; + } + if (hasType === 2) { + return 7; + } + if (hasType === 4) { + return 1; + } + if (hasType === 8) { + return 3; + } + if (hasType === 0) { + return 9; + } + return fakeMap; + } + + /** + * @param {FakeMap | FakeMapType} fakeMap fake map + * @returns {string} fake map init statement + */ + getFakeMapInitStatement(fakeMap) { + return typeof fakeMap === "object" + ? `var fakeMap = ${JSON.stringify(fakeMap, null, "\t")};` + : ""; + } + + /** + * @param {FakeMapType} type type + * @param {boolean=} asyncModule is async module + * @returns {string} return result + */ + getReturn(type, asyncModule) { + if (type === 9) { + return `${RuntimeGlobals.require}(id)`; + } + return `${RuntimeGlobals.createFakeNamespaceObject}(id, ${type}${ + asyncModule ? " | 16" : "" + })`; + } + + /** + * @param {FakeMap | FakeMapType} fakeMap fake map + * @param {boolean=} asyncModule us async module + * @param {string=} fakeMapDataExpression fake map data expression + * @returns {string} module object source + */ + getReturnModuleObjectSource( + fakeMap, + asyncModule, + fakeMapDataExpression = "fakeMap[id]" + ) { + if (typeof fakeMap === "number") { + return `return ${this.getReturn(fakeMap, asyncModule)};`; + } + return `return ${ + RuntimeGlobals.createFakeNamespaceObject + }(id, ${fakeMapDataExpression}${asyncModule ? " | 16" : ""})`; + } + + /** + * @param {Dependency[]} dependencies dependencies + * @param {ModuleId} id module id + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {string} source code + */ + getSyncSource(dependencies, id, chunkGraph) { + const map = this.getUserRequestMap(dependencies, chunkGraph); + const fakeMap = this.getFakeMap(dependencies, chunkGraph); + const returnModuleObject = this.getReturnModuleObjectSource(fakeMap); + + return `var map = ${JSON.stringify(map, null, "\t")}; +${this.getFakeMapInitStatement(fakeMap)} + +function webpackContext(req) { + var id = webpackContextResolve(req); + ${returnModuleObject} +} +function webpackContextResolve(req) { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; +} +webpackContext.keys = function webpackContextKeys() { + return Object.keys(map); +}; +webpackContext.resolve = webpackContextResolve; +module.exports = webpackContext; +webpackContext.id = ${JSON.stringify(id)};`; + } + + /** + * @param {Dependency[]} dependencies dependencies + * @param {ModuleId} id module id + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {string} source code + */ + getWeakSyncSource(dependencies, id, chunkGraph) { + const map = this.getUserRequestMap(dependencies, chunkGraph); + const fakeMap = this.getFakeMap(dependencies, chunkGraph); + const returnModuleObject = this.getReturnModuleObjectSource(fakeMap); + + return `var map = ${JSON.stringify(map, null, "\t")}; +${this.getFakeMapInitStatement(fakeMap)} + +function webpackContext(req) { + var id = webpackContextResolve(req); + if(!${RuntimeGlobals.moduleFactories}[id]) { + var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + ${returnModuleObject} +} +function webpackContextResolve(req) { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; +} +webpackContext.keys = function webpackContextKeys() { + return Object.keys(map); +}; +webpackContext.resolve = webpackContextResolve; +webpackContext.id = ${JSON.stringify(id)}; +module.exports = webpackContext;`; + } + + /** + * @param {Dependency[]} dependencies dependencies + * @param {ModuleId} id module id + * @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, { chunkGraph, runtimeTemplate }) { + const arrow = runtimeTemplate.supportsArrowFunction(); + const map = this.getUserRequestMap(dependencies, chunkGraph); + const fakeMap = this.getFakeMap(dependencies, chunkGraph); + const returnModuleObject = this.getReturnModuleObjectSource(fakeMap, true); + + return `var map = ${JSON.stringify(map, null, "\t")}; +${this.getFakeMapInitStatement(fakeMap)} + +function webpackAsyncContext(req) { + return webpackAsyncContextResolve(req).then(${ + arrow ? "id =>" : "function(id)" + } { + if(!${RuntimeGlobals.moduleFactories}[id]) { + var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + ${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(${arrow ? "() =>" : "function()"} { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; + }); +} +webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( + "Object.keys(map)" + )}; +webpackAsyncContext.resolve = webpackAsyncContextResolve; +webpackAsyncContext.id = ${JSON.stringify(id)}; +module.exports = webpackAsyncContext;`; + } + + /** + * @param {Dependency[]} dependencies dependencies + * @param {ModuleId} id module id + * @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, { chunkGraph, runtimeTemplate }) { + const arrow = runtimeTemplate.supportsArrowFunction(); + const map = this.getUserRequestMap(dependencies, chunkGraph); + const fakeMap = this.getFakeMap(dependencies, chunkGraph); + const thenFunction = + fakeMap !== 9 + ? `${arrow ? "id =>" : "function(id)"} { + ${this.getReturnModuleObjectSource(fakeMap, true)} + }` + : RuntimeGlobals.require; + return `var map = ${JSON.stringify(map, null, "\t")}; +${this.getFakeMapInitStatement(fakeMap)} + +function webpackAsyncContext(req) { + return webpackAsyncContextResolve(req).then(${thenFunction}); +} +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(${arrow ? "() =>" : "function()"} { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; + }); +} +webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( + "Object.keys(map)" + )}; +webpackAsyncContext.resolve = webpackAsyncContextResolve; +webpackAsyncContext.id = ${JSON.stringify(id)}; +module.exports = webpackAsyncContext;`; + } + + /** + * @param {AsyncDependenciesBlock} block block + * @param {Dependency[]} dependencies dependencies + * @param {ModuleId} id module id + * @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, { runtimeTemplate, chunkGraph }) { + const promise = runtimeTemplate.blockPromise({ + chunkGraph, + block, + message: "lazy-once context", + runtimeRequirements: new Set() + }); + const arrow = runtimeTemplate.supportsArrowFunction(); + const map = this.getUserRequestMap(dependencies, chunkGraph); + const fakeMap = this.getFakeMap(dependencies, chunkGraph); + const thenFunction = + fakeMap !== 9 + ? `${arrow ? "id =>" : "function(id)"} { + ${this.getReturnModuleObjectSource(fakeMap, true)}; + }` + : RuntimeGlobals.require; + + return `var map = ${JSON.stringify(map, null, "\t")}; +${this.getFakeMapInitStatement(fakeMap)} + +function webpackAsyncContext(req) { + return webpackAsyncContextResolve(req).then(${thenFunction}); +} +function webpackAsyncContextResolve(req) { + return ${promise}.then(${arrow ? "() =>" : "function()"} { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; + }); +} +webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( + "Object.keys(map)" + )}; +webpackAsyncContext.resolve = webpackAsyncContextResolve; +webpackAsyncContext.id = ${JSON.stringify(id)}; +module.exports = webpackAsyncContext;`; + } + + /** + * @param {AsyncDependenciesBlock[]} blocks blocks + * @param {ModuleId} id module id + * @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, { chunkGraph, runtimeTemplate }) { + const moduleGraph = chunkGraph.moduleGraph; + const arrow = runtimeTemplate.supportsArrowFunction(); + let hasMultipleOrNoChunks = false; + let hasNoChunk = 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}} 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 + }; + }) + .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 shortMode = hasNoChunk && !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 | ChunkId)[]} */ + const arrayStart = [moduleId]; + if (hasFakeMap) { + arrayStart.push(fakeMap[moduleId]); + } + map[item.userRequest] = arrayStart.concat( + /** @type {Chunk[]} */ + (item.chunks).map(chunk => /** @type {ChunkId} */ (chunk.id)) + ); + } + } + + const chunksStartPosition = hasFakeMap ? 2 : 1; + const requestPrefix = hasNoChunk + ? "Promise.resolve()" + : hasMultipleOrNoChunks + ? `Promise.all(ids.slice(${chunksStartPosition}).map(${RuntimeGlobals.ensureChunk}))` + : `${RuntimeGlobals.ensureChunk}(ids[${chunksStartPosition}])`; + const returnModuleObject = this.getReturnModuleObjectSource( + fakeMap, + true, + shortMode ? "invalid" : "ids[1]" + ); + + const webpackAsyncContext = + requestPrefix === "Promise.resolve()" + ? ` +function webpackAsyncContext(req) { + return Promise.resolve().then(${arrow ? "() =>" : "function()"} { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + + ${shortMode ? "var id = map[req];" : "var ids = map[req], id = ids[0];"} + ${returnModuleObject} + }); +}` + : `function webpackAsyncContext(req) { + if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { + return Promise.resolve().then(${arrow ? "() =>" : "function()"} { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + }); + } + + var ids = map[req], id = ids[0]; + return ${requestPrefix}.then(${arrow ? "() =>" : "function()"} { + ${returnModuleObject} + }); +}`; + + return `var map = ${JSON.stringify(map, null, "\t")}; +${webpackAsyncContext} +webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( + "Object.keys(map)" + )}; +webpackAsyncContext.id = ${JSON.stringify(id)}; +module.exports = webpackAsyncContext;`; + } + + /** + * @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 + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; +} +webpackEmptyContext.keys = ${runtimeTemplate.returningFunction("[]")}; +webpackEmptyContext.resolve = webpackEmptyContext; +webpackEmptyContext.id = ${JSON.stringify(id)}; +module.exports = webpackEmptyContext;`; + } + + /** + * @param {ModuleId} id module id + * @param {RuntimeTemplate} runtimeTemplate runtime template + * @returns {string} source for empty async context + */ + getSourceForEmptyAsyncContext(id, runtimeTemplate) { + const arrow = runtimeTemplate.supportsArrowFunction(); + 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(${arrow ? "() =>" : "function()"} { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + }); +} +webpackEmptyAsyncContext.keys = ${runtimeTemplate.returningFunction("[]")}; +webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext; +webpackEmptyAsyncContext.id = ${JSON.stringify(id)}; +module.exports = webpackEmptyAsyncContext;`; + } + + /** + * @param {string} asyncMode module mode + * @param {CodeGenerationContext} context context info + * @returns {string} the source code + */ + getSourceString(asyncMode, { runtimeTemplate, chunkGraph }) { + const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(this)); + if (asyncMode === "lazy") { + if (this.blocks && this.blocks.length > 0) { + return this.getLazySource(this.blocks, id, { + runtimeTemplate, + chunkGraph + }); + } + return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); + } + if (asyncMode === "eager") { + if (this.dependencies && this.dependencies.length > 0) { + return this.getEagerSource(this.dependencies, id, { + chunkGraph, + runtimeTemplate + }); + } + return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); + } + if (asyncMode === "lazy-once") { + const block = this.blocks[0]; + if (block) { + return this.getLazyOnceSource(block, block.dependencies, id, { + runtimeTemplate, + chunkGraph + }); + } + return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); + } + if (asyncMode === "async-weak") { + if (this.dependencies && this.dependencies.length > 0) { + return this.getAsyncWeakSource(this.dependencies, id, { + chunkGraph, + runtimeTemplate + }); + } + return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); + } + if ( + asyncMode === "weak" && + this.dependencies && + this.dependencies.length > 0 + ) { + return this.getWeakSyncSource(this.dependencies, id, chunkGraph); + } + if (this.dependencies && this.dependencies.length > 0) { + return this.getSyncSource(this.dependencies, id, chunkGraph); + } + return this.getSourceForEmptyContext(id, runtimeTemplate); + } + + /** + * @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); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + const { chunkGraph, compilation } = context; + const sources = new Map(); + sources.set( + "javascript", + this.getSource( + this.getSourceString(this.options.mode, context), + compilation + ) + ); + const set = new Set(); + const allDeps = + this.dependencies.length > 0 + ? /** @type {ContextElementDependency[]} */ (this.dependencies).slice() + : []; + 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); + } + } + return { + sources, + runtimeRequirements: set + }; + } + + /** + * @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 + let size = 160; + + // if we don't have dependencies we stop here. + for (const dependency of this.dependencies) { + const element = /** @type {ContextElementDependency} */ (dependency); + size += 5 + element.userRequest.length; + } + return size; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this._identifier); + write(this._forceBuild); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this._identifier = read(); + this._forceBuild = read(); + super.deserialize(context); + } +} + +makeSerializable(ContextModule, "webpack/lib/ContextModule"); + +module.exports = ContextModule; diff --git a/webpack-lib/lib/ContextModuleFactory.js b/webpack-lib/lib/ContextModuleFactory.js new file mode 100644 index 00000000000..23da02663e2 --- /dev/null +++ b/webpack-lib/lib/ContextModuleFactory.js @@ -0,0 +1,481 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +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("./ContextModule").ContextModuleOptions} ContextModuleOptions */ +/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./ResolverFactory")} ResolverFactory */ +/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */ +/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */ +/** + * @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 */ + +const EMPTY_RESOLVE_OPTIONS = {}; + +module.exports = class ContextModuleFactory extends ModuleFactory { + /** + * @param {ResolverFactory} resolverFactory resolverFactory + */ + constructor(resolverFactory) { + super(); + /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */ + const alternativeRequests = new AsyncSeriesWaterfallHook([ + "modules", + "options" + ]); + this.hooks = Object.freeze({ + /** @type {AsyncSeriesWaterfallHook<[TODO]>} */ + beforeResolve: new AsyncSeriesWaterfallHook(["data"]), + /** @type {AsyncSeriesWaterfallHook<[TODO]>} */ + afterResolve: new AsyncSeriesWaterfallHook(["data"]), + /** @type {SyncWaterfallHook<[string[]]>} */ + contextModuleFiles: new SyncWaterfallHook(["files"]), + /** @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 + }); + this.resolverFactory = resolverFactory; + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const context = data.context; + const dependencies = data.dependencies; + const resolveOptions = data.resolveOptions; + const dependency = /** @type {ContextDependency} */ (dependencies[0]); + const fileDependencies = new LazySet(); + const missingDependencies = new LazySet(); + const contextDependencies = new LazySet(); + this.hooks.beforeResolve.callAsync( + { + context, + dependencies, + layer: data.contextInfo.issuerLayer, + resolveOptions, + fileDependencies, + missingDependencies, + contextDependencies, + ...dependency.options + }, + (err, beforeResolveResult) => { + if (err) { + return callback(err, { + fileDependencies, + missingDependencies, + contextDependencies + }); + } + + // Ignored + if (!beforeResolveResult) { + return callback(null, { + fileDependencies, + missingDependencies, + contextDependencies + }); + } + + const context = beforeResolveResult.context; + const request = beforeResolveResult.request; + const resolveOptions = beforeResolveResult.resolveOptions; + + let loaders; + let resource; + let loadersPrefix = ""; + const idx = request.lastIndexOf("!"); + if (idx >= 0) { + let loadersRequest = request.slice(0, idx + 1); + let i; + for ( + i = 0; + i < loadersRequest.length && loadersRequest[i] === "!"; + i++ + ) { + loadersPrefix += "!"; + } + loadersRequest = loadersRequest + .slice(i) + .replace(/!+$/, "") + .replace(/!!+/g, "!"); + loaders = loadersRequest === "" ? [] : loadersRequest.split("!"); + resource = request.slice(idx + 1); + } else { + loaders = []; + resource = request; + } + + const contextResolver = this.resolverFactory.get( + "context", + dependencies.length > 0 + ? cachedSetProperty( + resolveOptions || EMPTY_RESOLVE_OPTIONS, + "dependencyType", + dependencies[0].category + ) + : resolveOptions + ); + const loaderResolver = this.resolverFactory.get("loader"); + + asyncLib.parallel( + [ + callback => { + const results = /** @type ResolveRequest[] */ ([]); + /** + * @param {ResolveRequest} obj obj + * @returns {void} + */ + const yield_ = obj => { + results.push(obj); + }; + + contextResolver.resolve( + {}, + context, + resource, + { + fileDependencies, + missingDependencies, + contextDependencies, + yield: yield_ + }, + err => { + if (err) return callback(err); + callback(null, results); + } + ); + }, + callback => { + asyncLib.map( + loaders, + (loader, callback) => { + loaderResolver.resolve( + {}, + context, + loader, + { + fileDependencies, + missingDependencies, + contextDependencies + }, + (err, result) => { + if (err) return callback(err); + callback(null, /** @type {string} */ (result)); + } + ); + }, + callback + ); + } + ], + (err, result) => { + 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( + { + addon: + loadersPrefix + + loaderResult.join("!") + + (loaderResult.length > 0 ? "!" : ""), + resource: + contextResult.length > 1 + ? contextResult.map(r => r.path) + : contextResult[0].path, + resolveDependencies: this.resolveDependencies.bind(this), + resourceQuery: contextResult[0].query, + resourceFragment: contextResult[0].fragment, + ...beforeResolveResult + }, + (err, result) => { + if (err) { + return callback(err, { + fileDependencies, + missingDependencies, + contextDependencies + }); + } + + // Ignored + if (!result) { + return callback(null, { + fileDependencies, + missingDependencies, + contextDependencies + }); + } + + return callback(null, { + module: new ContextModule(result.resolveDependencies, result), + fileDependencies, + missingDependencies, + contextDependencies + }); + } + ); + } + ); + } + ); + } + + /** + * @param {InputFileSystem} fs file system + * @param {ContextModuleOptions} options options + * @param {ResolveDependenciesCallback} callback callback function + * @returns {void} + */ + resolveDependencies(fs, options, callback) { + const cmf = this; + const { + resource, + resourceQuery, + resourceFragment, + recursive, + regExp, + include, + exclude, + referencedExports, + category, + typePrefix, + attributes + } = options; + if (!regExp || !resource) return callback(null, []); + + /** + * @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 + ); + }); + }; + + /** + * @param {string} ctx context + * @param {string} directory directory + * @param {function(string, string, function(): void): void} addSubDirectory addSubDirectoryFn + * @param {ResolveDependenciesCallback} callback callback + */ + const addDirectory = (ctx, directory, addSubDirectory, callback) => { + fs.readdir(directory, (err, files) => { + if (err) return callback(err); + const processedFiles = cmf.hooks.contextModuleFiles.call( + /** @type {string[]} */ (files).map(file => file.normalize("NFC")) + ); + if (!processedFiles || processedFiles.length === 0) + return callback(null, []); + asyncLib.map( + processedFiles.filter(p => p.indexOf(".") !== 0), + (segment, callback) => { + const subResource = join(fs, directory, segment); + + if (!exclude || !subResource.match(exclude)) { + 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(); + } + return callback(err); + } + + const stat = /** @type {IStats} */ (_stat); + + if (stat.isDirectory()) { + if (!recursive) return callback(); + addSubDirectory(ctx, subResource, callback); + } else if ( + stat.isFile() && + (!include || subResource.match(include)) + ) { + /** @type {{ context: string, request: string }} */ + const obj = { + context: ctx, + request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}` + }; + + this.hooks.alternativeRequests.callAsync( + [obj], + options, + (err, alternatives) => { + if (err) return callback(err); + callback( + null, + /** @type {ContextAlternativeRequest[]} */ + (alternatives) + .filter(obj => + regExp.test(/** @type {string} */ (obj.request)) + ) + .map(obj => { + const dep = new ContextElementDependency( + `${obj.request}${resourceQuery}${resourceFragment}`, + obj.request, + typePrefix, + /** @type {string} */ + (category), + referencedExports, + /** @type {TODO} */ + (obj.context), + attributes + ); + dep.optional = true; + return dep; + }) + ); + } + ); + } else { + callback(); + } + }); + } else { + callback(); + } + }, + (err, result) => { + if (err) return callback(err); + + if (!result) return callback(null, []); + + const flattenedResult = []; + + for (const item of result) { + if (item) flattenedResult.push(...item); + } + + callback(null, flattenedResult); + } + ); + }); + }; + + /** + * @param {string} ctx context + * @param {string} dir dir + * @param {ResolveDependenciesCallback} callback callback + * @returns {void} + */ + const addSubDirectory = (ctx, dir, callback) => + addDirectory(ctx, dir, addSubDirectory, callback); + + /** + * @param {string} resource resource + * @param {ResolveDependenciesCallback} callback callback + */ + const visitResource = (resource, callback) => { + if (typeof fs.realpath === "function") { + addDirectoryChecked(resource, resource, 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); + }); + } + } +}; diff --git a/webpack-lib/lib/ContextReplacementPlugin.js b/webpack-lib/lib/ContextReplacementPlugin.js new file mode 100644 index 00000000000..ac425f31321 --- /dev/null +++ b/webpack-lib/lib/ContextReplacementPlugin.js @@ -0,0 +1,172 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ContextElementDependency = require("./dependencies/ContextElementDependency"); +const { join } = require("./util/fs"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +class ContextReplacementPlugin { + /** + * @param {RegExp} resourceRegExp A regular expression that determines which files will be selected + * @param {TODO=} newContentResource A new resource to replace the match + * @param {TODO=} newContentRecursive If true, all subdirectories are searched for matches + * @param {RegExp=} newContentRegExp A regular expression that determines which files will be selected + */ + constructor( + resourceRegExp, + newContentResource, + newContentRecursive, + newContentRegExp + ) { + this.resourceRegExp = resourceRegExp; + + if (typeof newContentResource === "function") { + this.newContentCallback = newContentResource; + } else if ( + typeof newContentResource === "string" && + typeof newContentRecursive === "object" + ) { + this.newContentResource = newContentResource; + this.newContentCreateContextMap = (fs, callback) => { + callback(null, newContentRecursive); + }; + } else if ( + typeof newContentResource === "string" && + typeof newContentRecursive === "function" + ) { + this.newContentResource = newContentResource; + this.newContentCreateContextMap = newContentRecursive; + } else { + if (typeof newContentResource !== "string") { + newContentRegExp = newContentRecursive; + newContentRecursive = newContentResource; + newContentResource = undefined; + } + if (typeof newContentRecursive !== "boolean") { + newContentRegExp = newContentRecursive; + newContentRecursive = undefined; + } + this.newContentResource = newContentResource; + this.newContentRecursive = newContentRecursive; + this.newContentRegExp = newContentRegExp; + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const resourceRegExp = this.resourceRegExp; + const newContentCallback = this.newContentCallback; + const newContentResource = this.newContentResource; + const newContentRecursive = this.newContentRecursive; + const newContentRegExp = this.newContentRegExp; + const newContentCreateContextMap = this.newContentCreateContextMap; + + compiler.hooks.contextModuleFactory.tap("ContextReplacementPlugin", cmf => { + cmf.hooks.beforeResolve.tap("ContextReplacementPlugin", result => { + if (!result) return; + if (resourceRegExp.test(result.request)) { + if (newContentResource !== undefined) { + result.request = newContentResource; + } + if (newContentRecursive !== undefined) { + result.recursive = newContentRecursive; + } + if (newContentRegExp !== undefined) { + result.regExp = newContentRegExp; + } + if (typeof newContentCallback === "function") { + newContentCallback(result); + } else { + for (const d of result.dependencies) { + if (d.critical) d.critical = false; + } + } + } + return result; + }); + cmf.hooks.afterResolve.tap("ContextReplacementPlugin", result => { + if (!result) return; + if (resourceRegExp.test(result.resource)) { + if (newContentResource !== undefined) { + if ( + newContentResource.startsWith("/") || + (newContentResource.length > 1 && newContentResource[1] === ":") + ) { + result.resource = newContentResource; + } else { + result.resource = join( + /** @type {InputFileSystem} */ (compiler.inputFileSystem), + result.resource, + newContentResource + ); + } + } + if (newContentRecursive !== undefined) { + result.recursive = newContentRecursive; + } + if (newContentRegExp !== undefined) { + result.regExp = newContentRegExp; + } + if (typeof newContentCreateContextMap === "function") { + result.resolveDependencies = + createResolveDependenciesFromContextMap( + newContentCreateContextMap + ); + } + if (typeof newContentCallback === "function") { + const origResource = result.resource; + newContentCallback(result); + if ( + result.resource !== origResource && + !result.resource.startsWith("/") && + (result.resource.length <= 1 || result.resource[1] !== ":") + ) { + // When the function changed it to an relative path + result.resource = join( + /** @type {InputFileSystem} */ (compiler.inputFileSystem), + origResource, + result.resource + ); + } + } else { + for (const d of result.dependencies) { + if (d.critical) d.critical = false; + } + } + } + return result; + }); + }); + } +} + +const createResolveDependenciesFromContextMap = createContextMap => { + const resolveDependenciesFromContextMap = (fs, options, callback) => { + createContextMap(fs, (err, map) => { + if (err) return callback(err); + const dependencies = Object.keys(map).map( + key => + new ContextElementDependency( + map[key] + options.resourceQuery + options.resourceFragment, + key, + options.category, + options.referencedExports + ) + ); + callback(null, dependencies); + }); + }; + return resolveDependenciesFromContextMap; +}; + +module.exports = ContextReplacementPlugin; diff --git a/webpack-lib/lib/CssModule.js b/webpack-lib/lib/CssModule.js new file mode 100644 index 00000000000..c8556627e7e --- /dev/null +++ b/webpack-lib/lib/CssModule.js @@ -0,0 +1,175 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +const NormalModule = require("./NormalModule"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./NormalModule").NormalModuleCreateData} NormalModuleCreateData */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +/** @typedef {string | undefined} CssLayer */ +/** @typedef {string | undefined} Supports */ +/** @typedef {string | undefined} Media */ +/** @typedef {[CssLayer, Supports, Media]} InheritanceItem */ +/** @typedef {Array} Inheritance */ + +/** @typedef {NormalModuleCreateData & { cssLayer: CssLayer, supports: Supports, media: Media, inheritance: Inheritance }} CSSModuleCreateData */ + +class CssModule extends NormalModule { + /** + * @param {CSSModuleCreateData} options options object + */ + constructor(options) { + super(options); + + // Avoid override `layer` for `Module` class, because it is a feature to run module in specific layer + this.cssLayer = options.cssLayer; + this.supports = options.supports; + this.media = options.media; + this.inheritance = options.inheritance; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + let identifier = super.identifier(); + + if (this.cssLayer) { + identifier += `|${this.cssLayer}`; + } + + if (this.supports) { + identifier += `|${this.supports}`; + } + + if (this.media) { + identifier += `|${this.media}`; + } + + if (this.inheritance) { + const inheritance = this.inheritance.map( + (item, index) => + `inheritance_${index}|${item[0] || ""}|${item[1] || ""}|${ + item[2] || "" + }` + ); + + identifier += `|${inheritance.join("|")}`; + } + + // We generate extra code for HMR, so we need to invalidate the module + if (this.hot) { + identifier += `|${this.hot}`; + } + + return identifier; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + const readableIdentifier = super.readableIdentifier(requestShortener); + + let identifier = `css ${readableIdentifier}`; + + if (this.cssLayer) { + identifier += ` (layer: ${this.cssLayer})`; + } + + if (this.supports) { + identifier += ` (supports: ${this.supports})`; + } + + if (this.media) { + identifier += ` (media: ${this.media})`; + } + + return identifier; + } + + /** + * 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) { + super.updateCacheModule(module); + const m = /** @type {CssModule} */ (module); + this.cssLayer = m.cssLayer; + this.supports = m.supports; + this.media = m.media; + this.inheritance = m.inheritance; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.cssLayer); + write(this.supports); + write(this.media); + write(this.inheritance); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {CssModule} the deserialized object + */ + static deserialize(context) { + const obj = new CssModule({ + // 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), + cssLayer: /** @type {EXPECTED_ANY} */ (null), + supports: /** @type {EXPECTED_ANY} */ (null), + media: /** @type {EXPECTED_ANY} */ (null), + inheritance: /** @type {EXPECTED_ANY} */ (null) + }); + obj.deserialize(context); + return obj; + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {TODO} Module + */ + deserialize(context) { + const { read } = context; + this.cssLayer = read(); + this.supports = read(); + this.media = read(); + this.inheritance = read(); + super.deserialize(context); + } +} + +makeSerializable(CssModule, "webpack/lib/CssModule"); + +module.exports = CssModule; diff --git a/webpack-lib/lib/DefinePlugin.js b/webpack-lib/lib/DefinePlugin.js new file mode 100644 index 00000000000..1c1cf7aa2e8 --- /dev/null +++ b/webpack-lib/lib/DefinePlugin.js @@ -0,0 +1,719 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM, + JAVASCRIPT_MODULE_TYPE_DYNAMIC +} = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const WebpackError = require("./WebpackError"); +const ConstDependency = require("./dependencies/ConstDependency"); +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").ValueCacheVersions} ValueCacheVersions */ +/** @typedef {import("./NormalModule")} NormalModule */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ +/** @typedef {import("./logging/Logger").Logger} Logger */ +/** @typedef {import("./util/createHash").Algorithm} Algorithm */ + +/** @typedef {null|undefined|RegExp|Function|string|number|boolean|bigint|undefined} CodeValuePrimitive */ +/** @typedef {RecursiveArrayOrRecord} CodeValue */ + +/** + * @typedef {object} RuntimeValueOptions + * @property {string[]=} fileDependencies + * @property {string[]=} contextDependencies + * @property {string[]=} missingDependencies + * @property {string[]=} buildDependencies + * @property {string|function(): string=} version + */ + +/** @typedef {string | Set} ValueCacheVersion */ +/** @typedef {function({ module: NormalModule, key: string, readonly version: ValueCacheVersion }): CodeValuePrimitive} GeneratorFn */ + +class RuntimeValue { + /** + * @param {GeneratorFn} fn generator function + * @param {true | string[] | RuntimeValueOptions=} options options + */ + constructor(fn, options) { + this.fn = fn; + if (Array.isArray(options)) { + options = { + fileDependencies: options + }; + } + this.options = options || {}; + } + + get fileDependencies() { + return this.options === true ? true : this.options.fileDependencies; + } + + /** + * @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"; + } +} + +/** + * @param {Set | undefined} properties properties + * @returns {Set | undefined} used keys + */ +function getObjKeys(properties) { + if (!properties) return; + return new Set([...properties].map(p => p.id)); +} + +/** @typedef {Set | null} ObjKeys */ +/** @typedef {boolean | undefined | null} AsiSafe */ + +/** + * @param {any[]|{[k: string]: 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 +) => { + let code; + const arr = Array.isArray(obj); + if (arr) { + code = `[${ + /** @type {any[]} */ (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 = /** @type {{[k: string]: any}} */ (obj)[key]; + return `${JSON.stringify(key)}:${toCode( + code, + parser, + valueCacheVersions, + key, + runtimeTemplate, + logger, + null + )}`; + }) + .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; +}; + +/** + * @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()})`; + } + if (typeof code === "object") { + const items = Object.keys(code).map(key => ({ + key, + value: toCacheVersion(/** @type {Record} */ (code)[key]) + })); + if (items.some(({ value }) => value === undefined)) return; + return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`; + } + if (typeof code === "bigint") { + return `${code}n`; + } + return `${code}`; +}; + +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); + +class DefinePlugin { + /** + * Create a new define plugin + * @param {Record} definitions A map of global object definitions + */ + constructor(definitions) { + this.definitions = definitions; + } + + /** + * @param {GeneratorFn} fn generator function + * @param {true | string[] | RuntimeValueOptions=} options options + * @returns {RuntimeValue} runtime value + */ + static runtimeValue(fn, options) { + return new RuntimeValue(fn, options); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const definitions = this.definitions; + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const logger = compilation.getLogger("webpack.DefinePlugin"); + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + const { runtimeTemplate } = compilation; + + const mainHash = createHash( + /** @type {Algorithm} */ + (compilation.outputOptions.hashFunction) + ); + mainHash.update( + /** @type {string} */ + (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)) || "" + ); + + /** + * Handler + * @param {JavascriptParser} parser Parser + * @returns {void} + */ + const handler = parser => { + const mainValue = + /** @type {ValueCacheVersion} */ + (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)); + parser.hooks.program.tap(PLUGIN_NAME, () => { + const buildInfo = /** @type {BuildInfo} */ ( + parser.state.module.buildInfo + ); + if (!buildInfo.valueDependencies) + buildInfo.valueDependencies = new Map(); + buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue); + }); + + /** + * @param {string} key key + */ + const addValueDependency = key => { + const buildInfo = + /** @type {BuildInfo} */ + (parser.state.module.buildInfo); + /** @type {NonNullable} */ + (buildInfo.valueDependencies).set( + VALUE_DEP_PREFIX + key, + /** @type {ValueCacheVersion} */ + (compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key)) + ); + }; + + /** + * @template {Function} T + * @param {string} key key + * @param {T} fn fn + * @returns {function(TODO): TODO} result + */ + const withValueDependency = + (key, fn) => + (...args) => { + addValueDependency(key); + return fn(...args); + }; + + /** + * Walk definitions + * @param {Record} definitions Definitions map + * @param {string} prefix Prefix string + * @returns {void} + */ + const walkDefinitions = (definitions, prefix) => { + for (const key of Object.keys(definitions)) { + const code = definitions[key]; + if ( + code && + typeof code === "object" && + !(code instanceof RuntimeValue) && + !(code instanceof RegExp) + ) { + walkDefinitions( + /** @type {Record} */ (code), + `${prefix + key}.` + ); + applyObjectDefine(prefix + key, code); + continue; + } + applyDefineKey(prefix, key); + applyDefine(prefix + key, code); + } + }; + + /** + * Apply define key + * @param {string} prefix Prefix + * @param {string} key Key + * @returns {void} + */ + const applyDefineKey = (prefix, key) => { + const splittedKey = key.split("."); + 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(PLUGIN_NAME, () => { + addValueDependency(key); + if ( + parser.scope.definitions.get(firstKey) instanceof VariableInfo + ) { + return false; + } + return true; + }); + } + }; + + /** + * Apply Code + * @param {string} key Key + * @param {CodeValue} code Code + * @returns {void} + */ + const applyDefine = (key, code) => { + const originalKey = key; + const isTypeof = TYPEOF_OPERATOR_REGEXP.test(key); + if (isTypeof) key = key.replace(TYPEOF_OPERATOR_REGEXP, ""); + let recurse = false; + let recurseTypeof = false; + if (!isTypeof) { + parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => { + addValueDependency(originalKey); + return true; + }); + parser.hooks.evaluateIdentifier + .for(key) + .tap(PLUGIN_NAME, expr => { + /** + * this is needed in case there is a recursion in the DefinePlugin + * to prevent an endless recursion + * e.g.: new DefinePlugin({ + * "a": "b", + * "b": "a" + * }); + */ + if (recurse) return; + addValueDependency(originalKey); + recurse = true; + const res = parser.evaluate( + toCode( + code, + parser, + compilation.valueCacheVersions, + key, + runtimeTemplate, + logger, + null + ) + ); + recurse = false; + res.setRange(/** @type {Range} */ (expr.range)); + return res; + }); + 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); + }); + } + 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 + * e.g.: new DefinePlugin({ + * "typeof a": "typeof b", + * "typeof b": "typeof a" + * }); + */ + 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(/** @type {Range} */ (expr.range)); + return res; + }); + 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 toConstantDependency( + parser, + JSON.stringify(res.string) + ).bind(parser)(expr); + }); + }; + + /** + * Apply Object + * @param {string} key Key + * @param {object} obj Object + * @returns {void} + */ + const applyObjectDefine = (key, obj) => { + parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => { + addValueDependency(key); + return true; + }); + parser.hooks.evaluateIdentifier.for(key).tap(PLUGIN_NAME, expr => { + addValueDependency(key); + return new BasicEvaluatedExpression() + .setTruthy() + .setSideEffects(false) + .setRange(/** @type {Range} */ (expr.range)); + }); + parser.hooks.evaluateTypeof + .for(key) + .tap( + PLUGIN_NAME, + withValueDependency(key, evaluateToString("object")) + ); + 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); + }); + parser.hooks.typeof + .for(key) + .tap( + PLUGIN_NAME, + withValueDependency( + key, + toConstantDependency(parser, JSON.stringify("object")) + ) + ); + }; + + walkDefinitions(definitions, ""); + }; + + 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); + + /** + * Walk definitions + * @param {Record} 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 ( + code && + typeof code === "object" && + !(code instanceof RuntimeValue) && + !(code instanceof RegExp) + ) { + walkDefinitionsForValues( + /** @type {Record} */ (code), + `${prefix + key}.` + ); + } + } + }; + + walkDefinitionsForValues(definitions, ""); + + compilation.valueCacheVersions.set( + VALUE_DEP_MAIN, + /** @type {string} */ (mainHash.digest("hex").slice(0, 8)) + ); + } + ); + } +} +module.exports = DefinePlugin; diff --git a/webpack-lib/lib/DelegatedModule.js b/webpack-lib/lib/DelegatedModule.js new file mode 100644 index 00000000000..e6bc5bc25d5 --- /dev/null +++ b/webpack-lib/lib/DelegatedModule.js @@ -0,0 +1,262 @@ +/* + 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 { JS_TYPES } = require("./ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency"); +const StaticExportsDependency = require("./dependencies/StaticExportsDependency"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./LibManifestPlugin").ManifestModuleData} ManifestModuleData */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./Module").SourceContext} SourceContext */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./util/Hash")} Hash */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +/** @typedef {string} SourceRequest */ +/** @typedef {"require" | "object"} Type */ +/** @typedef {TODO} Data */ + +const RUNTIME_REQUIREMENTS = new Set([ + RuntimeGlobals.module, + RuntimeGlobals.require +]); + +class DelegatedModule extends Module { + /** + * @param {SourceRequest} sourceRequest source request + * @param {Data} data data + * @param {Type} type type + * @param {string} userRequest user request + * @param {string | Module} originalRequest original request + */ + constructor(sourceRequest, data, type, userRequest, originalRequest) { + super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); + + // Info from Factory + this.sourceRequest = sourceRequest; + this.request = data.id; + this.delegationType = type; + this.userRequest = userRequest; + this.originalRequest = originalRequest; + /** @type {ManifestModuleData | undefined} */ + this.delegateData = data; + + // Build info + this.delegatedSourceDependency = undefined; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return typeof this.originalRequest === "string" + ? this.originalRequest + : this.originalRequest.libIdent(options); + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `delegated ${JSON.stringify(this.request)} from ${ + this.sourceRequest + }`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `delegated ${this.userRequest} from ${this.sourceRequest}`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + const delegateData = /** @type {ManifestModuleData} */ (this.delegateData); + this.buildMeta = { ...delegateData.buildMeta }; + this.buildInfo = {}; + this.dependencies.length = 0; + this.delegatedSourceDependency = new DelegatedSourceDependency( + this.sourceRequest + ); + this.addDependency(this.delegatedSourceDependency); + this.addDependency( + new StaticExportsDependency(delegateData.exports || true, false) + ); + callback(); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { + const dep = /** @type {DelegatedSourceDependency} */ (this.dependencies[0]); + const sourceModule = moduleGraph.getModule(dep); + let str; + + if (!sourceModule) { + str = runtimeTemplate.throwMissingModuleErrorBlock({ + request: this.sourceRequest + }); + } else { + str = `module.exports = (${runtimeTemplate.moduleExports({ + module: sourceModule, + chunkGraph, + request: dep.request, + runtimeRequirements: new Set() + })})`; + + switch (this.delegationType) { + case "require": + str += `(${JSON.stringify(this.request)})`; + break; + case "object": + str += `[${JSON.stringify(this.request)}]`; + break; + } + + str += ";"; + } + + const sources = new Map(); + if (this.useSourceMap || this.useSimpleSourceMap) { + sources.set("javascript", new OriginalSource(str, this.identifier())); + } else { + sources.set("javascript", new RawSource(str)); + } + + return { + sources, + runtimeRequirements: RUNTIME_REQUIREMENTS + }; + } + + /** + * @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; + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + hash.update(this.delegationType); + hash.update(JSON.stringify(this.request)); + super.updateHash(hash, context); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + // constructor + write(this.sourceRequest); + write(this.delegateData); + write(this.delegationType); + write(this.userRequest); + write(this.originalRequest); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context\ + * @returns {DelegatedModule} DelegatedModule + */ + static deserialize(context) { + const { read } = context; + const obj = new DelegatedModule( + read(), // sourceRequest + read(), // delegateData + read(), // delegationType + read(), // userRequest + read() // originalRequest + ); + obj.deserialize(context); + return obj; + } + + /** + * 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) { + super.updateCacheModule(module); + const m = /** @type {DelegatedModule} */ (module); + this.delegationType = m.delegationType; + this.userRequest = m.userRequest; + this.originalRequest = m.originalRequest; + this.delegateData = m.delegateData; + } + + /** + * Assuming this module is in the cache. Remove internal references to allow freeing some memory. + */ + cleanupForCache() { + super.cleanupForCache(); + this.delegateData = undefined; + } +} + +makeSerializable(DelegatedModule, "webpack/lib/DelegatedModule"); + +module.exports = DelegatedModule; diff --git a/webpack-lib/lib/DelegatedModuleFactoryPlugin.js b/webpack-lib/lib/DelegatedModuleFactoryPlugin.js new file mode 100644 index 00000000000..ae9b79aaed7 --- /dev/null +++ b/webpack-lib/lib/DelegatedModuleFactoryPlugin.js @@ -0,0 +1,111 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const DelegatedModule = require("./DelegatedModule"); + +/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptions} DllReferencePluginOptions */ +/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsContent} DllReferencePluginOptionsContent */ +/** @typedef {import("./DelegatedModule").Data} Data */ +/** @typedef {import("./DelegatedModule").SourceRequest} SourceRequest */ +/** @typedef {import("./DelegatedModule").Type} Type */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ + +/** + * @typedef {object} Options + * @property {SourceRequest} source source + * @property {NonNullable} context absolute context path to which lib ident is relative to + * @property {DllReferencePluginOptionsContent} content content + * @property {DllReferencePluginOptions["type"]} type type + * @property {DllReferencePluginOptions["extensions"]} extensions extensions + * @property {DllReferencePluginOptions["scope"]} scope scope + * @property {object=} associatedObjectForCache object for caching + */ + +class DelegatedModuleFactoryPlugin { + /** + * @param {Options} options options + */ + constructor(options) { + this.options = options; + options.type = options.type || "require"; + options.extensions = options.extensions || ["", ".js", ".json", ".wasm"]; + } + + /** + * @param {NormalModuleFactory} normalModuleFactory the normal module factory + * @returns {void} + */ + apply(normalModuleFactory) { + const scope = this.options.scope; + if (scope) { + normalModuleFactory.hooks.factorize.tapAsync( + "DelegatedModuleFactoryPlugin", + (data, callback) => { + const [dependency] = data.dependencies; + const { request } = dependency; + if (request && request.startsWith(`${scope}/`)) { + const innerRequest = `.${request.slice(scope.length)}`; + let resolved; + if (innerRequest in this.options.content) { + resolved = this.options.content[innerRequest]; + return callback( + null, + new DelegatedModule( + this.options.source, + resolved, + /** @type {Type} */ (this.options.type), + innerRequest, + request + ) + ); + } + const extensions = + /** @type {string[]} */ + (this.options.extensions); + for (let i = 0; i < extensions.length; i++) { + const extension = 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, + /** @type {Type} */ (this.options.type), + requestPlusExt, + request + extension + ) + ); + } + } + } + return callback(); + } + ); + } else { + normalModuleFactory.hooks.module.tap( + "DelegatedModuleFactoryPlugin", + module => { + 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, + /** @type {Type} */ (this.options.type), + request, + module + ); + } + return module; + } + ); + } + } +} +module.exports = DelegatedModuleFactoryPlugin; diff --git a/webpack-lib/lib/DelegatedPlugin.js b/webpack-lib/lib/DelegatedPlugin.js new file mode 100644 index 00000000000..735e2f083e2 --- /dev/null +++ b/webpack-lib/lib/DelegatedPlugin.js @@ -0,0 +1,47 @@ +/* + 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"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./DelegatedModuleFactoryPlugin").Options} Options */ + +class DelegatedPlugin { + /** + * @param {Options} options options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DelegatedPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + DelegatedSourceDependency, + normalModuleFactory + ); + } + ); + + compiler.hooks.compile.tap("DelegatedPlugin", ({ normalModuleFactory }) => { + new DelegatedModuleFactoryPlugin({ + associatedObjectForCache: compiler.root, + ...this.options + }).apply(normalModuleFactory); + }); + } +} + +module.exports = DelegatedPlugin; diff --git a/webpack-lib/lib/DependenciesBlock.js b/webpack-lib/lib/DependenciesBlock.js new file mode 100644 index 00000000000..a952b643b56 --- /dev/null +++ b/webpack-lib/lib/DependenciesBlock.js @@ -0,0 +1,122 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @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 = []; + /** @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; + } + + /** + * @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); + } + + /** + * @param {Dependency} dependency dependency being removed + * @returns {void} + */ + removeDependency(dependency) { + const idx = this.dependencies.indexOf(dependency); + if (idx >= 0) { + this.dependencies.splice(idx, 1); + } + } + + /** + * Removes all dependencies and blocks + * @returns {void} + */ + clearDependenciesAndBlocks() { + this.dependencies.length = 0; + this.blocks.length = 0; + } + + /** + * @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); + } + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize({ write }) { + write(this.dependencies); + write(this.blocks); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize({ read }) { + this.dependencies = read(); + this.blocks = read(); + for (const block of this.blocks) { + block.parent = this; + } + } +} + +makeSerializable(DependenciesBlock, "webpack/lib/DependenciesBlock"); + +module.exports = DependenciesBlock; diff --git a/webpack-lib/lib/Dependency.js b/webpack-lib/lib/Dependency.js new file mode 100644 index 00000000000..a18f7365444 --- /dev/null +++ b/webpack-lib/lib/Dependency.js @@ -0,0 +1,367 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RawModule = require("./RawModule"); +const memoize = require("./util/memoize"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./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 {object} UpdateHashContext + * @property {ChunkGraph} chunkGraph + * @property {RuntimeSpec} runtime + * @property {RuntimeTemplate=} runtimeTemplate + */ + +/** + * @typedef {object} SourcePosition + * @property {number} line + * @property {number=} column + */ + +/** + * @typedef {object} RealDependencyLocation + * @property {SourcePosition} start + * @property {SourcePosition=} end + * @property {number=} index + */ + +/** + * @typedef {object} SyntheticDependencyLocation + * @property {string} name + * @property {number=} index + */ + +/** @typedef {SyntheticDependencyLocation | RealDependencyLocation} DependencyLocation */ + +/** + * @typedef {object} ExportSpec + * @property {string} 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 {(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 + */ + +/** + * @typedef {object} ExportsSpec + * @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports + * @property {Set=} 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 {Module[]=} dependencies module on which the result depends on + */ + +/** + * @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 + */ + +/** @typedef {function(ModuleGraphConnection, RuntimeSpec): ConnectionState} GetConditionFn */ + +const TRANSITIVE = Symbol("transitive"); + +const getIgnoredModule = memoize( + () => new RawModule("/* (ignored) */", "ignored", "(ignored)") +); + +class Dependency { + constructor() { + /** @type {Module | undefined} */ + this._parentModule = undefined; + /** @type {DependenciesBlock | undefined} */ + this._parentDependenciesBlock = undefined; + /** @type {number} */ + this._parentDependenciesBlockIndex = -1; + // TODO check if this can be moved into ModuleDependency + /** @type {boolean} */ + this.weak = false; + // TODO check if this can be moved into ModuleDependency + /** @type {boolean} */ + this.optional = false; + this._locSL = 0; + this._locSC = 0; + this._locEL = 0; + this._locEC = 0; + this._locI = undefined; + this._locN = undefined; + this._loc = undefined; + } + + /** + * @returns {string} a display name for the type of dependency + */ + get type() { + return "unknown"; + } + + /** + * @returns {string} a dependency category, typical categories are "commonjs", "amd", "esm" + */ + get category() { + return "unknown"; + } + + /** + * @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; + this._loc = loc; + } + + /** + * @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; + } + + /** + * @returns {string | undefined} a request context + */ + getContext() { + return undefined; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return null; + } + + /** + * @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 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 and ModuleGraph.getConnection().active" + ); + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return Dependency.EXPORTS_OBJECT_REFERENCED; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return null; + } + + /** + * 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; + } + + /** + * Returns errors + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} errors + */ + getErrors(moduleGraph) { + return null; + } + + /** + * Update the hash + * @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; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return true; + } + + /** + * @param {string} context context directory + * @returns {Module | null} a module + */ + createIgnoredModule(context) { + return getIgnoredModule(); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize({ write }) { + write(this.weak); + write(this.optional); + write(this._locSL); + write(this._locSC); + write(this._locEL); + write(this._locEC); + write(this._locI); + write(this._locN); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize({ read }) { + this.weak = read(); + this.optional = read(); + this._locSL = read(); + this._locSC = read(); + this._locEL = read(); + this._locEC = read(); + this._locI = read(); + this._locN = read(); + } +} + +/** @type {string[][]} */ +Dependency.NO_EXPORTS_REFERENCED = []; +/** @type {string[][]} */ +Dependency.EXPORTS_OBJECT_REFERENCED = [[]]; + +// eslint-disable-next-line no-warning-comments +// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 +Object.defineProperty(Dependency.prototype, "module", { + /** + * @deprecated + * @returns {never} throws + */ + get() { + throw new Error( + "module property was removed from Dependency (use compilation.moduleGraph.getModule(dependency) instead)" + ); + }, + + /** + * @deprecated + * @returns {never} throws + */ + set() { + throw new Error( + "module property was removed from Dependency (use compilation.moduleGraph.updateModule(dependency, module) instead)" + ); + } +}); + +// eslint-disable-next-line no-warning-comments +// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 +Object.defineProperty(Dependency.prototype, "disconnect", { + get() { + throw new Error( + "disconnect was removed from Dependency (Dependency no longer carries graph specific information)" + ); + } +}); + +Dependency.TRANSITIVE = TRANSITIVE; + +module.exports = Dependency; diff --git a/webpack-lib/lib/DependencyTemplate.js b/webpack-lib/lib/DependencyTemplate.js new file mode 100644 index 00000000000..8402ade157e --- /dev/null +++ b/webpack-lib/lib/DependencyTemplate.js @@ -0,0 +1,69 @@ +/* + 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 */ + +/** + * @template T + * @typedef {import("./InitFragment")} InitFragment + */ + +/** + * @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 + */ + +/** + * @typedef {object} CssDependencyTemplateContextExtras + * @property {CssData} cssData the css exports data + */ + +/** + * @typedef {object} CssData + * @property {boolean} esModule whether export __esModule + * @property {Map} exports the css exports + */ + +/** @typedef {DependencyTemplateContext & CssDependencyTemplateContextExtras} CssDependencyTemplateContext */ + +class DependencyTemplate { + /* istanbul ignore next */ + /** + * @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("./AbstractMethodError"); + throw new AbstractMethodError(); + } +} + +module.exports = DependencyTemplate; diff --git a/webpack-lib/lib/DependencyTemplates.js b/webpack-lib/lib/DependencyTemplates.js new file mode 100644 index 00000000000..3b553dae4cb --- /dev/null +++ b/webpack-lib/lib/DependencyTemplates.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const createHash = require("./util/createHash"); + +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ +/** @typedef {typeof import("./util/Hash")} Hash */ + +/** @typedef {new (...args: any[]) => Dependency} DependencyConstructor */ + +class DependencyTemplates { + /** + * @param {string | Hash} hashFunction the hash function to use + */ + constructor(hashFunction = "md4") { + /** @type {Map} */ + this._map = new Map(); + /** @type {string} */ + this._hash = "31d6cfe0d16ae931b73c59d7e0c089c0"; + this._hashFunction = hashFunction; + } + + /** + * @param {DependencyConstructor} dependency Constructor of Dependency + * @returns {DependencyTemplate | undefined} template for this dependency + */ + get(dependency) { + return this._map.get(dependency); + } + + /** + * @param {DependencyConstructor} dependency Constructor of Dependency + * @param {DependencyTemplate} dependencyTemplate template for this dependency + * @returns {void} + */ + set(dependency, dependencyTemplate) { + this._map.set(dependency, dependencyTemplate); + } + + /** + * @param {string} part additional hash contributor + * @returns {void} + */ + updateHash(part) { + const hash = createHash(this._hashFunction); + hash.update(`${this._hash}${part}`); + this._hash = /** @type {string} */ (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/webpack-lib/lib/DllEntryPlugin.js b/webpack-lib/lib/DllEntryPlugin.js new file mode 100644 index 00000000000..de849fa5376 --- /dev/null +++ b/webpack-lib/lib/DllEntryPlugin.js @@ -0,0 +1,72 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const DllModuleFactory = require("./DllModuleFactory"); +const DllEntryDependency = require("./dependencies/DllEntryDependency"); +const EntryDependency = require("./dependencies/EntryDependency"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {string[]} Entries */ +/** @typedef {{ name: string, filename: TODO }} Options */ + +class DllEntryPlugin { + /** + * @param {string} context context + * @param {Entries} entries entry names + * @param {Options} options options + */ + constructor(context, entries, options) { + this.context = context; + this.entries = entries; + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DllEntryPlugin", + (compilation, { normalModuleFactory }) => { + const dllModuleFactory = new DllModuleFactory(); + compilation.dependencyFactories.set( + DllEntryDependency, + dllModuleFactory + ); + compilation.dependencyFactories.set( + EntryDependency, + normalModuleFactory + ); + } + ); + compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => { + compilation.addEntry( + this.context, + new DllEntryDependency( + this.entries.map((e, idx) => { + const dep = new EntryDependency(e); + dep.loc = { + name: this.options.name, + index: idx + }; + return dep; + }), + this.options.name + ), + this.options, + error => { + if (error) return callback(error); + callback(); + } + ); + }); + } +} + +module.exports = DllEntryPlugin; diff --git a/webpack-lib/lib/DllModule.js b/webpack-lib/lib/DllModule.js new file mode 100644 index 00000000000..e9948fc61cc --- /dev/null +++ b/webpack-lib/lib/DllModule.js @@ -0,0 +1,174 @@ +/* + 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 { JS_TYPES } = require("./ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./Module").SourceContext} SourceContext */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./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 */ + +const RUNTIME_REQUIREMENTS = new Set([ + RuntimeGlobals.require, + RuntimeGlobals.module +]); + +class DllModule extends Module { + /** + * @param {string} context context path + * @param {Dependency[]} dependencies dependencies + * @param {string} name name + */ + constructor(context, dependencies, name) { + super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, context); + + // Info from Factory + /** @type {Dependency[]} */ + this.dependencies = dependencies; + this.name = name; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `dll ${this.name}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `dll ${this.name}`; + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = {}; + return callback(); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + const sources = new Map(); + sources.set( + "javascript", + new RawSource(`module.exports = ${RuntimeGlobals.require};`) + ); + return { + sources, + runtimeRequirements: RUNTIME_REQUIREMENTS + }; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @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 12; + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + hash.update(`dll module${this.name || ""}`); + super.updateHash(hash, context); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + context.write(this.name); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + this.name = context.read(); + super.deserialize(context); + } + + /** + * 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) { + super.updateCacheModule(module); + this.dependencies = module.dependencies; + } + + /** + * Assuming this module is in the cache. Remove internal references to allow freeing some memory. + */ + cleanupForCache() { + super.cleanupForCache(); + this.dependencies = /** @type {EXPECTED_ANY} */ (undefined); + } +} + +makeSerializable(DllModule, "webpack/lib/DllModule"); + +module.exports = DllModule; diff --git a/webpack-lib/lib/DllModuleFactory.js b/webpack-lib/lib/DllModuleFactory.js new file mode 100644 index 00000000000..d8800353da9 --- /dev/null +++ b/webpack-lib/lib/DllModuleFactory.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const DllModule = require("./DllModule"); +const ModuleFactory = require("./ModuleFactory"); + +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */ + +class DllModuleFactory extends ModuleFactory { + constructor() { + super(); + this.hooks = Object.freeze({}); + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const dependency = /** @type {DllEntryDependency} */ (data.dependencies[0]); + callback(null, { + module: new DllModule( + data.context, + dependency.dependencies, + dependency.name + ) + }); + } +} + +module.exports = DllModuleFactory; diff --git a/webpack-lib/lib/DllPlugin.js b/webpack-lib/lib/DllPlugin.js new file mode 100644 index 00000000000..25440df04ee --- /dev/null +++ b/webpack-lib/lib/DllPlugin.js @@ -0,0 +1,70 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const DllEntryPlugin = require("./DllEntryPlugin"); +const FlagAllModulesAsUsedPlugin = require("./FlagAllModulesAsUsedPlugin"); +const LibManifestPlugin = require("./LibManifestPlugin"); +const createSchemaValidation = require("./util/create-schema-validation"); + +/** @typedef {import("../declarations/plugins/DllPlugin").DllPluginOptions} DllPluginOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./DllEntryPlugin").Entries} Entries */ +/** @typedef {import("./DllEntryPlugin").Options} Options */ + +const validate = createSchemaValidation( + require("../schemas/plugins/DllPlugin.check.js"), + () => require("../schemas/plugins/DllPlugin.json"), + { + name: "Dll Plugin", + baseDataPath: "options" + } +); + +class DllPlugin { + /** + * @param {DllPluginOptions} options options object + */ + constructor(options) { + validate(options); + this.options = { + ...options, + entryOnly: options.entryOnly !== false + }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => { + if (typeof entry !== "function") { + for (const name of Object.keys(entry)) { + /** @type {Options} */ + const options = { name, filename: entry.filename }; + new DllEntryPlugin( + context, + /** @type {Entries} */ (entry[name].import), + options + ).apply(compiler); + } + } else { + throw new Error( + "DllPlugin doesn't support dynamic entry (function) yet" + ); + } + return true; + }); + new LibManifestPlugin(this.options).apply(compiler); + if (!this.options.entryOnly) { + new FlagAllModulesAsUsedPlugin("DllPlugin").apply(compiler); + } + } +} + +module.exports = DllPlugin; diff --git a/webpack-lib/lib/DllReferencePlugin.js b/webpack-lib/lib/DllReferencePlugin.js new file mode 100644 index 00000000000..50b2c541021 --- /dev/null +++ b/webpack-lib/lib/DllReferencePlugin.js @@ -0,0 +1,193 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const parseJson = require("json-parse-even-better-errors"); +const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin"); +const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin"); +const WebpackError = require("./WebpackError"); +const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency"); +const createSchemaValidation = require("./util/create-schema-validation"); +const makePathsRelative = require("./util/identifier").makePathsRelative; + +/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ +/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptions} DllReferencePluginOptions */ +/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsContent} DllReferencePluginOptionsContent */ +/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsManifest} DllReferencePluginOptionsManifest */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +const validate = createSchemaValidation( + require("../schemas/plugins/DllReferencePlugin.check.js"), + () => require("../schemas/plugins/DllReferencePlugin.json"), + { + name: "Dll Reference Plugin", + baseDataPath: "options" + } +); + +/** @typedef {{ path: string, data: DllReferencePluginOptionsManifest | undefined, error: Error | undefined }} CompilationDataItem */ + +class DllReferencePlugin { + /** + * @param {DllReferencePluginOptions} options options object + */ + constructor(options) { + validate(options); + this.options = options; + /** @type {WeakMap} */ + this._compilationData = new WeakMap(); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DllReferencePlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + DelegatedSourceDependency, + normalModuleFactory + ); + } + ); + + compiler.hooks.beforeCompile.tapAsync( + "DllReferencePlugin", + (params, callback) => { + if ("manifest" in this.options) { + const manifest = this.options.manifest; + if (typeof manifest === "string") { + /** @type {InputFileSystem} */ + (compiler.inputFileSystem).readFile(manifest, (err, result) => { + if (err) return callback(err); + /** @type {CompilationDataItem} */ + const data = { + path: manifest, + data: undefined, + error: undefined + }; + // Catch errors parsing the manifest so that blank + // or malformed manifest files don't kill the process. + try { + data.data = parseJson( + /** @type {Buffer} */ (result).toString("utf-8") + ); + } catch (parseErr) { + // Store the error in the params so that it can + // be added as a compilation error later on. + const manifestPath = makePathsRelative( + /** @type {string} */ (compiler.options.context), + manifest, + compiler.root + ); + data.error = new DllManifestError( + manifestPath, + /** @type {Error} */ (parseErr).message + ); + } + this._compilationData.set(params, data); + return callback(); + }); + return; + } + } + return callback(); + } + ); + + compiler.hooks.compile.tap("DllReferencePlugin", params => { + let name = this.options.name; + let sourceType = this.options.sourceType; + let resolvedContent = + "content" in this.options ? this.options.content : undefined; + if ("manifest" in this.options) { + const manifestParameter = this.options.manifest; + let manifest; + if (typeof manifestParameter === "string") { + const data = + /** @type {CompilationDataItem} */ + (this._compilationData.get(params)); + // If there was an error parsing the manifest + // file, exit now because the error will be added + // as a compilation error in the "compilation" hook. + if (data.error) { + return; + } + manifest = data.data; + } else { + manifest = manifestParameter; + } + if (manifest) { + if (!name) name = manifest.name; + if (!sourceType) sourceType = manifest.type; + if (!resolvedContent) resolvedContent = manifest.content; + } + } + /** @type {Externals} */ + const externals = {}; + const source = `dll-reference ${name}`; + externals[source] = /** @type {string} */ (name); + const normalModuleFactory = params.normalModuleFactory; + new ExternalModuleFactoryPlugin(sourceType || "var", externals).apply( + normalModuleFactory + ); + new DelegatedModuleFactoryPlugin({ + source, + type: this.options.type, + scope: this.options.scope, + context: + /** @type {string} */ + (this.options.context || compiler.options.context), + content: + /** @type {DllReferencePluginOptionsContent} */ + (resolvedContent), + extensions: this.options.extensions, + associatedObjectForCache: compiler.root + }).apply(normalModuleFactory); + }); + + compiler.hooks.compilation.tap( + "DllReferencePlugin", + (compilation, params) => { + if ("manifest" in this.options) { + const manifest = this.options.manifest; + if (typeof manifest === "string") { + const data = /** @type {CompilationDataItem} */ ( + this._compilationData.get(params) + ); + // If there was an error parsing the manifest file, add the + // error as a compilation error to make the compilation fail. + if (data.error) { + compilation.errors.push( + /** @type {DllManifestError} */ (data.error) + ); + } + compilation.fileDependencies.add(manifest); + } + } + } + ); + } +} + +class DllManifestError extends WebpackError { + /** + * @param {string} filename filename of the manifest + * @param {string} message error message + */ + constructor(filename, message) { + super(); + + this.name = "DllManifestError"; + this.message = `Dll manifest ${filename}\n${message}`; + } +} + +module.exports = DllReferencePlugin; diff --git a/webpack-lib/lib/DynamicEntryPlugin.js b/webpack-lib/lib/DynamicEntryPlugin.js new file mode 100644 index 00000000000..5e185fbee0f --- /dev/null +++ b/webpack-lib/lib/DynamicEntryPlugin.js @@ -0,0 +1,86 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Naoyuki Kanezawa @nkzawa +*/ + +"use strict"; + +const EntryOptionPlugin = require("./EntryOptionPlugin"); +const EntryPlugin = require("./EntryPlugin"); +const EntryDependency = require("./dependencies/EntryDependency"); + +/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ +/** @typedef {import("../declarations/WebpackOptions").EntryDynamicNormalized} EntryDynamic */ +/** @typedef {import("../declarations/WebpackOptions").EntryItem} EntryItem */ +/** @typedef {import("../declarations/WebpackOptions").EntryStaticNormalized} EntryStatic */ +/** @typedef {import("./Compiler")} Compiler */ + +class DynamicEntryPlugin { + /** + * @param {string} context the context path + * @param {EntryDynamic} entry the entry value + */ + constructor(context, entry) { + this.context = context; + this.entry = entry; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DynamicEntryPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + EntryDependency, + normalModuleFactory + ); + } + ); + + compiler.hooks.make.tapPromise("DynamicEntryPlugin", compilation => + Promise.resolve(this.entry()) + .then(entry => { + 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( + /** + * @param {(value?: any) => 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(x => {}) + ); + } +} + +module.exports = DynamicEntryPlugin; diff --git a/webpack-lib/lib/EntryOptionPlugin.js b/webpack-lib/lib/EntryOptionPlugin.js new file mode 100644 index 00000000000..3e290c186f2 --- /dev/null +++ b/webpack-lib/lib/EntryOptionPlugin.js @@ -0,0 +1,96 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ +/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ + +class EntryOptionPlugin { + /** + * @param {Compiler} compiler the compiler instance one is tapping into + * @returns {void} + */ + apply(compiler) { + compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => { + EntryOptionPlugin.applyEntryOption(compiler, context, entry); + return true; + }); + } + + /** + * @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); + for (const entry of descImport) { + new EntryPlugin(context, entry, options).apply(compiler); + } + } + } + } + + /** + * @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, + library: desc.library + }; + if (desc.layer !== undefined && !compiler.options.experiments.layers) { + throw new Error( + "'entryOptions.layer' is only allowed when 'experiments.layers' is enabled" + ); + } + 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/webpack-lib/lib/EntryPlugin.js b/webpack-lib/lib/EntryPlugin.js new file mode 100644 index 00000000000..77c879705e8 --- /dev/null +++ b/webpack-lib/lib/EntryPlugin.js @@ -0,0 +1,70 @@ +/* + 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 */ + +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 || ""; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "EntryPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + EntryDependency, + normalModuleFactory + ); + } + ); + + const { entry, options, context } = this; + const dep = EntryPlugin.createDependency(entry, options); + + compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => { + compilation.addEntry(context, dep, options, err => { + callback(err); + }); + }); + } + + /** + * @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/webpack-lib/lib/Entrypoint.js b/webpack-lib/lib/Entrypoint.js new file mode 100644 index 00000000000..7aa019e4d28 --- /dev/null +++ b/webpack-lib/lib/Entrypoint.js @@ -0,0 +1,101 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ChunkGroup = require("./ChunkGroup"); + +/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ +/** @typedef {import("./Chunk")} Chunk */ + +/** @typedef {{ name?: string } & Omit} EntryOptions */ + +/** + * Entrypoint serves as an encapsulation primitive for chunks that are + * a part of a single ChunkGroup. They represent all bundles that need to be loaded for a + * single instance of a page. Multi-page application architectures will typically yield multiple Entrypoint objects + * inside of the compilation, whereas a Single Page App may only contain one with many lazy-loaded chunks. + */ +class Entrypoint extends ChunkGroup { + /** + * Creates an instance of Entrypoint. + * @param {EntryOptions | string} entryOptions the options for the entrypoint (or name) + * @param {boolean=} initial false, when the entrypoint is not initial loaded + */ + constructor(entryOptions, initial = true) { + if (typeof entryOptions === "string") { + entryOptions = { name: entryOptions }; + } + super({ + name: entryOptions.name + }); + this.options = entryOptions; + /** @type {Chunk=} */ + this._runtimeChunk = undefined; + /** @type {Chunk=} */ + this._entrypointChunk = undefined; + /** @type {boolean} */ + this._initial = initial; + } + + /** + * @returns {boolean} true, when this chunk group will be loaded on initial page load + */ + isInitial() { + return this._initial; + } + + /** + * Sets the runtimeChunk for an entrypoint. + * @param {Chunk} chunk the chunk being set as the runtime chunk. + * @returns {void} + */ + setRuntimeChunk(chunk) { + this._runtimeChunk = chunk; + } + + /** + * Fetches the chunk reference containing the webpack bootstrap code + * @returns {Chunk | null} returns the runtime chunk or null if there is none + */ + getRuntimeChunk() { + 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); + } + + /** + * @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); + } +} + +module.exports = Entrypoint; diff --git a/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js b/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js new file mode 100644 index 00000000000..1a1ea9ece66 --- /dev/null +++ b/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js @@ -0,0 +1,52 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Gengkun He @ahabhgk +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {"asyncWebAssembly" | "topLevelAwait" | "external promise" | "external script" | "external import" | "external module"} Feature */ + +class EnvironmentNotSupportAsyncWarning extends WebpackError { + /** + * Creates an instance of EnvironmentNotSupportAsyncWarning. + * @param {Module} module module + * @param {Feature} feature feature + */ + constructor(module, feature) { + const message = `The generated code contains 'async/await' because this module is using "${feature}". +However, your target environment does not appear to support 'async/await'. +As a result, the code may not run as expected or may cause runtime errors.`; + super(message); + + this.name = "EnvironmentNotSupportAsyncWarning"; + this.module = module; + } + + /** + * Creates an instance of EnvironmentNotSupportAsyncWarning. + * @param {Module} module module + * @param {RuntimeTemplate} runtimeTemplate compilation + * @param {Feature} feature feature + */ + static check(module, runtimeTemplate, feature) { + if (!runtimeTemplate.supportsAsyncFunction()) { + module.addWarning(new EnvironmentNotSupportAsyncWarning(module, feature)); + } + } +} + +makeSerializable( + EnvironmentNotSupportAsyncWarning, + "webpack/lib/EnvironmentNotSupportAsyncWarning" +); + +module.exports = EnvironmentNotSupportAsyncWarning; diff --git a/webpack-lib/lib/EnvironmentPlugin.js b/webpack-lib/lib/EnvironmentPlugin.js new file mode 100644 index 00000000000..eb9e37a6d4c --- /dev/null +++ b/webpack-lib/lib/EnvironmentPlugin.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Authors Simen Brekken @simenbrekken, Einar Löve @einarlove +*/ + +"use strict"; + +const DefinePlugin = require("./DefinePlugin"); +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./DefinePlugin").CodeValue} CodeValue */ + +class 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 = /** @type {Record} */ (keys[0]); + } else { + this.keys = /** @type {string[]} */ (keys); + this.defaultValues = {}; + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + /** @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 WebpackError( + `EnvironmentPlugin - ${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.errors.push(error); + }); + } + + definitions[`process.env.${key}`] = + value === undefined ? "undefined" : JSON.stringify(value); + } + + new DefinePlugin(definitions).apply(compiler); + } +} + +module.exports = EnvironmentPlugin; diff --git a/webpack-lib/lib/ErrorHelpers.js b/webpack-lib/lib/ErrorHelpers.js new file mode 100644 index 00000000000..58c11554193 --- /dev/null +++ b/webpack-lib/lib/ErrorHelpers.js @@ -0,0 +1,100 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const loaderFlag = "LOADER_EXECUTION"; + +const webpackOptionsFlag = "WEBPACK_OPTIONS"; + +/** + * @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 errorStack.join("\n"); +}; + +/** + * @param {string} stack stack trace + * @returns {string} stack trace without the loader execution flag included + */ +const cutOffLoaderExecution = stack => cutOffByFlag(stack, loaderFlag); + +/** + * @param {string} stack stack trace + * @returns {string} stack trace without the webpack options flag included + */ +const cutOffWebpackOptions = stack => cutOffByFlag(stack, webpackOptionsFlag); + +/** + * @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"); + + /** @type {string[]} */ + const result = []; + + for (const [idx, line] of stackSplitByLines.entries()) { + if (!line.includes(messageSplitByLines[idx])) result.push(line); + } + + return result.join("\n"); +}; + +/** + * @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; + } + const firstLine = stack.slice(0, nextLine); + return firstLine === message ? stack.slice(nextLine + 1) : stack; +}; + +/** + * @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; +}; + +/** + * @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.cutOffByFlag = cutOffByFlag; +module.exports.cutOffLoaderExecution = cutOffLoaderExecution; +module.exports.cutOffWebpackOptions = cutOffWebpackOptions; +module.exports.cutOffMultilineMessage = cutOffMultilineMessage; +module.exports.cutOffMessage = cutOffMessage; +module.exports.cleanUp = cleanUp; +module.exports.cleanUpWebpackOptions = cleanUpWebpackOptions; diff --git a/webpack-lib/lib/EvalDevToolModulePlugin.js b/webpack-lib/lib/EvalDevToolModulePlugin.js new file mode 100644 index 00000000000..a364c3f9d2f --- /dev/null +++ b/webpack-lib/lib/EvalDevToolModulePlugin.js @@ -0,0 +1,129 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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").OutputNormalized} OutputOptions */ +/** @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/). + */ +`); + +/** + * @typedef {object} EvalDevToolModulePluginOptions + * @property {OutputOptions["devtoolNamespace"]=} namespace namespace + * @property {string=} sourceUrlComment source url comment + * @property {OutputOptions["devtoolModuleFilenameTemplate"]=} moduleFilenameTemplate module filename template + */ + +class EvalDevToolModulePlugin { + /** + * @param {EvalDevToolModulePluginOptions=} options options + */ + constructor(options = {}) { + this.namespace = options.namespace || ""; + this.sourceUrlComment = options.sourceUrlComment || "\n//# sourceURL=[url]"; + this.moduleFilenameTemplate = + options.moduleFilenameTemplate || + "webpack://[namespace]/[resourcePath]?[loaders]"; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("EvalDevToolModulePlugin", compilation => { + const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); + hooks.renderModuleContent.tap( + "EvalDevToolModulePlugin", + (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 + )})` + : JSON.stringify(content + footer) + });` + ); + cache.set(source, result); + return result; + } + ); + hooks.inlineInRuntimeBailout.tap( + "EvalDevToolModulePlugin", + () => "the eval devtool is used." + ); + hooks.render.tap( + "EvalDevToolModulePlugin", + source => new ConcatSource(devtoolWarning, source) + ); + hooks.chunkHash.tap("EvalDevToolModulePlugin", (chunk, hash) => { + hash.update("EvalDevToolModulePlugin"); + hash.update("2"); + }); + if (compilation.outputOptions.trustedTypes) { + compilation.hooks.additionalModuleRuntimeRequirements.tap( + "EvalDevToolModulePlugin", + (module, set, context) => { + set.add(RuntimeGlobals.createScript); + } + ); + } + }); + } +} + +module.exports = EvalDevToolModulePlugin; diff --git a/webpack-lib/lib/EvalSourceMapDevToolPlugin.js b/webpack-lib/lib/EvalSourceMapDevToolPlugin.js new file mode 100644 index 00000000000..a4bb7fd61e5 --- /dev/null +++ b/webpack-lib/lib/EvalSourceMapDevToolPlugin.js @@ -0,0 +1,227 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").DevTool} DevToolOptions */ +/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./NormalModule").SourceMap} SourceMap */ + +/** @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/). + */ +`); + +class EvalSourceMapDevToolPlugin { + /** + * @param {SourceMapDevToolPluginOptions|string} inputOptions Options object + */ + constructor(inputOptions) { + /** @type {SourceMapDevToolPluginOptions} */ + let options; + if (typeof inputOptions === "string") { + options = { + append: inputOptions + }; + } else { + options = inputOptions; + } + this.sourceMapComment = + options.append && typeof options.append !== "function" + ? options.append + : "//# sourceURL=[module]\n//# sourceMappingURL=[url]"; + this.moduleFilenameTemplate = + options.moduleFilenameTemplate || + "webpack://[namespace]/[resource-path]?[hash]"; + this.namespace = options.namespace || ""; + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap( + "EvalSourceMapDevToolPlugin", + compilation => { + const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); + new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation); + const matchModule = ModuleFilenameHelpers.matchObject.bind( + ModuleFilenameHelpers, + options + ); + hooks.renderModuleContent.tap( + "EvalSourceMapDevToolPlugin", + (source, m, { chunk, runtimeTemplate, chunkGraph }) => { + const cachedSource = cache.get(source); + if (cachedSource !== undefined) { + return cachedSource; + } + + /** + * @param {Source} r result + * @returns {Source} result + */ + const result = r => { + cache.set(source, r); + return r; + }; + + if (m instanceof NormalModule) { + const module = /** @type {NormalModule} */ (m); + if (!matchModule(module.resource)) { + return result(source); + } + } else if (m instanceof ConcatenatedModule) { + const concatModule = /** @type {ConcatenatedModule} */ (m); + if (concatModule.rootModule instanceof NormalModule) { + const module = /** @type {NormalModule} */ ( + concatModule.rootModule + ); + if (!matchModule(module.resource)) { + return result(source); + } + } else { + return result(source); + } + } else { + return result(source); + } + + const namespace = compilation.getPath(this.namespace, { + chunk + }); + /** @type {SourceMap} */ + let sourceMap; + let content; + if (source.sourceAndMap) { + const sourceAndMap = source.sourceAndMap(options); + sourceMap = /** @type {SourceMap} */ (sourceAndMap.map); + content = sourceAndMap.source; + } else { + sourceMap = /** @type {SourceMap} */ (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 = /** @type {string} */ (compiler.options.context); + const root = compiler.root; + const modules = sourceMap.sources.map(source => { + if (!source.startsWith("webpack://")) return source; + source = makePathsAbsolute(context, source.slice(10), root); + 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.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 + )})` + : JSON.stringify(content + footer) + });` + ) + ); + } + ); + hooks.inlineInRuntimeBailout.tap( + "EvalDevToolModulePlugin", + () => "the eval-source-map devtool is used." + ); + hooks.render.tap( + "EvalSourceMapDevToolPlugin", + source => new ConcatSource(devtoolWarning, source) + ); + hooks.chunkHash.tap("EvalSourceMapDevToolPlugin", (chunk, hash) => { + hash.update("EvalSourceMapDevToolPlugin"); + hash.update("2"); + }); + if (compilation.outputOptions.trustedTypes) { + compilation.hooks.additionalModuleRuntimeRequirements.tap( + "EvalSourceMapDevToolPlugin", + (module, set, context) => { + set.add(RuntimeGlobals.createScript); + } + ); + } + } + ); + } +} + +module.exports = EvalSourceMapDevToolPlugin; diff --git a/webpack-lib/lib/ExportsInfo.js b/webpack-lib/lib/ExportsInfo.js new file mode 100644 index 00000000000..f55dcf2d92d --- /dev/null +++ b/webpack-lib/lib/ExportsInfo.js @@ -0,0 +1,1624 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { equals } = require("./util/ArrayHelpers"); +const SortableSet = require("./util/SortableSet"); +const makeSerializable = require("./util/makeSerializable"); +const { forEachRuntime } = require("./util/runtime"); + +/** @typedef {import("./Dependency").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ +/** @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 */ + +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"); + +class RestoreProvidedData { + constructor( + exports, + otherProvided, + otherCanMangleProvide, + otherTerminalBinding + ) { + this.exports = exports; + this.otherProvided = otherProvided; + this.otherCanMangleProvide = otherCanMangleProvide; + this.otherTerminalBinding = otherTerminalBinding; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize({ write }) { + write(this.exports); + write(this.otherProvided); + write(this.otherCanMangleProvide); + write(this.otherTerminalBinding); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RestoreProvidedData} RestoreProvidedData + */ + static deserialize({ read }) { + return new RestoreProvidedData(read(), read(), read(), read()); + } +} + +makeSerializable( + RestoreProvidedData, + "webpack/lib/ModuleGraph", + "RestoreProvidedData" +); + +/** @typedef {Map} Exports */ +/** @typedef {string | string[] | false} UsedName */ + +class ExportsInfo { + constructor() { + /** @type {Exports} */ + this._exports = new Map(); + this._otherExportsInfo = new ExportInfo(null); + this._sideEffectsOnlyInfo = new ExportInfo("*side effects only*"); + this._exportsAreOrdered = false; + /** @type {ExportsInfo=} */ + this._redirectTo = undefined; + } + + /** + * @returns {Iterable} all owned exports in any order + */ + get ownedExports() { + return this._exports.values(); + } + + /** + * @returns {Iterable} all owned exports in order + */ + get orderedOwnedExports() { + if (!this._exportsAreOrdered) { + this._sortExports(); + } + return this._exports.values(); + } + + /** + * @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(); + } + + /** + * @returns {Iterable} all exports in order + */ + get orderedExports() { + if (!this._exportsAreOrdered) { + this._sortExports(); + } + if (this._redirectTo !== undefined) { + 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(); + } + + /** + * @returns {ExportInfo} the export info of unlisted exports + */ + get otherExportsInfo() { + if (this._redirectTo !== undefined) + return this._redirectTo.otherExportsInfo; + return this._otherExportsInfo; + } + + /** + * @param {Exports} exports exports + * @private + */ + _sortExportsMap(exports) { + if (exports.size > 1) { + 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; + } + + /** + * @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()) { + if (exportInfo.provided === undefined) { + exportInfo.provided = false; + } + if (exportInfo.canMangleProvide === undefined) { + exportInfo.canMangleProvide = true; + } + } + if (this._redirectTo !== undefined) { + this._redirectTo.setHasProvideInfo(); + } else { + if (this._otherExportsInfo.provided === undefined) { + this._otherExportsInfo.provided = false; + } + if (this._otherExportsInfo.canMangleProvide === undefined) { + this._otherExportsInfo.canMangleProvide = true; + } + } + } + + setHasUseInfo() { + for (const exportInfo of this._exports.values()) { + exportInfo.setHasUseInfo(); + } + this._sideEffectsOnlyInfo.setHasUseInfo(); + if (this._redirectTo !== undefined) { + this._redirectTo.setHasUseInfo(); + } else { + this._otherExportsInfo.setHasUseInfo(); + } + } + + /** + * @param {string} 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; + } + + /** + * @param {string} 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; + } + + /** + * @param {string} 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; + } + + /** + * @param {string[]} name export name + * @returns {ExportInfo | undefined} export info for this name + */ + getReadOnlyExportInfoRecursive(name) { + const exportInfo = this.getReadOnlyExportInfo(name[0]); + if (name.length === 1) return exportInfo; + if (!exportInfo.exportsInfo) return; + return exportInfo.exportsInfo.getReadOnlyExportInfoRecursive(name.slice(1)); + } + + /** + * @param {string[]=} name the export name + * @returns {ExportsInfo | undefined} the nested exports info + */ + getNestedExportsInfo(name) { + if (Array.isArray(name) && name.length > 0) { + const info = this.getReadOnlyExportInfo(name[0]); + if (!info.exportsInfo) return; + return info.exportsInfo.getNestedExportsInfo(name.slice(1)); + } + return this; + } + + /** + * @param {boolean=} canMangle true, if exports can still be mangled (defaults to false) + * @param {Set=} excludeExports list of unaffected exports + * @param {any=} 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; + } + + /** + * @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.setUsedConditionally( + used => used < UsageState.Unknown, + UsageState.Unknown, + runtime + ) + ) { + changed = true; + } + if (this._otherExportsInfo.canMangleUse !== false) { + this._otherExportsInfo.canMangleUse = false; + changed = true; + } + } + return changed; + } + + /** + * @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.setUsed(UsageState.NoInfo, runtime)) { + changed = true; + } + if (this._otherExportsInfo.canMangleUse !== false) { + this._otherExportsInfo.canMangleUse = false; + changed = true; + } + } + return changed; + } + + /** + * @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; + } + + /** + * @param {RuntimeSpec} runtime the runtime + * @returns {boolean} true, when something changed + */ + setUsedForSideEffectsOnly(runtime) { + return this._sideEffectsOnlyInfo.setUsedConditionally( + used => used === UsageState.Unused, + UsageState.Used, + runtime + ); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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) { + // eslint-disable-next-line no-constant-binary-expression + if (!this._redirectTo !== undefined) { + switch (this._otherExportsInfo.getUsed(runtime)) { + case UsageState.NoInfo: + return null; + case UsageState.Unknown: + case UsageState.OnlyPropertiesUsed: + case UsageState.Used: + return true; + } + } + 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)); + } + + /** + * @returns {null | true | string[]} list of exports when known + */ + getProvidedExports() { + // eslint-disable-next-line no-constant-binary-expression + if (!this._redirectTo !== undefined) { + switch (this._otherExportsInfo.provided) { + case undefined: + return null; + case null: + return true; + case true: + return true; + } + } + 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; + } + + /** + * @param {RuntimeSpec} runtime the runtime + * @returns {ExportInfo[]} exports that are relevant (not unused and potential provided) + */ + getRelevantExports(runtime) { + 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; + } + + /** + * @param {string | string[]} name the name of the export + * @returns {boolean | undefined | null} if the export is provided + */ + isExportProvided(name) { + if (Array.isArray(name)) { + const info = this.getReadOnlyExportInfo(name[0]); + if (info.exportsInfo && name.length > 1) { + return info.exportsInfo.isExportProvided(name.slice(1)); + } + return info.provided ? name.length === 1 || undefined : info.provided; + } + const info = this.getReadOnlyExportInfo(name); + return info.provided; + } + + /** + * @param {RuntimeSpec} runtime runtime + * @returns {string} key representing the usage + */ + getUsageKey(runtime) { + 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("|"); + } + + /** + * @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; + } + + /** + * @param {string | string[]} 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); + const info = this.getReadOnlyExportInfo(name[0]); + if (info.exportsInfo && name.length > 1) { + return info.exportsInfo.getUsed(name.slice(1), runtime); + } + return info.getUsed(runtime); + } + const info = this.getReadOnlyExportInfo(name); + return info.getUsed(runtime); + } + + /** + * @param {string | string[]} 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)) { + // TODO improve this + if (name.length === 0) { + if (!this.isUsed(runtime)) return false; + return name; + } + const info = this.getReadOnlyExportInfo(name[0]); + const x = info.getUsedName(name[0], runtime); + if (x === false) return false; + const arr = + /** @type {string[]} */ + (x === name[0] && name.length === 1 ? name : [x]); + if (name.length === 1) { + return arr; + } + if ( + info.exportsInfo && + info.getUsed(runtime) === UsageState.OnlyPropertiesUsed + ) { + const nested = info.exportsInfo.getUsedName(name.slice(1), runtime); + if (!nested) return false; + return arr.concat(nested); + } + return arr.concat(name.slice(1)); + } + const info = this.getReadOnlyExportInfo(name); + const usedName = info.getUsedName(name, runtime); + return usedName; + } + + /** + * @param {Hash} hash the hash + * @param {RuntimeSpec} runtime the runtime + * @returns {void} + */ + updateHash(hash, runtime) { + this._updateHash(hash, runtime, new Set()); + } + + /** + * @param {Hash} hash the hash + * @param {RuntimeSpec} runtime the runtime + * @param {Set} alreadyVisitedExportsInfo for circular references + * @returns {void} + */ + _updateHash(hash, runtime, alreadyVisitedExportsInfo) { + const set = new Set(alreadyVisitedExportsInfo); + set.add(this); + for (const exportInfo of this.orderedExports) { + if (exportInfo.hasInfo(this._otherExportsInfo, runtime)) { + exportInfo._updateHash(hash, runtime, set); + } + } + this._sideEffectsOnlyInfo._updateHash(hash, runtime, set); + this._otherExportsInfo._updateHash(hash, runtime, set); + if (this._redirectTo !== undefined) { + this._redirectTo._updateHash(hash, runtime, set); + } + } + + /** + * @returns {RestoreProvidedData} restore provided data + */ + getRestoreProvidedData() { + const otherProvided = this._otherExportsInfo.provided; + const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide; + const otherTerminalBinding = this._otherExportsInfo.terminalBinding; + const exports = []; + for (const exportInfo of this.orderedExports) { + if ( + exportInfo.provided !== otherProvided || + exportInfo.canMangleProvide !== otherCanMangleProvide || + exportInfo.terminalBinding !== otherTerminalBinding || + exportInfo.exportsInfoOwned + ) { + exports.push({ + name: exportInfo.name, + provided: exportInfo.provided, + canMangleProvide: exportInfo.canMangleProvide, + terminalBinding: exportInfo.terminalBinding, + exportsInfo: exportInfo.exportsInfoOwned + ? /** @type {NonNullable} */ + (exportInfo.exportsInfo).getRestoreProvidedData() + : undefined + }); + } + } + return new RestoreProvidedData( + exports, + otherProvided, + otherCanMangleProvide, + otherTerminalBinding + ); + } + + /** + * @param {{ otherProvided: any, otherCanMangleProvide: any, otherTerminalBinding: any, exports: any }} 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.terminalBinding = exp.terminalBinding; + if (exp.exportsInfo) { + const exportsInfo = exportInfo.createNestedExportsInfo(); + exportsInfo.restoreProvided(exp.exportsInfo); + } + } + if (wasEmpty) this._exportsAreOrdered = true; + } +} + +/** @typedef {{ module: Module, export: string[] }} TargetItemWithoutConnection */ +/** @typedef {{ module: Module, connection: ModuleGraphConnection, export: string[] | undefined }} TargetItem */ +/** @typedef {Map} Target */ + +class ExportInfo { + /** + * @param {string} name the original name of the export + * @param {ExportInfo=} initFrom init values from this ExportInfo + */ + constructor(name, initFrom) { + /** @type {string} */ + this.name = name; + /** + * @private + * @type {string | null} + */ + this._usedName = initFrom ? initFrom._usedName : null; + /** + * @private + * @type {UsageStateType | undefined} + */ + this._globalUsed = initFrom ? initFrom._globalUsed : undefined; + /** + * @private + * @type {Map} + */ + 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 {boolean | null | 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; + /** + * 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; + /** @type {boolean} */ + this.exportsInfoOwned = false; + /** @type {ExportsInfo | undefined} */ + this.exportsInfo = undefined; + /** @type {Target | undefined} */ + this._target = undefined; + if (initFrom && initFrom._target) { + this._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; + } + + // TODO webpack 5 remove + /** + * @private + * @param {*} v v + */ + set used(v) { + throw new Error("REMOVED"); + } + + // TODO webpack 5 remove + /** @private */ + get used() { + throw new Error("REMOVED"); + } + + // TODO webpack 5 remove + /** + * @private + * @param {*} v v + */ + set usedName(v) { + throw new Error("REMOVED"); + } + + // TODO webpack 5 remove + /** @private */ + get usedName() { + throw new Error("REMOVED"); + } + + 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}` + ); + } + + /** + * @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; + } + return changed; + } + + /** + * @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; + } + return changed; + } + + setHasUseInfo() { + if (!this._hasUseInRuntimeInfo) { + this._hasUseInRuntimeInfo = true; + } + if (this.canMangleUse === undefined) { + this.canMangleUse = true; + } + if (this.exportsInfoOwned) { + /** @type {ExportsInfo} */ + (this.exportsInfo).setHasUseInfo(); + } + } + + /** + * @param {function(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 => + this._usedInRuntime.set(/** @type {string} */ (runtime), newValue) + ); + return true; + } + } else { + let changed = false; + forEachRuntime(runtime, _runtime => { + const runtime = /** @type {string} */ (_runtime); + let oldValue = + /** @type {UsageStateType} */ + (this._usedInRuntime.get(runtime)); + if (oldValue === undefined) oldValue = UsageState.Unused; + if (newValue !== oldValue && condition(oldValue)) { + if (newValue === UsageState.Unused) { + this._usedInRuntime.delete(runtime); + } else { + this._usedInRuntime.set(runtime, newValue); + } + changed = true; + } + }); + if (changed) { + if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined; + return true; + } + } + return false; + } + + /** + * @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 => + this._usedInRuntime.set(/** @type {string} */ (runtime), newValue) + ); + return true; + } + } else { + let changed = false; + forEachRuntime(runtime, _runtime => { + const runtime = /** @type {string} */ (_runtime); + let oldValue = + /** @type {UsageStateType} */ + (this._usedInRuntime.get(runtime)); + if (oldValue === undefined) oldValue = UsageState.Unused; + if (newValue !== oldValue) { + if (newValue === UsageState.Unused) { + this._usedInRuntime.delete(runtime); + } else { + this._usedInRuntime.set(runtime, newValue); + } + changed = true; + } + }); + if (changed) { + if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined; + return true; + } + } + return false; + } + + /** + * @param {any} 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; + } + + /** + * @param {any} key the key + * @param {ModuleGraphConnection} connection the target module if a single one + * @param {(string[] | 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 = new Map(); + this._target.set(key, { + connection, + export: /** @type {string[]} */ (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 {string[]} */ (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 {string[]} */ (exportName); + oldTarget.priority = priority; + this._maxTarget = undefined; + return true; + } + return false; + } + + /** + * @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; + } + + /** + * get used name + * @param {string | undefined} fallbackName fallback name for used exports with no name + * @param {RuntimeSpec} runtime check usage for this runtime only + * @returns {string | 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 && + Array.from(runtime).every( + runtime => !this._usedInRuntime.has(runtime) + ) + ) { + return false; + } + } + } + if (this._usedName !== null) return this._usedName; + return /** @type {string | false} */ (this.name || fallbackName); + } + + /** + * @returns {boolean} true, when a mangled name of this export is set + */ + hasUsedName() { + return this._usedName !== null; + } + + /** + * Sets the mangled name of this export + * @param {string} name the new name + * @returns {void} + */ + setUsedName(name) { + this._usedName = name; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {function(TargetItem): boolean} 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 + 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; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {function(Module): boolean} 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()); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {function(Module): boolean} validTargetModuleFilter a valid target module + * @param {Set} 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 + }; + 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.concat(target.export.slice(1)) + : target.export.slice(1) + }; + } + } + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target + * @returns {TargetItem | undefined} the target + */ + getTarget(moduleGraph, resolveTargetFilter = RETURNS_TRUE) { + const result = this._getTarget(moduleGraph, resolveTargetFilter, undefined); + if (result === CIRCULAR) return; + return result; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target + * @param {Set | undefined} alreadyVisited set of already visited export info to avoid circular references + * @returns {TargetItem | CIRCULAR | undefined} the target + */ + _getTarget(moduleGraph, resolveTargetFilter, alreadyVisited) { + /** + * @param {TargetItem | null} inputTarget unresolved target + * @param {Set} alreadyVisited set of already visited export info to avoid circular references + * @returns {TargetItem | 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 {TargetItem} */ + 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.concat( + /** @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 {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target + * @param {function(TargetItem): ModuleGraphConnection=} updateOriginalConnection updates the original connection instead of using the target connection + * @returns {TargetItem | 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 {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, + export: /** @type {NonNullable} */ (target.export), + priority: 0 + }); + return target; + } + + /** + * @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; + } + + /** + * @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) + ); + } + + /** + * @param {Hash} hash the hash + * @param {RuntimeSpec} runtime the runtime + * @returns {void} + */ + updateHash(hash, runtime) { + this._updateHash(hash, runtime, new Set()); + } + + /** + * @param {Hash} hash the hash + * @param {RuntimeSpec} runtime the runtime + * @param {Set} alreadyVisitedExportsInfo for circular references + */ + _updateHash(hash, runtime, alreadyVisitedExportsInfo) { + hash.update( + `${this._usedName || this.name}${this.getUsed(runtime)}${this.provided}${ + this.terminalBinding + }` + ); + 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.UsageState = UsageState; diff --git a/webpack-lib/lib/ExportsInfoApiPlugin.js b/webpack-lib/lib/ExportsInfoApiPlugin.js new file mode 100644 index 00000000000..faf4594bbd0 --- /dev/null +++ b/webpack-lib/lib/ExportsInfoApiPlugin.js @@ -0,0 +1,87 @@ +/* + 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 { + /** + * Apply the plugin + * @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() + ); + /** + * @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/webpack-lib/lib/ExternalModule.js b/webpack-lib/lib/ExternalModule.js new file mode 100644 index 00000000000..c3fa3357ada --- /dev/null +++ b/webpack-lib/lib/ExternalModule.js @@ -0,0 +1,999 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { OriginalSource, RawSource } = require("webpack-sources"); +const ConcatenationScope = require("./ConcatenationScope"); +const EnvironmentNotSupportAsyncWarning = require("./EnvironmentNotSupportAsyncWarning"); +const { UsageState } = require("./ExportsInfo"); +const InitFragment = require("./InitFragment"); +const Module = require("./Module"); +const { + JS_TYPES, + CSS_URL_TYPES, + CSS_IMPORT_TYPES +} = require("./ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const Template = require("./Template"); +const StaticExportsDependency = require("./dependencies/StaticExportsDependency"); +const createHash = require("./util/createHash"); +const extractUrlAndGlobal = require("./util/extractUrlAndGlobal"); +const makeSerializable = require("./util/makeSerializable"); +const propertyAccess = require("./util/propertyAccess"); +const { register } = require("./util/serialization"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./ExportsInfo")} ExportsInfo */ +/** @typedef {import("./Generator").GenerateContext} GenerateContext */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ +/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./util/Hash")} Hash */ +/** @typedef {typeof import("./util/Hash")} HashConstructor */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** @typedef {{ attributes?: ImportAttributes, externalType: "import" | "module" | undefined }} ImportDependencyMeta */ +/** @typedef {{ layer?: string, supports?: string, media?: string }} CssImportDependencyMeta */ +/** @typedef {{ sourceType: "css-url" }} AssetDependencyMeta */ + +/** @typedef {ImportDependencyMeta | CssImportDependencyMeta | AssetDependencyMeta} DependencyMeta */ + +/** + * @typedef {object} SourceData + * @property {boolean=} iife + * @property {string=} init + * @property {string} expression + * @property {InitFragment[]=} chunkInitFragments + * @property {ReadOnlyRuntimeRequirements=} runtimeRequirements + */ + +const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); +const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([RuntimeGlobals.loadScript]); +const RUNTIME_REQUIREMENTS_FOR_MODULE = new Set([ + RuntimeGlobals.definePropertyGetters +]); +const EMPTY_RUNTIME_REQUIREMENTS = new Set([]); + +/** + * @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}` + }; +}; + +/** + * @param {string|string[]} 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 + )}` + }; +}; + +/** + * @param {string|string[]} moduleAndSpecifiers the module request + * @param {string} importMetaName import.meta name + * @param {boolean} needPrefix need to use `node:` prefix for `module` import + * @returns {SourceData} the generated source + */ +const getSourceForCommonJsExternalInNodeModule = ( + moduleAndSpecifiers, + importMetaName, + needPrefix +) => { + const chunkInitFragments = [ + new InitFragment( + `import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "${ + needPrefix ? "node:" : "" + }module";\n`, + InitFragment.STAGE_HARMONY_IMPORTS, + 0, + "external module node-commonjs" + ) + ]; + if (!Array.isArray(moduleAndSpecifiers)) { + return { + chunkInitFragments, + expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify( + moduleAndSpecifiers + )})` + }; + } + const moduleName = moduleAndSpecifiers[0]; + return { + chunkInitFragments, + expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify( + moduleName + )})${propertyAccess(moduleAndSpecifiers, 1)}` + }; +}; + +/** + * @param {string|string[]} 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 importName = runtimeTemplate.outputOptions.importFunctionName; + if ( + !runtimeTemplate.supportsDynamicImport() && + (importName === "import" || importName === "module-import") + ) { + throw new Error( + "The target environment doesn't support 'import()' so it's not possible to use external type 'import'" + ); + } + 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" + )});` + }; +}; + +/** + * @param {string} key key + * @param {any | undefined} value value + * @returns {undefined | string} replaced value + */ +const importAssertionReplacer = (key, value) => { + if (key === "_isLegacyAssert") { + return; + } + + return value; +}; + +/** + * @extends {InitFragment} + */ +class ModuleExternalInitFragment extends InitFragment { + /** + * @param {string} request import source + * @param {string=} ident recomputed ident + * @param {ImportDependencyMeta=} dependencyMeta the dependency meta + * @param {string | HashConstructor=} hashFunction the hash function to use + */ + constructor(request, ident, dependencyMeta, hashFunction = "md4") { + if (ident === undefined) { + ident = Template.toIdentifier(request); + if (ident !== request) { + ident += `_${createHash(hashFunction) + .update(request) + .digest("hex") + .slice(0, 8)}`; + } + } + const identifier = `__WEBPACK_EXTERNAL_MODULE_${ident}__`; + super( + `import * as ${identifier} from ${JSON.stringify(request)}${ + dependencyMeta && dependencyMeta.attributes + ? dependencyMeta.attributes._isLegacyAssert + ? ` assert ${JSON.stringify( + dependencyMeta.attributes, + importAssertionReplacer + )}` + : ` with ${JSON.stringify(dependencyMeta.attributes)}` + : "" + };\n`, + InitFragment.STAGE_HARMONY_IMPORTS, + 0, + `external module import ${ident}` + ); + this._ident = ident; + this._request = request; + this._dependencyMeta = request; + this._identifier = identifier; + } + + getNamespaceIdentifier() { + return this._identifier; + } +} + +register( + ModuleExternalInitFragment, + "webpack/lib/ExternalModule", + "ModuleExternalInitFragment", + { + serialize(obj, { write }) { + write(obj._request); + write(obj._ident); + write(obj._dependencyMeta); + }, + deserialize({ read }) { + return new ModuleExternalInitFragment(read(), read(), read()); + } + } +); + +/** + * @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) { + 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(", ")} })`; + } +}; + +/** + * @param {string|string[]} 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 + * @returns {SourceData} the generated source + */ +const getSourceForModuleExternal = ( + moduleAndSpecifiers, + exportsInfo, + runtime, + runtimeTemplate, + dependencyMeta +) => { + if (!Array.isArray(moduleAndSpecifiers)) + moduleAndSpecifiers = [moduleAndSpecifiers]; + const initFragment = new ModuleExternalInitFragment( + moduleAndSpecifiers[0], + undefined, + dependencyMeta, + runtimeTemplate.outputOptions.hashFunction + ); + const baseAccess = `${initFragment.getNamespaceIdentifier()}${propertyAccess( + moduleAndSpecifiers, + 1 + )}`; + const moduleRemapping = generateModuleRemapping( + baseAccess, + exportsInfo, + runtime, + runtimeTemplate + ); + const expression = moduleRemapping || baseAccess; + return { + expression, + init: moduleRemapping + ? `var x = ${runtimeTemplate.basicFunction( + "y", + `var x = {}; ${RuntimeGlobals.definePropertyGetters}(x, y); return x` + )} \nvar y = ${runtimeTemplate.returningFunction( + runtimeTemplate.returningFunction("x"), + "x" + )}` + : undefined, + runtimeRequirements: moduleRemapping + ? RUNTIME_REQUIREMENTS_FOR_MODULE + : undefined, + chunkInitFragments: [initFragment] + }; +}; + +/** + * @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: "var __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();`, + "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", + "var 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 + }; +}; + +/** + * @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`; + +/** + * @param {string|number} 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 + }; +}; + +/** + * @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 */ + +class ExternalModule extends Module { + /** + * @param {string | string[] | RequestRecord} request request + * @param {string} type type + * @param {string} userRequest user request + * @param {DependencyMeta=} dependencyMeta dependency meta + */ + constructor(request, type, userRequest, dependencyMeta) { + super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); + + // Info from Factory + /** @type {string | string[] | Record} */ + this.request = request; + /** @type {string} */ + this.externalType = type; + /** @type {string} */ + this.userRequest = userRequest; + /** @type {DependencyMeta=} */ + this.dependencyMeta = dependencyMeta; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + if ( + this.externalType === "asset" && + this.dependencyMeta && + /** @type {AssetDependencyMeta} */ + (this.dependencyMeta).sourceType === "css-url" + ) { + return CSS_URL_TYPES; + } else if (this.externalType === "css-import") { + return CSS_IMPORT_TYPES; + } + + return JS_TYPES; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return this.userRequest; + } + + /** + * @param {Chunk} chunk the chunk which condition should be checked + * @param {Compilation} compilation the compilation + * @returns {boolean} true, if the chunk is ok for the module + */ + chunkCondition(chunk, { chunkGraph }) { + return this.externalType === "css-import" + ? true + : chunkGraph.getNumberOfEntryModules(chunk) > 0; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `external ${this._resolveExternalType(this.externalType)} ${JSON.stringify(this.request)}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `external ${JSON.stringify(this.request)}`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = { + async: false, + exportsType: undefined + }; + this.buildInfo = { + strict: true, + topLevelDeclarations: new Set(), + module: 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.module) { + 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(); + } + + /** + * restore unsafe cache data + * @param {object} unsafeCacheData data from getUnsafeCacheData + * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching + */ + restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) { + this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory); + } + + /** + * @param {ConcatenationBailoutReasonContext} context context + * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated + */ + getConcatenationBailoutReason({ moduleGraph }) { + switch (this.externalType) { + case "amd": + case "amd-require": + case "umd": + case "umd2": + case "system": + case "jsonp": + return `${this.externalType} externals can't be concatenated`; + } + return undefined; + } + + _getRequestAndExternalType() { + let { request, externalType } = this; + if (typeof request === "object" && !Array.isArray(request)) + request = request[externalType]; + externalType = this._resolveExternalType(externalType); + return { request, externalType }; + } + + /** + * Resolve the detailed external type from the raw external type. + * e.g. resolve "module" or "import" from "module-import" type + * @param {string} externalType raw external type + * @returns {string} 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; + } + + return "asset"; + } + + return externalType; + } + + /** + * @private + * @param {string | string[]} request request + * @param {string} 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 + * @returns {SourceData} the source data + */ + _getSourceData( + request, + externalType, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime, + dependencyMeta + ) { + switch (externalType) { + case "this": + case "window": + case "self": + return getSourceForGlobalVariableExternal(request, this.externalType); + case "global": + return getSourceForGlobalVariableExternal( + request, + runtimeTemplate.globalObject + ); + case "commonjs": + case "commonjs2": + case "commonjs-module": + case "commonjs-static": + return getSourceForCommonJsExternal(request); + case "node-commonjs": + return /** @type {BuildInfo} */ (this.buildInfo).module + ? getSourceForCommonJsExternalInNodeModule( + request, + /** @type {string} */ + (runtimeTemplate.outputOptions.importMetaName), + /** @type {boolean} */ + (runtimeTemplate.supportNodePrefixForCoreModules()) + ) + : getSourceForCommonJsExternal(request); + case "amd": + case "amd-require": + case "umd": + case "umd2": + 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).module)) { + 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) + ); + } + case "var": + case "promise": + case "const": + case "let": + case "assign": + default: + return getSourceForDefaultCase( + this.isOptional(moduleGraph), + request, + runtimeTemplate + ); + } + } + + /** + * @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": { + const sources = new Map(); + sources.set( + "javascript", + new RawSource(`module.exports = ${JSON.stringify(request)};`) + ); + const data = new Map(); + data.set("url", { javascript: request }); + return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data }; + } + case "css-url": { + const sources = new Map(); + const data = new Map(); + data.set("url", { "css-url": request }); + return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data }; + } + case "css-import": { + 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 + ); + + let sourceString = sourceData.expression; + if (sourceData.iife) + sourceString = `(function() { return ${sourceString}; }())`; + if (concatenationScope) { + sourceString = `${ + runtimeTemplate.supportsConst() ? "const" : "var" + } ${ConcatenationScope.NAMESPACE_OBJECT_EXPORT} = ${sourceString};`; + concatenationScope.registerNamespaceExport( + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + ); + } else { + sourceString = `module.exports = ${sourceString};`; + } + if (sourceData.init) + sourceString = `${sourceData.init}\n${sourceString}`; + + let data; + if (sourceData.chunkInitFragments) { + data = new Map(); + data.set("chunkInitFragments", sourceData.chunkInitFragments); + } + + const sources = new Map(); + if (this.useSourceMap || this.useSimpleSourceMap) { + sources.set( + "javascript", + new OriginalSource(sourceString, this.identifier()) + ); + } else { + sources.set("javascript", 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 + }; + } + } + } + + /** + * @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; + } + + /** + * @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 + )}` + ); + super.updateHash(hash, context); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.request); + write(this.externalType); + write(this.userRequest); + write(this.dependencyMeta); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.request = read(); + this.externalType = read(); + this.userRequest = read(); + this.dependencyMeta = read(); + + super.deserialize(context); + } +} + +makeSerializable(ExternalModule, "webpack/lib/ExternalModule"); + +module.exports = ExternalModule; diff --git a/webpack-lib/lib/ExternalModuleFactoryPlugin.js b/webpack-lib/lib/ExternalModuleFactoryPlugin.js new file mode 100644 index 00000000000..853a88c0217 --- /dev/null +++ b/webpack-lib/lib/ExternalModuleFactoryPlugin.js @@ -0,0 +1,326 @@ +/* + 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 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 { resolveByProperty, cachedSetProperty } = require("./util/cleverMerge"); + +/** @typedef {import("../declarations/WebpackOptions").ExternalItemFunctionData} ExternalItemFunctionData */ +/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ +/** @typedef {import("./Compilation").DepConstructor} DepConstructor */ +/** @typedef {import("./ExternalModule").DependencyMeta} DependencyMeta */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ + +const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /; +const EMPTY_RESOLVE_OPTIONS = {}; + +// TODO webpack 6 remove this +const callDeprecatedExternals = util.deprecate( + /** + * @param {TODO} externalsFunction externals function + * @param {string} context context + * @param {string} request request + * @param {(err: Error | null | undefined, value: ExternalValue | undefined, ty: ExternalType | 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" +); + +const cache = new WeakMap(); + +/** + * @template {object} T + * @param {T} obj obj + * @param {TODO} layer layer + * @returns {Omit} result + */ +const resolveLayer = (obj, layer) => { + let map = cache.get(/** @type {object} */ (obj)); + if (map === undefined) { + map = new Map(); + cache.set(/** @type {object} */ (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 */ +/** @typedef {string | undefined} ExternalType */ + +class ExternalModuleFactoryPlugin { + /** + * @param {string | undefined} type default external type + * @param {Externals} externals externals config + */ + constructor(type, externals) { + this.type = type; + this.externals = externals; + } + + /** + * @param {NormalModuleFactory} normalModuleFactory the normal module factory + * @returns {void} + */ + apply(normalModuleFactory) { + const globalType = this.type; + normalModuleFactory.hooks.factorize.tapAsync( + "ExternalModuleFactoryPlugin", + (data, callback) => { + const context = data.context; + const contextInfo = data.contextInfo; + const dependency = data.dependencies[0]; + const dependencyType = data.dependencyType; + + /** + * @param {ExternalValue} value the external config + * @param {ExternalType | undefined} type type of external + * @param {function((Error | null)=, ExternalModule=): void} callback callback + * @returns {void} + */ + const handleExternal = (value, type, callback) => { + if (value === false) { + // Not externals, fallback to original factory + return callback(); + } + /** @type {string | string[] | Record} */ + 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 = 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 = firstItem.slice(0, idx); + externalConfig = [ + firstItem.slice(idx + 1), + ...externalConfig.slice(1) + ]; + } + } + + const resolvedType = /** @type {string} */ (type || globalType); + + // 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.assertions, + externalType + }; + } else if (dependency instanceof CssImportDependency) { + dependencyMeta = { + layer: dependency.layer, + supports: dependency.supports, + media: dependency.media + }; + } + + if ( + resolvedType === "asset" && + dependency instanceof CssUrlDependency + ) { + dependencyMeta = { sourceType: "css-url" }; + } + + callback( + null, + new ExternalModule( + externalConfig, + resolvedType, + dependency.request, + dependencyMeta + ) + ); + }; + + /** + * @param {Externals} externals externals config + * @param {function((Error | null)=, ExternalModule=): void} callback callback + * @returns {void} + */ + const handleExternals = (externals, callback) => { + if (typeof externals === "string") { + if (externals === dependency.request) { + return handleExternal(dependency.request, undefined, callback); + } + } else if (Array.isArray(externals)) { + let i = 0; + const next = () => { + /** @type {boolean | undefined} */ + let asyncFlag; + /** + * @param {(Error | null)=} err err + * @param {ExternalModule=} module module + * @returns {void} + */ + const handleExternalsAndCallback = (err, module) => { + if (err) return callback(err); + if (!module) { + if (asyncFlag) { + asyncFlag = false; + return; + } + return next(); + } + callback(null, module); + }; + + do { + asyncFlag = true; + if (i >= externals.length) return callback(); + handleExternals(externals[i++], handleExternalsAndCallback); + } while (!asyncFlag); + asyncFlag = false; + }; + + next(); + return; + } else if (externals instanceof RegExp) { + if (externals.test(dependency.request)) { + return handleExternal(dependency.request, undefined, callback); + } + } else if (typeof externals === "function") { + /** + * @param {Error | null | undefined} err err + * @param {ExternalValue=} value value + * @param {ExternalType=} 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) => { + 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, + /** @type {TODO} */ + (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") { + const resolvedExternals = resolveLayer( + externals, + contextInfo.issuerLayer + ); + if ( + Object.prototype.hasOwnProperty.call( + resolvedExternals, + dependency.request + ) + ) { + return handleExternal( + resolvedExternals[dependency.request], + undefined, + callback + ); + } + } + callback(); + }; + + handleExternals(this.externals, callback); + } + ); + } +} +module.exports = ExternalModuleFactoryPlugin; diff --git a/webpack-lib/lib/ExternalsPlugin.js b/webpack-lib/lib/ExternalsPlugin.js new file mode 100644 index 00000000000..01e74690777 --- /dev/null +++ b/webpack-lib/lib/ExternalsPlugin.js @@ -0,0 +1,37 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin"); + +/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ +/** @typedef {import("./Compiler")} Compiler */ + +class ExternalsPlugin { + /** + * @param {string | undefined} type default external type + * @param {Externals} externals externals config + */ + constructor(type, externals) { + this.type = type; + this.externals = externals; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => { + new ExternalModuleFactoryPlugin(this.type, this.externals).apply( + normalModuleFactory + ); + }); + } +} + +module.exports = ExternalsPlugin; diff --git a/webpack-lib/lib/FalseIIFEUmdWarning.js b/webpack-lib/lib/FalseIIFEUmdWarning.js new file mode 100644 index 00000000000..79eaa54ae03 --- /dev/null +++ b/webpack-lib/lib/FalseIIFEUmdWarning.js @@ -0,0 +1,19 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Arka Pratim Chaudhuri @arkapratimc +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +class FalseIIFEUmdWarning extends WebpackError { + constructor() { + super(); + this.name = "FalseIIFEUmdWarning"; + this.message = + "Configuration:\nSetting 'output.iife' to 'false' is incompatible with 'output.library.type' set to 'umd'. This configuration may cause unexpected behavior, as UMD libraries are expected to use an IIFE (Immediately Invoked Function Expression) to support various module formats. Consider setting 'output.iife' to 'true' or choosing a different 'library.type' to ensure compatibility.\nLearn more: https://webpack.js.org/configuration/output/"; + } +} + +module.exports = FalseIIFEUmdWarning; diff --git a/webpack-lib/lib/FileSystemInfo.js b/webpack-lib/lib/FileSystemInfo.js new file mode 100644 index 00000000000..ed7f327a2c4 --- /dev/null +++ b/webpack-lib/lib/FileSystemInfo.js @@ -0,0 +1,4041 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { create: createResolver } = require("enhanced-resolve"); +const nodeModule = require("module"); +const asyncLib = require("neo-async"); +const { isAbsolute } = require("path"); +const AsyncQueue = require("./util/AsyncQueue"); +const StackedCacheMap = require("./util/StackedCacheMap"); +const createHash = require("./util/createHash"); +const { join, dirname, relative, lstatReadlinkAbsolute } = require("./util/fs"); +const makeSerializable = require("./util/makeSerializable"); +const processAsyncTree = require("./util/processAsyncTree"); + +/** @typedef {import("enhanced-resolve").Resolver} Resolver */ +/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */ +/** @typedef {import("enhanced-resolve").ResolveFunctionAsync} ResolveFunctionAsync */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./logging/Logger").Logger} Logger */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {typeof import("./util/Hash")} Hash */ +/** @typedef {import("./util/fs").IStats} IStats */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("./util/fs").PathLike} PathLike */ +/** @typedef {import("./util/fs").StringCallback} StringCallback */ +/** + * @template T + * @typedef {import("./util/AsyncQueue").Callback} ProcessorCallback + */ +/** + * @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; + +const EMPTY_SET = new Set(); + +const RBDT_RESOLVE_CJS = 0; +const RBDT_RESOLVE_ESM = 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_CJS | RBDT_RESOLVE_ESM | 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"); + +/** + * @typedef {object} FileSystemInfoEntry + * @property {number} safeTime + * @property {number=} timestamp + */ + +/** + * @typedef {object} ResolvedContextFileSystemInfoEntry + * @property {number} safeTime + * @property {string=} timestampHash + */ + +/** + * @typedef {object} ContextFileSystemInfoEntry + * @property {number} safeTime + * @property {string=} timestampHash + * @property {ResolvedContextFileSystemInfoEntry=} resolved + * @property {Set=} symlinks + */ + +/** + * @typedef {object} TimestampAndHash + * @property {number} safeTime + * @property {number=} timestamp + * @property {string} hash + */ + +/** + * @typedef {object} ResolvedContextTimestampAndHash + * @property {number} safeTime + * @property {string=} timestampHash + * @property {string} hash + */ + +/** @typedef {Set} Symlinks */ + +/** + * @typedef {object} ContextTimestampAndHash + * @property {number} safeTime + * @property {string=} timestampHash + * @property {string} hash + * @property {ResolvedContextTimestampAndHash=} resolved + * @property {Symlinks=} symlinks + */ + +/** + * @typedef {object} ContextHash + * @property {string} hash + * @property {string=} resolved + * @property {Symlinks=} symlinks + */ + +/** @typedef {Set} SnapshotContent */ + +/** + * @typedef {object} SnapshotOptimizationEntry + * @property {Snapshot} snapshot + * @property {number} shared + * @property {SnapshotContent | undefined} snapshotContent + * @property {Set | undefined} children + */ + +/** + * @typedef {object} ResolveBuildDependenciesResult + * @property {Set} files list of files + * @property {Set} directories list of directories + * @property {Set} missing list of missing entries + * @property {Map} resolveResults stored resolve results + * @property {object} resolveDependencies dependencies of the resolving + * @property {Set} resolveDependencies.files list of files + * @property {Set} resolveDependencies.directories list of directories + * @property {Set} resolveDependencies.missing list of missing entries + */ + +/** + * @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 { + /** + * @param {() => IteratorResult} next next + */ + constructor(next) { + this.next = next; + } +} + +/** + * @typedef {(snapshot: Snapshot) => (Map | Set | undefined)[]} GetMapsFunction + */ + +class 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 {(snapshot: Snapshot) => (Map | Set | undefined)[]} */ + let getMaps; + /** @type {(Map | Set | undefined)[]} */ + 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 = 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; + } + + /** + * @param {number} value start value + */ + setStartTime(value) { + this._flags = this._flags | 1; + this.startTime = value; + } + + /** + * @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; + } + + /** + * @param {FileTimestamps} value file timestamps + */ + setFileTimestamps(value) { + this._flags = this._flags | 2; + this.fileTimestamps = value; + } + + hasFileHashes() { + return (this._flags & 4) !== 0; + } + + /** + * @param {FileHashes} value file hashes + */ + setFileHashes(value) { + this._flags = this._flags | 4; + this.fileHashes = value; + } + + hasFileTshs() { + return (this._flags & 8) !== 0; + } + + /** + * @param {FileTshs} value file tshs + */ + setFileTshs(value) { + this._flags = this._flags | 8; + this.fileTshs = value; + } + + hasContextTimestamps() { + return (this._flags & 0x10) !== 0; + } + + /** + * @param {ContextTimestamps} value context timestamps + */ + setContextTimestamps(value) { + this._flags = this._flags | 0x10; + this.contextTimestamps = value; + } + + hasContextHashes() { + return (this._flags & 0x20) !== 0; + } + + /** + * @param {ContextHashes} value context hashes + */ + setContextHashes(value) { + this._flags = this._flags | 0x20; + this.contextHashes = value; + } + + hasContextTshs() { + return (this._flags & 0x40) !== 0; + } + + /** + * @param {ContextTshs} value context tshs + */ + setContextTshs(value) { + this._flags = this._flags | 0x40; + this.contextTshs = value; + } + + hasMissingExistence() { + return (this._flags & 0x80) !== 0; + } + + /** + * @param {MissingExistence} value context tshs + */ + setMissingExistence(value) { + this._flags = this._flags | 0x80; + this.missingExistence = value; + } + + hasManagedItemInfo() { + return (this._flags & 0x100) !== 0; + } + + /** + * @param {ManagedItemInfo} value managed item info + */ + setManagedItemInfo(value) { + this._flags = this._flags | 0x100; + this.managedItemInfo = value; + } + + hasManagedFiles() { + return (this._flags & 0x200) !== 0; + } + + /** + * @param {ManagedFiles} value managed files + */ + setManagedFiles(value) { + this._flags = this._flags | 0x200; + this.managedFiles = value; + } + + hasManagedContexts() { + return (this._flags & 0x400) !== 0; + } + + /** + * @param {ManagedContexts} value managed contexts + */ + setManagedContexts(value) { + this._flags = this._flags | 0x400; + this.managedContexts = value; + } + + hasManagedMissing() { + return (this._flags & 0x800) !== 0; + } + + /** + * @param {ManagedMissing} value managed missing + */ + setManagedMissing(value) { + this._flags = this._flags | 0x800; + this.managedMissing = value; + } + + hasChildren() { + return (this._flags & 0x1000) !== 0; + } + + /** + * @param {Children} value children + */ + setChildren(value) { + this._flags = this._flags | 0x1000; + this.children = value; + } + + /** + * @param {Snapshot} child children + */ + addChild(child) { + if (!this.hasChildren()) { + this.setChildren(new Set()); + } + /** @type {Children} */ + (this.children).add(child); + } + + /** + * @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); + } + + /** + * @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(); + } + + /** + * @param {GetMapsFunction} getMaps first + * @returns {Iterable} iterable + */ + _createIterable(getMaps) { + return new SnapshotIterable(this, getMaps); + } + + /** + * @returns {Iterable} iterable + */ + getFileIterable() { + if (this._cachedFileIterable === undefined) { + this._cachedFileIterable = this._createIterable(s => [ + s.fileTimestamps, + s.fileHashes, + s.fileTshs, + s.managedFiles + ]); + } + return this._cachedFileIterable; + } + + /** + * @returns {Iterable} iterable + */ + getContextIterable() { + if (this._cachedContextIterable === undefined) { + this._cachedContextIterable = this._createIterable(s => [ + s.contextTimestamps, + s.contextHashes, + s.contextTshs, + s.managedContexts + ]); + } + return this._cachedContextIterable; + } + + /** + * @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; + +/** + * @template U, T + * @typedef {U extends true ? Set : Map} SnapshotOptimizationValue + */ + +/** + * @template T + * @template {boolean} [U=false] + */ +class SnapshotOptimization { + /** + * @param {function(Snapshot): boolean} has has value + * @param {function(Snapshot): SnapshotOptimizationValue | undefined} get get value + * @param {function(Snapshot, 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; + } + + /** + * @param {Snapshot} newSnapshot snapshot + * @param {Set} capturedFiles files to snapshot/share + * @returns {void} + */ + optimize(newSnapshot, capturedFiles) { + /** + * @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); + }; + /** + * @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); + continue; + } 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; + } + 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); + continue; + } + } + 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 + 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; + } + 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; + } +} + +/** + * @param {string} str input + * @returns {string} result + */ +const parseString = str => { + if (str[0] === "'" || str[0] === "`") + str = `"${str.slice(1, -1).replace(/"/g, '\\"')}"`; + return JSON.parse(str); +}; + +/* istanbul ignore next */ +/** + * @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; +}; + +/** + * @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; +}; + +/** + * @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); +}; + +/** + * @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; +}; + +/** + * @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; +}; + +/** + * @template T + * @param {Set} source source + * @param {Set} target target + */ +const addAll = (source, target) => { + for (const key of source) target.add(key); +}; + +/** @typedef {Set} LoggedPaths */ + +/** + * Used to access information about the filesystem in a cached way + */ +class 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 {string | Hash=} options.hashFunction the hash function to use + */ + constructor( + fs, + { + unmanagedPaths = [], + managedPaths = [], + immutablePaths = [], + logger, + hashFunction = "md4" + } = {} + ) { + 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 = Array.from(unmanagedPaths); + this.unmanagedPathsWithSlash = /** @type {string[]} */ ( + _unmanagedPaths.filter(p => typeof p === "string") + ).map(p => join(fs, p, "_").slice(0, -1)); + this.unmanagedPathsRegExps = /** @type {RegExp[]} */ ( + _unmanagedPaths.filter(p => typeof p !== "string") + ); + + this.managedPaths = Array.from(managedPaths); + this.managedPathsWithSlash = /** @type {string[]} */ ( + this.managedPaths.filter(p => typeof p === "string") + ).map(p => join(fs, p, "_").slice(0, -1)); + + this.managedPathsRegExps = /** @type {RegExp[]} */ ( + this.managedPaths.filter(p => typeof p !== "string") + ); + this.immutablePaths = Array.from(immutablePaths); + this.immutablePathsWithSlash = /** @type {string[]} */ ( + this.immutablePaths.filter(p => typeof p === "string") + ).map(p => join(fs, p, "_").slice(0, -1)); + this.immutablePathsRegExps = /** @type {RegExp[]} */ ( + 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); + /** + * @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() + ); + } + + /** + * @param {string} path path + * @param {string} reason reason + * @param {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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {string} path file path + * @param {function((WebpackError | null)=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function + * @returns {void} + */ + getFileTimestamp(path, callback) { + const cache = this._fileTimestamps.get(path); + if (cache !== undefined) return callback(null, cache); + this.fileTimestampQueue.add(path, callback); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, (ResolvedContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function + * @returns {void} + */ + getContextTimestamp(path, callback) { + const cache = this._contextTimestamps.get(path); + if (cache !== undefined) { + if (cache === "ignore") return callback(null, "ignore"); + const resolved = getResolvedTimestamp(cache); + if (resolved !== undefined) return callback(null, resolved); + return this._resolveContextTimestamp( + /** @type {ResolvedContextFileSystemInfoEntry} */ + (cache), + callback + ); + } + this.contextTimestampQueue.add(path, (err, _entry) => { + if (err) return callback(err); + const entry = /** @type {ContextFileSystemInfoEntry} */ (_entry); + const resolved = getResolvedTimestamp(entry); + if (resolved !== undefined) return callback(null, resolved); + this._resolveContextTimestamp(entry, callback); + }); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, (ContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function + * @returns {void} + */ + _getUnresolvedContextTimestamp(path, callback) { + const cache = this._contextTimestamps.get(path); + if (cache !== undefined) return callback(null, cache); + this.contextTimestampQueue.add(path, callback); + } + + /** + * @param {string} path file path + * @param {function((WebpackError | null)=, (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); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, 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); + }); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, (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); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, (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); + }); + } + + /** + * @param {string} path context path + * @param {function((WebpackError | null)=, (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", "node"], + exportsFields: ["exports"], + fileSystem: this.fs + }); + const resolveCjsAsChild = createResolver({ + extensions: [".js", ".json", ".node"], + conditionNames: ["require", "node"], + exportsFields: [], + fileSystem: this.fs + }); + const resolveEsm = createResolver({ + extensions: [".js", ".json", ".node"], + fullySpecified: true, + conditionNames: ["import", "node"], + exportsFields: ["exports"], + fileSystem: this.fs + }); + return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild }; + } + + /** + * @param {string} context context directory + * @param {Iterable} deps dependencies + * @param {function((Error | null)=, ResolveBuildDependenciesResult=): void} callback callback function + * @returns {void} + */ + resolveBuildDependencies(context, deps, callback) { + const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } = + this._createBuildDependenciesResolvers(); + + /** @type {Set} */ + const files = new Set(); + /** @type {Set} */ + const fileSymlinks = new Set(); + /** @type {Set} */ + const directories = new Set(); + /** @type {Set} */ + const directorySymlinks = new Set(); + /** @type {Set} */ + const missing = new Set(); + /** @type {Set} */ + const resolveFiles = new Set(); + /** @type {Set} */ + const resolveDirectories = new Set(); + /** @type {Set} */ + const resolveMissing = new Set(); + /** @type {Map} */ + const resolveResults = new Map(); + const invalidResolveResults = new Set(); + const resolverContext = { + fileDependencies: resolveFiles, + contextDependencies: resolveDirectories, + missingDependencies: resolveMissing + }; + /** + * @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 */ + + /** + * @param {Job} job job + * @returns {`resolve commonjs file ${string}${string}`|`resolve esm file ${string}${string}`|`resolve esm ${string}${string}`|`resolve directory ${string}`|`file ${string}`|`unknown ${string} ${string}`|`resolve commonjs ${string}${string}`|`directory ${string}`|`file dependencies ${string}`|`directory dependencies ${string}`} result + */ + const jobToString = job => { + switch (job.type) { + case RBDT_RESOLVE_CJS: + return `resolve commonjs ${job.path}${expectedToString( + job.expected + )}`; + case RBDT_RESOLVE_ESM: + return `resolve esm ${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}`; + }; + /** + * @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_CJS, + context, + path: dep, + expected: undefined, + issuer: undefined + }) + ), + 20, + (job, push, callback) => { + const { type, context, path, expected } = job; + /** + * @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(); + } + ); + }; + /** + * @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(); + } + ); + }; + switch (type) { + case RBDT_RESOLVE_CJS: { + const isDirectory = /[\\/]$/.test(path); + if (isDirectory) { + resolveDirectory(path.slice(0, -1)); + } else { + resolveFile(path, "f", resolveCjs); + } + break; + } + case RBDT_RESOLVE_ESM: { + const isDirectory = /[\\/]$/.test(path); + if (isDirectory) { + resolveDirectory(path.slice(0, -1)); + } else { + resolveFile(path); + } + break; + } + case RBDT_RESOLVE_DIRECTORY: { + resolveDirectory(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)) { + 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 = require("es-module-lexer"); + 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); + for (const imp of imports) { + try { + let dependency; + if (imp.d === -1) { + // import ... from "..." + dependency = parseString( + source.substring(imp.s - 1, imp.e + 1) + ); + } else if (imp.d > -1) { + // import() + const expr = source.substring(imp.s, imp.e).trim(); + dependency = parseString(expr); + } else { + // e.g. import.meta + continue; + } + + // we should not track Node.js build dependencies + if (dependency.startsWith("node:")) continue; + if (builtinModules.has(dependency)) continue; + + push({ + type: RBDT_RESOLVE_ESM_FILE, + context, + path: dependency, + expected: imp.d > -1 ? false : undefined, + issuer: job + }); + } catch (err1) { + logger.warn( + `Parsing of ${path} for build dependencies failed at 'import(${source.substring( + 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); + let packageData; + try { + packageData = JSON.parse( + /** @type {Buffer} */ (content).toString("utf-8") + ); + } catch (parseErr) { + return callback(/** @type {Error} */ (parseErr)); + } + const depsObject = packageData.dependencies; + const optionalDepsObject = packageData.optionalDependencies; + const allDeps = new 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 + } + }); + } + ); + } + + /** + * @param {Map} resolveResults results from resolving + * @param {function((Error | null)=, 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; + } + }, + /** + * @param {Error | typeof INVALID=} err error or invalid flag + * @returns {void} + */ + err => { + if (err === INVALID) { + return callback(null, false); + } + if (err) { + return callback(err); + } + return callback(null, true); + } + ); + } + + /** + * @param {number | null | undefined} startTime when processing the files has started + * @param {Iterable | null} files all files + * @param {Iterable | null} directories all directories + * @param {Iterable | null} missing all missing files or directories + * @param {SnapshotOptions | null | undefined} options options object (for future extensions) + * @param {function(WebpackError | null, 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); + } + }; + /** + * @param {string} path path + * @param {Set} 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; + }; + /** + * @param {Iterable} items items + * @param {Set} managedSet managed set + * @returns {Set} result + */ + const captureNonManaged = (items, managedSet) => { + const capturedItems = new Set(); + for (const path of items) { + if (!checkManaged(path, managedSet)) capturedItems.add(path); + } + return capturedItems; + }; + /** + * @param {Set} capturedFiles captured files + */ + const processCapturedFiles = capturedFiles => { + 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) { + if (cache !== "ignore") { + fileTimestamps.set(path, 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)); + } + /** + * @param {Set} capturedDirectories captured directories + */ + const processCapturedDirectories = capturedDirectories => { + 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++; + /** + * @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); + let resolved; + if ( + cache !== undefined && + (resolved = getResolvedHash(cache)) !== undefined + ) { + contextHashes.set(path, resolved); + } else { + jobs++; + /** + * @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; + let resolved; + if ( + cache !== undefined && + (resolved = getResolvedTimestamp(cache)) !== undefined + ) { + contextTimestamps.set(path, resolved); + } else { + jobs++; + /** + * @param {(Error | null)=} err error + * @param {(FileSystemInfoEntry | "ignore" | null)=} 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 {FileSystemInfoEntry | null} */ + (entry) + ); + jobDone(); + } + }; + if (cache !== undefined) { + this._resolveContextTimestamp( + /** @type {ContextFileSystemInfoEntry} */ + (cache), + callback + ); + } else { + this.getContextTimestamp(path, callback); + } + } + } + break; + } + }; + if (directories) { + processCapturedDirectories( + captureNonManaged(directories, managedContexts) + ); + } + /** + * @param {Set} capturedMissing captured missing + */ + const processCapturedMissing = capturedMissing => { + this._missingExistenceOptimization.optimize(snapshot, capturedMissing); + for (const path of capturedMissing) { + const cache = this._fileTimestamps.get(path); + if (cache !== undefined) { + 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 + /** + * @param {Set} set set + * @param {function(Set): void} fn fn + */ + const process = (set, fn) => { + if (set.size === 0) return; + 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(); + } + + /** + * @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; + } + + /** + * @param {Snapshot} snapshot the snapshot made + * @param {function((WebpackError | null)=, boolean=): void} 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); + } + + /** + * @param {Snapshot} snapshot the snapshot made + * @param {function((WebpackError | null)=, boolean=): void} 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); + } + }; + /** + * @param {string} path path + * @param {WebpackError} err err + */ + const invalidWithError = (path, err) => { + if (this._remainingLogs > 0) { + this._log(path, "error occurred: %s", err); + } + invalid(); + }; + /** + * @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; + }; + /** + * @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; + }; + /** + * @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; + }; + /** + * @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()) { + /** + * @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) { + if (cache !== "ignore" && !checkFile(path, 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(); + } + }); + } + } + } + /** + * @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) { + if (cache === "ignore" || !checkFile(path, 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; + let resolved; + if ( + cache !== undefined && + (resolved = getResolvedTimestamp(cache)) !== undefined + ) { + if (!checkContext(path, resolved, ts)) { + invalid(); + return; + } + } else { + jobs++; + /** + * @param {(WebpackError | null)=} err error + * @param {(ResolvedContextFileSystemInfoEntry | "ignore" | null)=} 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 (cache !== undefined) { + this._resolveContextTimestamp( + /** @type {ContextFileSystemInfoEntry} */ + (cache), + callback + ); + } else { + this.getContextTimestamp(path, callback); + } + } + } + } + /** + * @param {string} path path + * @param {string | null} hash hash + */ + const processContextHashSnapshot = (path, hash) => { + const cache = this._contextHashes.get(path); + let resolved; + if ( + cache !== undefined && + (resolved = getResolvedHash(cache)) !== undefined + ) { + if (!checkHash(path, resolved, hash)) { + invalid(); + } + } else { + jobs++; + /** + * @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; + let resolved; + if ( + cache !== undefined && + (resolved = getResolvedTimestamp(cache)) !== undefined + ) { + if ( + !checkContext( + path, + /** @type {ResolvedContextFileSystemInfoEntry | null} */ + (resolved), + tsh, + false + ) + ) { + processContextHashSnapshot(path, tsh && tsh.hash); + } + } else { + jobs++; + /** + * @param {(WebpackError | null)=} err error + * @param {(ResolvedContextFileSystemInfoEntry | "ignore" | null)=} entry entry + * @returns {void} + */ + const callback = (err, entry) => { + if (err) return invalidWithError(path, err); + if ( + !checkContext( + path, + // TODO: test with `"ignore"` + /** @type {ResolvedContextFileSystemInfoEntry | null} */ + (entry), + tsh, + false + ) + ) { + processContextHashSnapshot(path, tsh && tsh.hash); + } + jobDone(); + }; + if (cache !== undefined) { + this._resolveContextTimestamp( + /** @type {ContextFileSystemInfoEntry} */ + (cache), + 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) { + 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); + } + } + + /** + * @type {Processor} + * @private + */ + _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); + 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); + }); + } + + /** + * @type {Processor} + * @private + */ + _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 = /** @type {string} */ (hash.digest("hex")); + + this._fileHashes.set(path, digest); + + callback(null, digest); + }); + } + + /** + * @param {string} path path + * @param {function(WebpackError | null, TimestampAndHash=) : void} callback callback + * @private + */ + _getFileTimestampAndHash(path, callback) { + /** + * @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, /** @type {TODO} */ (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)); + }); + } + } + + /** + * @template T + * @template ItemType + * @param {object} options options + * @param {string} options.path path + * @param {function(string): ItemType} options.fromImmutablePath called when context item is an immutable path + * @param {function(string): ItemType} options.fromManagedItem called when context item is a managed path + * @param {function(string, string, function((WebpackError | null)=, ItemType=): void): void} options.fromSymlink called when context item is a symlink + * @param {function(string, IStats, function((WebpackError | null)=, (ItemType | null)=): void): void} options.fromFile called when context item is a file + * @param {function(string, IStats, function((WebpackError | null)=, ItemType=): void): void} options.fromDirectory called when context item is a directory + * @param {function(string[], ItemType[]): T} options.reduce called from all context items + * @param {function((Error | null)=, (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); + } + ); + }); + } + + /** + * @type {Processor} + * @private + */ + _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) + return callback(null, cache === "ignore" ? 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) => { + 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 = /** @type {string} */ (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); + } + ); + } + + /** + * @param {ContextFileSystemInfoEntry} entry entry + * @param {function((WebpackError | null)=, (ResolvedContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback + * @returns {void} + */ + _resolveContextTimestamp(entry, callback) { + /** @type {string[]} */ + const hashes = []; + let safeTime = 0; + 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) 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: /** @type {string} */ (hash.digest("hex")) + }) + ); + } + ); + } + + /** + * @type {Processor} + * @private + */ + _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 || ""); + }); + }, + /** + * @param {string[]} files files + * @param {(string | ContextHash)[]} fileHashes hashes + * @returns {ContextHash} reduced hash + */ + reduce: (files, fileHashes) => { + 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: /** @type {string} */ (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); + } + ); + } + + /** + * @param {ContextHash} entry context hash + * @param {function(WebpackError | null, string=): void} callback callback + * @returns {void} + */ + _resolveContextHash(entry, callback) { + /** @type {string[]} */ + const hashes = []; + 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) 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 = /** @type {string} */ (hash.digest("hex"))) + ); + } + ); + } + + /** + * @type {Processor} + * @private + */ + _readContextTimestampAndHash(path, callback) { + /** + * @param {ContextFileSystemInfoEntry | "ignore" | null} 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 | null} */ (null), + fromManagedItem: info => ({ + safeTime: 0, + timestampHash: info, + hash: info || "" + }), + fromSymlink: (file, target, callback) => { + callback( + null, + /** @type {TODO} */ + ({ + 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); + }); + }, + /** + * @param {string[]} files files + * @param {(Partial & Partial | string | null)[]} results results + * @returns {ContextTimestampAndHash} tsh + */ + reduce: (files, results) => { + 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: /** @type {string} */ (tsHash.digest("hex")), + hash: /** @type {string} */ (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); + } + ); + } + } + + /** + * @param {ContextTimestampAndHash} entry entry + * @param {ProcessorCallback} callback callback + * @returns {void} + */ + _resolveContextTsh(entry, callback) { + /** @type {string[]} */ + const hashes = []; + /** @type {string[]} */ + const tsHashes = []; + let safeTime = 0; + 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) 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: /** @type {string} */ (tsHash.digest("hex")), + hash: /** @type {string} */ (hash.digest("hex")) + }) + ); + } + ); + } + + /** + * @type {Processor>} + * @private + */ + _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); + }); + } + + /** + * @type {Processor} + * @private + */ + _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)); + } + let data; + try { + data = JSON.parse(/** @type {Buffer} */ (content).toString("utf-8")); + } 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; + const map = new Map(); + for (const [path, info] of this._fileTimestamps) { + if (info) map.set(path, typeof info === "object" ? info.safeTime : null); + } + return (this._cachedDeprecatedFileTimestamps = map); + } + + getDeprecatedContextTimestamps() { + if (this._cachedDeprecatedContextTimestamps !== undefined) + return this._cachedDeprecatedContextTimestamps; + const map = new Map(); + for (const [path, info] of this._contextTimestamps) { + if (info) map.set(path, typeof info === "object" ? info.safeTime : null); + } + return (this._cachedDeprecatedContextTimestamps = map); + } +} + +module.exports = FileSystemInfo; +module.exports.Snapshot = Snapshot; diff --git a/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js b/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js new file mode 100644 index 00000000000..eb3ee4cf43d --- /dev/null +++ b/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js @@ -0,0 +1,55 @@ +/* + 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 { + /** + * @param {string} explanation explanation + */ + constructor(explanation) { + this.explanation = explanation; + } + + /** + * Apply the plugin + * @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); + if (module.factoryMeta === undefined) { + module.factoryMeta = {}; + } + /** @type {FactoryMeta} */ + (module.factoryMeta).sideEffectFree = false; + } + }); + }); + } +} + +module.exports = FlagAllModulesAsUsedPlugin; diff --git a/webpack-lib/lib/FlagDependencyExportsPlugin.js b/webpack-lib/lib/FlagDependencyExportsPlugin.js new file mode 100644 index 00000000000..aacbb3d2789 --- /dev/null +++ b/webpack-lib/lib/FlagDependencyExportsPlugin.js @@ -0,0 +1,420 @@ +/* + 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"); + +/** @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("./Module")} Module */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ + +const PLUGIN_NAME = "FlagDependencyExportsPlugin"; +const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; + +class FlagDependencyExportsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + 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( + 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(); + } + // 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(); + } + const memCache = moduleMemCaches && moduleMemCaches.get(module); + const memCacheValue = memCache && memCache.get(this); + if (memCacheValue !== undefined) { + statRestoredFromMemCache++; + exportsInfo.restoreProvided(memCacheValue); + return callback(); + } + cache.get( + module.identifier(), + /** @type {BuildInfo} */ + (module.buildInfo).hash, + (err, result) => { + if (err) return callback(err); + + if (result !== undefined) { + statRestoredFromCache++; + exportsInfo.restoreProvided(result); + } else { + statNotCached++; + // Without cached info enqueue module for determining the exports + queue.enqueue(module); + exportsInfo.setHasProvideInfo(); + } + callback(); + } + ); + }, + 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; + + /** + * @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); + } + }; + + /** + * @param {Dependency} dep the dependency + * @returns {void} + */ + const processDependency = dep => { + const exportDesc = dep.getExports(moduleGraph); + if (!exportDesc) return; + exportsSpecsFromDependencies.set(dep, exportDesc); + }; + + /** + * @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 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) { + let name; + let canMangle = globalCanMangle; + let terminalBinding = globalTerminalBinding; + let exports; + let from = globalFrom; + let fromExport; + let priority = globalPriority; + let hidden = false; + 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.hidden !== undefined) + hidden = exportNameOrSpec.hidden; + } + const exportInfo = exportsInfo.getExportInfo(name); + + if ( + exportInfo.provided === false || + exportInfo.provided === null + ) { + exportInfo.provided = true; + changed = true; + } + + if ( + exportInfo.canMangleProvide !== false && + canMangle === false + ) { + exportInfo.canMangleProvide = false; + changed = true; + } + + if (terminalBinding && !exportInfo.terminalBinding) { + exportInfo.terminalBinding = 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); + 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); + moduleGraph.unfreeze(); + for (const [dep, exportsSpec] of exportsSpecsFromDependencies) { + processExportsSpec(dep, exportsSpec); + } + + if (cacheable) { + modulesToStore.add(module); + } + + if (changed) { + notifyDependencies(); + } + } + 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); + } + ); + } + ); + } + ); + + /** @type {WeakMap} */ + const providedExportsCache = new WeakMap(); + compilation.hooks.rebuildModule.tap(PLUGIN_NAME, module => { + providedExportsCache.set( + module, + moduleGraph.getExportsInfo(module).getRestoreProvidedData() + ); + }); + compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, module => { + moduleGraph + .getExportsInfo(module) + .restoreProvided(providedExportsCache.get(module)); + }); + }); + } +} + +module.exports = FlagDependencyExportsPlugin; diff --git a/webpack-lib/lib/FlagDependencyUsagePlugin.js b/webpack-lib/lib/FlagDependencyUsagePlugin.js new file mode 100644 index 00000000000..247dbf90528 --- /dev/null +++ b/webpack-lib/lib/FlagDependencyUsagePlugin.js @@ -0,0 +1,347 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("./ExportsInfo")} ExportsInfo */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +const { NO_EXPORTS_REFERENCED, EXPORTS_OBJECT_REFERENCED } = Dependency; + +const PLUGIN_NAME = "FlagDependencyUsagePlugin"; +const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; + +class FlagDependencyUsagePlugin { + /** + * @param {boolean} global do a global analysis instead of per runtime + */ + constructor(global) { + this.global = global; + } + + /** + * Apply the plugin + * @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( + { 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<[Module, RuntimeSpec]>} */ + const queue = new TupleQueue(); + + /** + * @param {Module} module module to process + * @param {(string[] | ReferencedExport)[]} 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; + } + for (const usedExportInfo of usedExports) { + let usedExport; + let canMangle = true; + if (Array.isArray(usedExportInfo)) { + usedExport = usedExportInfo; + } else { + usedExport = usedExportInfo.name; + canMangle = usedExportInfo.canMangle !== 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; + } + 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 ( + !forceSideEffects && + module.factoryMeta !== undefined && + module.factoryMeta.sideEffectFree + ) { + return; + } + if (exportsInfo.setUsedForSideEffectsOnly(runtime)) { + queue.enqueue(module, runtime); + } + } + }; + + /** + * @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) => { + /** @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 ( + !this.global && + b.groupOptions && + b.groupOptions.entryOptions + ) { + processModule( + b, + 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 { + 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 + }); + } + } + } + } + } + } + + for (const [module, referencedExports] of map) { + if (Array.isArray(referencedExports)) { + processReferencedModule( + module, + referencedExports, + runtime, + forceSideEffects + ); + } else { + processReferencedModule( + module, + Array.from(referencedExports.values()), + runtime, + forceSideEffects + ); + } + } + }; + + logger.time("initialize exports usage"); + for (const module of modules) { + const exportsInfo = moduleGraph.getExportsInfo(module); + exportInfoToModuleMap.set(exportsInfo, module); + exportsInfo.setHasUseInfo(); + } + logger.timeEnd("initialize exports usage"); + + logger.time("trace exports usage in graph"); + + /** + * @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 [module, runtime] = /** @type {[Module, RuntimeSpec]} */ ( + queue.dequeue() + ); + processModule(module, runtime, false); + } + logger.timeEnd("trace exports usage in graph"); + } + ); + }); + } +} + +module.exports = FlagDependencyUsagePlugin; diff --git a/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js b/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js new file mode 100644 index 00000000000..d2826d12fb2 --- /dev/null +++ b/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js @@ -0,0 +1,56 @@ +/* + 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 { + /** + * @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; + } + + /** + * Apply the plugin + * @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/webpack-lib/lib/Generator.js b/webpack-lib/lib/Generator.js new file mode 100644 index 00000000000..2764305757c --- /dev/null +++ b/webpack-lib/lib/Generator.js @@ -0,0 +1,155 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./ConcatenationScope")} ConcatenationScope */ +/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */ +/** @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 {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 {string} type which kind of code should be generated + * @property {function(): Map=} getData get access to the code generation data + */ + +/** + * @typedef {object} UpdateHashContext + * @property {NormalModule} module the module + * @property {ChunkGraph} chunkGraph + * @property {RuntimeSpec} runtime + * @property {RuntimeTemplate=} runtimeTemplate + */ + +class Generator { + /** + * @param {Record} map map of types + * @returns {ByTypeGenerator} generator by type + */ + static byType(map) { + return new ByTypeGenerator(map); + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * @abstract + * @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("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @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}`; + } + + /** + * @param {Hash} hash hash that will be modified + * @param {UpdateHashContext} updateHashContext context for updating hash + */ + updateHash(hash, { module, runtime }) { + // no nothing + } +} + +class ByTypeGenerator extends Generator { + /** + * @param {Record} map map of types + */ + constructor(map) { + super(); + this.map = map; + this._types = new Set(Object.keys(map)); + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return this._types; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type = "javascript") { + const t = type; + const generator = this.map[t]; + return generator ? generator.getSize(module, t) : 0; + } + + /** + * @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, generateContext); + } +} + +module.exports = Generator; diff --git a/webpack-lib/lib/GraphHelpers.js b/webpack-lib/lib/GraphHelpers.js new file mode 100644 index 00000000000..65d7087281d --- /dev/null +++ b/webpack-lib/lib/GraphHelpers.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./Module")} Module */ + +/** + * @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); + } +}; + +module.exports.connectChunkGroupAndChunk = connectChunkGroupAndChunk; +module.exports.connectChunkGroupParentAndChild = + connectChunkGroupParentAndChild; diff --git a/webpack-lib/lib/HarmonyLinkingError.js b/webpack-lib/lib/HarmonyLinkingError.js new file mode 100644 index 00000000000..8259beca634 --- /dev/null +++ b/webpack-lib/lib/HarmonyLinkingError.js @@ -0,0 +1,16 @@ +/* + 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; + } +}; diff --git a/webpack-lib/lib/HookWebpackError.js b/webpack-lib/lib/HookWebpackError.js new file mode 100644 index 00000000000..84702401a37 --- /dev/null +++ b/webpack-lib/lib/HookWebpackError.js @@ -0,0 +1,91 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Module")} Module */ + +/** + * @template T + * @callback Callback + * @param {Error | null} err + * @param {T=} stats + * @returns {void} + */ + +class HookWebpackError extends WebpackError { + /** + * Creates an instance of HookWebpackError. + * @param {Error} error inner error + * @param {string} hook name of hook + */ + constructor(error, hook) { + super(error.message); + + this.name = "HookWebpackError"; + this.hook = hook; + this.error = error; + this.hideStack = true; + this.details = `caused by plugins in ${hook}\n${error.stack}`; + + this.stack += `\n-- inner error --\n${error.stack}`; + } +} + +module.exports = HookWebpackError; + +/** + * @param {Error} error an error + * @param {string} hook name of the hook + * @returns {WebpackError} a webpack error + */ +const makeWebpackError = (error, hook) => { + if (error instanceof WebpackError) return error; + return new HookWebpackError(error, hook); +}; +module.exports.makeWebpackError = makeWebpackError; + +/** + * @template T + * @param {function(WebpackError | null, T=): void} callback webpack error callback + * @param {string} hook name of hook + * @returns {Callback} generic callback + */ +const makeWebpackErrorCallback = (callback, hook) => (err, result) => { + if (err) { + if (err instanceof WebpackError) { + callback(err); + return; + } + callback(new HookWebpackError(err, hook)); + return; + } + callback(null, result); +}; + +module.exports.makeWebpackErrorCallback = makeWebpackErrorCallback; + +/** + * @template T + * @param {function(): T} fn function which will be wrapping in try catch + * @param {string} hook name of hook + * @returns {T} the result + */ +const tryRunOrWebpackError = (fn, hook) => { + let r; + try { + r = fn(); + } catch (err) { + if (err instanceof WebpackError) { + throw err; + } + throw new HookWebpackError(/** @type {Error} */ (err), hook); + } + return r; +}; + +module.exports.tryRunOrWebpackError = tryRunOrWebpackError; diff --git a/webpack-lib/lib/HotModuleReplacementPlugin.js b/webpack-lib/lib/HotModuleReplacementPlugin.js new file mode 100644 index 00000000000..5eb7c76d0f9 --- /dev/null +++ b/webpack-lib/lib/HotModuleReplacementPlugin.js @@ -0,0 +1,875 @@ +/* + 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 ChunkGraph = require("./ChunkGraph"); +const Compilation = require("./Compilation"); +const HotUpdateChunk = require("./HotUpdateChunk"); +const NormalModule = require("./NormalModule"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const WebpackError = require("./WebpackError"); +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 HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule"); +const JavascriptParser = require("./javascript/JavascriptParser"); +const { + evaluateToIdentifier +} = require("./javascript/JavascriptParserHelpers"); +const { find, isSubset } = require("./util/SetHelpers"); +const TupleSet = require("./util/TupleSet"); +const { compareModulesById } = require("./util/comparators"); +const { + getRuntimeKey, + keyToRuntime, + forEachRuntime, + mergeRuntimeOwned, + subtractRuntime, + intersectRuntime +} = require("./util/runtime"); + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM, + WEBPACK_MODULE_TYPE_RUNTIME +} = require("./ModuleTypeConstants"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ +/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Chunk").ChunkId} ChunkId */ +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./RuntimeModule")} RuntimeModule */ +/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @typedef {object} HMRJavascriptParserHooks + * @property {SyncBailHook<[Expression | SpreadElement, string[]], void>} hotAcceptCallback + * @property {SyncBailHook<[CallExpression, string[]], void>} hotAcceptWithoutCallback + */ + +/** @typedef {{ updatedChunkIds: Set, removedChunkIds: Set, removedModules: Set, filename: string, assetInfo: AssetInfo }} HotUpdateMainContentByRuntimeItem */ +/** @typedef {Map} HotUpdateMainContentByRuntime */ + +/** @type {WeakMap} */ +const parserHooksMap = new WeakMap(); + +const PLUGIN_NAME = "HotModuleReplacementPlugin"; + +class HotModuleReplacementPlugin { + /** + * @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; + } + + /** + * @param {object=} options options + */ + constructor(options) { + this.options = options || {}; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _backCompat: backCompat } = compiler; + if (compiler.options.output.strictModuleErrorHandling === undefined) + compiler.options.output.strictModuleErrorHandling = true; + const runtimeRequirements = [RuntimeGlobals.module]; + + /** + * @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 {string[]} */ + 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.loc = Object.create( + /** @type {DependencyLocation} */ (expr.loc) + ); + dep.loc.index = 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; + }; + }; + + /** + * @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.loc = Object.create(/** @type {DependencyLocation} */ (expr.loc)); + dep.loc.index = idx; + module.addDependency(dep); + } + } + return true; + }; + + /** + * @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; + }; + + /** + * @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)); + }; + + /** + * @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 + ); + compilation.dependencyTemplates.set( + ModuleHotAcceptDependency, + new ModuleHotAcceptDependency.Template() + ); + compilation.dependencyFactories.set( + ModuleHotDeclineDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + ModuleHotDeclineDependency, + new ModuleHotDeclineDependency.Template() + ); + // #endregion + + // #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() + ); + // #endregion + + let hotIndex = 0; + /** @type {Record} */ + const fullHashChunkModuleHashes = {}; + /** @type {Record} */ + 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] = chunk.hash; + records.chunkRuntime[chunkId] = getRuntimeKey(chunk.runtime); + } + records.chunkModuleIds = {}; + for (const chunk of compilation.chunks) { + records.chunkModuleIds[/** @type {ChunkId} */ (chunk.id)] = + Array.from( + chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById(chunkGraph) + ), + m => chunkGraph.getModuleId(m) + ); + } + }); + /** @type {TupleSet<[Module, Chunk]>} */ + const updatedModules = new TupleSet(); + /** @type {TupleSet<[Module, Chunk]>} */ + const fullHashModules = new TupleSet(); + /** @type {TupleSet<[Module, RuntimeSpec]>} */ + const nonCodeGeneratedModules = new TupleSet(); + compilation.hooks.fullHash.tap(PLUGIN_NAME, hash => { + const chunkGraph = compilation.chunkGraph; + const records = compilation.records; + for (const chunk of compilation.chunks) { + /** + * @param {Module} module module + * @returns {string} module hash + */ + const getModuleHash = module => { + if ( + compilation.codeGenerationResults.has(module, chunk.runtime) + ) { + return compilation.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 (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; + } + } + } + } + + 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 chunkGraph = compilation.chunkGraph; + const records = compilation.records; + if (records.hash === compilation.hash) return; + if ( + !records.chunkModuleHashes || + !records.chunkHashes || + !records.chunkModuleIds + ) { + return; + } + for (const [module, chunk] of fullHashModules) { + const key = `${chunk.id}|${module.identifier()}`; + const hash = nonCodeGeneratedModules.has(module, chunk.runtime) + ? chunkGraph.getModuleHash(module, chunk.runtime) + : compilation.codeGenerationResults.getHash( + module, + chunk.runtime + ); + if (records.chunkModuleHashes[key] !== hash) { + updatedModules.add(module, chunk); + } + chunkModuleHashes[key] = hash; + } + + /** @type {HotUpdateMainContentByRuntime} */ + const hotUpdateMainContentByRuntime = new Map(); + let allOldRuntime; + for (const key of Object.keys(records.chunkRuntime)) { + const runtime = keyToRuntime(records.chunkRuntime[key]); + allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime); + } + forEachRuntime(allOldRuntime, runtime => { + const { path: filename, info: assetInfo } = + compilation.getPathWithInfo( + /** @type {NonNullable} */ + (compilation.outputOptions.hotUpdateMainFilename), + { + hash: records.hash, + runtime + } + ); + hotUpdateMainContentByRuntime.set( + /** @type {string} */ (runtime), + { + updatedChunkIds: new Set(), + removedChunkIds: new Set(), + removedModules: 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 id = + /** @type {ModuleId} */ + (chunkGraph.getModuleId(module)); + allModules.set(id, module); + } + + // List of completely removed modules + /** @type {Set} */ + const completelyRemovedModules = new Set(); + + for (const key of Object.keys(records.chunkHashes)) { + const oldRuntime = keyToRuntime(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; + let newModules; + let newRuntimeModules; + let newFullHashModules; + let newDependentHashModules; + let newRuntime; + let removedFromRuntime; + const currentChunk = find( + compilation.chunks, + chunk => `${chunk.id}` === key + ); + if (currentChunk) { + chunkId = currentChunk.id; + newRuntime = intersectRuntime( + currentChunk.runtime, + allOldRuntime + ); + if (newRuntime === undefined) continue; + newModules = chunkGraph + .getChunkModules(currentChunk) + .filter(module => updatedModules.has(module, currentChunk)); + newRuntimeModules = Array.from( + chunkGraph.getChunkRuntimeModulesIterable(currentChunk) + ).filter(module => updatedModules.has(module, currentChunk)); + const fullHashModules = + chunkGraph.getChunkFullHashModulesIterable(currentChunk); + newFullHashModules = + fullHashModules && + Array.from(fullHashModules).filter(module => + updatedModules.has(module, currentChunk) + ); + const dependentHashModules = + chunkGraph.getChunkDependentHashModulesIterable(currentChunk); + newDependentHashModules = + dependentHashModules && + Array.from(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) + : compilation.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 + // We (incorrectly) assume that it's not in an overlapping runtime combination + // and dispose it from the main runtimes the chunk was removed from + forEachRuntime(removedFromRuntime, runtime => { + // If the module is still used in this runtime, do not dispose it + // This could create a bad runtime state where the module is still loaded, + // but no chunk which contains it. This means we don't receive further HMR updates + // to this module and that's bad. + // TODO force load one of the chunks which contains the module + for (const moduleRuntime of runtimes) { + if (typeof moduleRuntime === "string") { + if (moduleRuntime === runtime) return; + } else if ( + moduleRuntime !== undefined && + moduleRuntime.has(/** @type {string} */ (runtime)) + ) + return; + } + const item = + /** @type {HotUpdateMainContentByRuntimeItem} */ ( + hotUpdateMainContentByRuntime.get( + /** @type {string} */ (runtime) + ) + ); + 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); + } + chunkGraph.attachModules(hotUpdateChunk, newModules || []); + chunkGraph.attachRuntimeModules( + hotUpdateChunk, + newRuntimeModules || [] + ); + if (newFullHashModules) { + chunkGraph.attachFullHashModules( + hotUpdateChunk, + newFullHashModules + ); + } + if (newDependentHashModules) { + chunkGraph.attachDependentHashModules( + hotUpdateChunk, + newDependentHashModules + ); + } + const renderManifest = compilation.getRenderManifest({ + chunk: hotUpdateChunk, + hash: records.hash, + fullHash: records.hash, + outputOptions: compilation.outputOptions, + moduleTemplates: compilation.moduleTemplates, + dependencyTemplates: compilation.dependencyTemplates, + 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 completelyRemovedModulesArray = Array.from( + completelyRemovedModules + ); + const hotUpdateMainContentByFilename = new Map(); + for (const { + removedChunkIds, + removedModules, + updatedChunkIds, + filename, + assetInfo + } of hotUpdateMainContentByRuntime.values()) { + const old = hotUpdateMainContentByFilename.get(filename); + if ( + old && + (!isSubset(old.removedChunkIds, removedChunkIds) || + !isSubset(old.removedModules, removedModules) || + !isSubset(old.updatedChunkIds, updatedChunkIds)) + ) { + 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); + continue; + } + hotUpdateMainContentByFilename.set(filename, { + removedChunkIds, + removedModules, + updatedChunkIds, + assetInfo + }); + } + for (const [ + filename, + { removedChunkIds, removedModules, updatedChunkIds, assetInfo } + ] of hotUpdateMainContentByFilename) { + const hotUpdateMainJson = { + c: Array.from(updatedChunkIds), + r: Array.from(removedChunkIds), + m: + removedModules.size === 0 + ? completelyRemovedModulesArray + : completelyRemovedModulesArray.concat( + Array.from( + removedModules, + m => + /** @type {ModuleId} */ (chunkGraph.getModuleId(m)) + ) + ) + }; + + const source = new RawSource(JSON.stringify(hotUpdateMainJson)); + compilation.emitAsset(filename, source, { + hotModuleReplacement: true, + ...assetInfo + }); + } + } + ); + + 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() + ); + } + ); + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, parser => { + applyModuleHot(parser); + applyImportMetaHot(parser); + }); + normalModuleFactory.hooks.parser + .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 => { + module.hot = true; + return module; + }); + + NormalModule.getCompilationHooks(compilation).loader.tap( + PLUGIN_NAME, + context => { + context.hot = true; + } + ); + } + ); + } +} + +module.exports = HotModuleReplacementPlugin; diff --git a/webpack-lib/lib/HotUpdateChunk.js b/webpack-lib/lib/HotUpdateChunk.js new file mode 100644 index 00000000000..d939838527d --- /dev/null +++ b/webpack-lib/lib/HotUpdateChunk.js @@ -0,0 +1,19 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Chunk = require("./Chunk"); + +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./util/Hash")} Hash */ + +class HotUpdateChunk extends Chunk { + constructor() { + super(); + } +} + +module.exports = HotUpdateChunk; diff --git a/webpack-lib/lib/IgnoreErrorModuleFactory.js b/webpack-lib/lib/IgnoreErrorModuleFactory.js new file mode 100644 index 00000000000..4fd73e7fa8b --- /dev/null +++ b/webpack-lib/lib/IgnoreErrorModuleFactory.js @@ -0,0 +1,39 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const ModuleFactory = require("./ModuleFactory"); + +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ + +/** + * Ignores error when module is unresolved + */ +class IgnoreErrorModuleFactory extends ModuleFactory { + /** + * @param {NormalModuleFactory} normalModuleFactory normalModuleFactory instance + */ + constructor(normalModuleFactory) { + super(); + + this.normalModuleFactory = normalModuleFactory; + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + this.normalModuleFactory.create(data, (err, result) => + callback(null, result) + ); + } +} + +module.exports = IgnoreErrorModuleFactory; diff --git a/webpack-lib/lib/IgnorePlugin.js b/webpack-lib/lib/IgnorePlugin.js new file mode 100644 index 00000000000..8049ac129cb --- /dev/null +++ b/webpack-lib/lib/IgnorePlugin.js @@ -0,0 +1,102 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RawModule = require("./RawModule"); +const EntryDependency = require("./dependencies/EntryDependency"); +const createSchemaValidation = require("./util/create-schema-validation"); + +/** @typedef {import("../declarations/plugins/IgnorePlugin").IgnorePluginOptions} IgnorePluginOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */ + +const validate = createSchemaValidation( + require("../schemas/plugins/IgnorePlugin.check.js"), + () => require("../schemas/plugins/IgnorePlugin.json"), + { + name: "Ignore Plugin", + baseDataPath: "options" + } +); + +class IgnorePlugin { + /** + * @param {IgnorePluginOptions} options IgnorePlugin options + */ + constructor(options) { + validate(options); + this.options = options; + + /** + * @private + * @type {Function} + */ + this.checkIgnore = this.checkIgnore.bind(this); + } + + /** + * Note that if "contextRegExp" is given, both the "resourceRegExp" and "contextRegExp" have to match. + * @param {ResolveData} resolveData resolve data + * @returns {false|undefined} returns false when the request should be ignored, otherwise undefined + */ + checkIgnore(resolveData) { + if ( + "checkResource" in this.options && + this.options.checkResource && + this.options.checkResource(resolveData.request, resolveData.context) + ) { + return false; + } + + 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; + } + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.normalModuleFactory.tap("IgnorePlugin", nmf => { + nmf.hooks.beforeResolve.tap("IgnorePlugin", resolveData => { + const result = this.checkIgnore(resolveData); + + if ( + result === false && + resolveData.dependencies.length > 0 && + resolveData.dependencies[0] instanceof EntryDependency + ) { + resolveData.ignoredModule = new RawModule( + "", + "ignored-entry-module", + "(ignored-entry-module)" + ); + } + + return result; + }); + }); + compiler.hooks.contextModuleFactory.tap("IgnorePlugin", cmf => { + cmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore); + }); + } +} + +module.exports = IgnorePlugin; diff --git a/webpack-lib/lib/IgnoreWarningsPlugin.js b/webpack-lib/lib/IgnoreWarningsPlugin.js new file mode 100644 index 00000000000..e844a8369e4 --- /dev/null +++ b/webpack-lib/lib/IgnoreWarningsPlugin.js @@ -0,0 +1,36 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../declarations/WebpackOptions").IgnoreWarningsNormalized} IgnoreWarningsNormalized */ +/** @typedef {import("./Compiler")} Compiler */ + +class IgnoreWarningsPlugin { + /** + * @param {IgnoreWarningsNormalized} ignoreWarnings conditions to ignore warnings + */ + constructor(ignoreWarnings) { + this._ignoreWarnings = ignoreWarnings; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("IgnoreWarningsPlugin", compilation => { + compilation.hooks.processWarnings.tap("IgnoreWarningsPlugin", warnings => + warnings.filter( + warning => + !this._ignoreWarnings.some(ignore => ignore(warning, compilation)) + ) + ); + }); + } +} + +module.exports = IgnoreWarningsPlugin; diff --git a/webpack-lib/lib/InitFragment.js b/webpack-lib/lib/InitFragment.js new file mode 100644 index 00000000000..7a5d9630771 --- /dev/null +++ b/webpack-lib/lib/InitFragment.js @@ -0,0 +1,185 @@ +/* + 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 */ + +/** + * @template T + * @param {InitFragment} fragment the init fragment + * @param {number} index index + * @returns {[InitFragment, number]} tuple with both + */ +const extractFragmentIndex = (fragment, index) => [fragment, index]; + +/** + * @template T + * @param {[InitFragment, number]} a first pair + * @param {[InitFragment, 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; +}; + +/** + * @template GenerateContext + */ +class 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 {string=} 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; + } + + /** + * @param {GenerateContext} context context + * @returns {string | Source | undefined} the source code that will be included as initialization code + */ + getContent(context) { + return this.content; + } + + /** + * @param {GenerateContext} context context + * @returns {string|Source=} the source code that will be included at the end of the module + */ + getEndContent(context) { + return this.endContent; + } + + /** + * @template Context + * @template T + * @param {Source} source sources + * @param {InitFragment[]} 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. + const keyedFragments = new Map(); + for (const [fragment] of sortedFragments) { + if ( + typeof ( + /** @type {InitFragment & { mergeAll?: (fragments: InitFragment[]) => InitFragment[] }} */ + (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 oldValue = keyedFragments.get(fragment.key); + if (oldValue !== undefined) { + keyedFragments.set(fragment.key, fragment.merge(oldValue)); + continue; + } + } + keyedFragments.set(fragment.key || Symbol("fragment key"), fragment); + } + + const concatSource = new ConcatSource(); + const endContents = []; + for (let fragment of keyedFragments.values()) { + if (Array.isArray(fragment)) { + fragment = fragment[0].mergeAll(fragment); + } + concatSource.add(fragment.getContent(context)); + const endContent = fragment.getEndContent(context); + if (endContent) { + endContents.push(endContent); + } + } + + concatSource.add(source); + for (const content of endContents.reverse()) { + concatSource.add(content); + } + return concatSource; + } + return source; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.content); + write(this.stage); + write(this.position); + write(this.key); + write(this.endContent); + } + + /** + * @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.prototype.merge = + /** @type {TODO} */ + (undefined); + +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/webpack-lib/lib/InvalidDependenciesModuleWarning.js b/webpack-lib/lib/InvalidDependenciesModuleWarning.js new file mode 100644 index 00000000000..a69eed58d92 --- /dev/null +++ b/webpack-lib/lib/InvalidDependenciesModuleWarning.js @@ -0,0 +1,44 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ + +class InvalidDependenciesModuleWarning extends WebpackError { + /** + * @param {Module} module module tied to dependency + * @param {Iterable} deps invalid dependencies + */ + constructor(module, deps) { + const orderedDeps = deps ? Array.from(deps).sort() : []; + const depsList = orderedDeps.map(dep => ` * ${JSON.stringify(dep)}`); + super(`Invalid dependencies have been reported by plugins or loaders for this module. All reported dependencies need to be absolute paths. +Invalid dependencies may lead to broken watching and caching. +As best effort we try to convert all invalid values to absolute paths and converting globs into context dependencies, but this is deprecated behavior. +Loaders: Pass absolute paths to this.addDependency (existing files), this.addMissingDependency (not existing files), and this.addContextDependency (directories). +Plugins: Pass absolute paths to fileDependencies (existing files), missingDependencies (not existing files), and contextDependencies (directories). +Globs: They are not supported. Pass absolute path to the directory as context dependencies. +The following invalid values have been reported: +${depsList.slice(0, 3).join("\n")}${ + depsList.length > 3 ? "\n * and more ..." : "" + }`); + + this.name = "InvalidDependenciesModuleWarning"; + this.details = depsList.slice(3).join("\n"); + this.module = module; + } +} + +makeSerializable( + InvalidDependenciesModuleWarning, + "webpack/lib/InvalidDependenciesModuleWarning" +); + +module.exports = InvalidDependenciesModuleWarning; diff --git a/webpack-lib/lib/JavascriptMetaInfoPlugin.js b/webpack-lib/lib/JavascriptMetaInfoPlugin.js new file mode 100644 index 00000000000..b8f77bea369 --- /dev/null +++ b/webpack-lib/lib/JavascriptMetaInfoPlugin.js @@ -0,0 +1,80 @@ +/* + 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 InnerGraph = require("./optimize/InnerGraph"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ + +const PLUGIN_NAME = "JavascriptMetaInfoPlugin"; + +class JavascriptMetaInfoPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + const handler = parser => { + parser.hooks.call.for("eval").tap(PLUGIN_NAME, () => { + const buildInfo = + /** @type {BuildInfo} */ + (parser.state.module.buildInfo); + buildInfo.moduleConcatenationBailout = "eval()"; + buildInfo.usingEval = true; + 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()) { + const freeInfo = parser.getFreeInfoFromVariable(name); + if (freeInfo === undefined) { + 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/webpack-lib/lib/LibManifestPlugin.js b/webpack-lib/lib/LibManifestPlugin.js new file mode 100644 index 00000000000..ab9d2fc57d8 --- /dev/null +++ b/webpack-lib/lib/LibManifestPlugin.js @@ -0,0 +1,144 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const EntryDependency = require("./dependencies/EntryDependency"); +const { someInIterable } = require("./util/IterableHelpers"); +const { compareModulesById } = require("./util/comparators"); +const { dirname, mkdirp } = require("./util/fs"); + +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Compiler").IntermediateFileSystem} IntermediateFileSystem */ +/** @typedef {import("./Module").BuildMeta} BuildMeta */ + +/** + * @typedef {object} ManifestModuleData + * @property {string | number} id + * @property {BuildMeta} buildMeta + * @property {boolean | string[] | undefined} exports + */ + +/** + * @typedef {object} LibManifestPluginOptions + * @property {string=} context Context of requests in the manifest file (defaults to the webpack context). + * @property {boolean=} entryOnly If true, only entry points will be exposed (default: true). + * @property {boolean=} format If true, manifest json file (output) will be formatted. + * @property {string=} name Name of the exposed dll function (external name, use value of 'output.library'). + * @property {string} path Absolute path to the manifest json file (output). + * @property {string=} type Type of the dll bundle (external type, use value of 'output.libraryTarget'). + */ + +class LibManifestPlugin { + /** + * @param {LibManifestPluginOptions} options the options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.emit.tapAsync( + { + name: "LibManifestPlugin", + stage: 110 + }, + (compilation, callback) => { + const moduleGraph = compilation.moduleGraph; + // store used paths to detect issue and output an error. #18200 + const usedPaths = new Set(); + asyncLib.each( + Array.from(compilation.chunks), + (chunk, callback) => { + if (!chunk.canBeInitial()) { + callback(); + return; + } + const chunkGraph = compilation.chunkGraph; + const targetPath = compilation.getPath(this.options.path, { + chunk + }); + if (usedPaths.has(targetPath)) { + callback(new Error("each chunk must have a unique path")); + return; + } + usedPaths.add(targetPath); + const name = + this.options.name && + compilation.getPath(this.options.name, { + chunk, + contentHashType: "javascript" + }); + const content = Object.create(null); + for (const module of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesById(chunkGraph) + )) { + if ( + this.options.entryOnly && + !someInIterable( + moduleGraph.getIncomingConnections(module), + c => c.dependency instanceof EntryDependency + ) + ) { + continue; + } + const ident = module.libIdent({ + context: + this.options.context || + /** @type {string} */ (compiler.options.context), + associatedObjectForCache: compiler.root + }); + if (ident) { + const exportsInfo = moduleGraph.getExportsInfo(module); + const providedExports = exportsInfo.getProvidedExports(); + /** @type {ManifestModuleData} */ + const data = { + id: /** @type {ModuleId} */ (chunkGraph.getModuleId(module)), + buildMeta: /** @type {BuildMeta} */ (module.buildMeta), + exports: Array.isArray(providedExports) + ? providedExports + : undefined + }; + content[ident] = data; + } + } + const manifest = { + name, + type: this.options.type, + content + }; + // Apply formatting to content if format flag is true; + const manifestContent = this.options.format + ? JSON.stringify(manifest, null, 2) + : JSON.stringify(manifest); + const buffer = Buffer.from(manifestContent, "utf8"); + const intermediateFileSystem = + /** @type {IntermediateFileSystem} */ ( + compiler.intermediateFileSystem + ); + mkdirp( + intermediateFileSystem, + dirname(intermediateFileSystem, targetPath), + err => { + if (err) return callback(err); + intermediateFileSystem.writeFile(targetPath, buffer, callback); + } + ); + }, + callback + ); + } + ); + } +} +module.exports = LibManifestPlugin; diff --git a/webpack-lib/lib/LibraryTemplatePlugin.js b/webpack-lib/lib/LibraryTemplatePlugin.js new file mode 100644 index 00000000000..91cc4ab1440 --- /dev/null +++ b/webpack-lib/lib/LibraryTemplatePlugin.js @@ -0,0 +1,48 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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 */ + +// TODO webpack 6 remove +class 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.library = { + type: target || "var", + name, + umdNamedDefine, + auxiliaryComment, + export: exportProperty + }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { output } = compiler.options; + output.library = this.library; + new EnableLibraryPlugin(this.library.type).apply(compiler); + } +} + +module.exports = LibraryTemplatePlugin; diff --git a/webpack-lib/lib/LoaderOptionsPlugin.js b/webpack-lib/lib/LoaderOptionsPlugin.js new file mode 100644 index 00000000000..0cd6d7ad82b --- /dev/null +++ b/webpack-lib/lib/LoaderOptionsPlugin.js @@ -0,0 +1,83 @@ +/* + 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 createSchemaValidation = require("./util/create-schema-validation"); + +/** @typedef {import("../declarations/plugins/LoaderOptionsPlugin").LoaderOptionsPluginOptions} LoaderOptionsPluginOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./ModuleFilenameHelpers").MatchObject} MatchObject */ + +const validate = createSchemaValidation( + require("../schemas/plugins/LoaderOptionsPlugin.check.js"), + () => require("../schemas/plugins/LoaderOptionsPlugin.json"), + { + name: "Loader Options Plugin", + baseDataPath: "options" + } +); + +class LoaderOptionsPlugin { + /** + * @param {LoaderOptionsPluginOptions & MatchObject} options options object + */ + constructor(options = {}) { + validate(options); + // If no options are set then generate empty options object + if (typeof options !== "object") options = {}; + if (!options.test) { + // This is mocking a RegExp object which always returns true + // TODO: Figure out how to do `as unknown as RegExp` for this line + // in JSDoc equivalent + /** @type {any} */ + const defaultTrueMockRegExp = { + test: () => true + }; + + /** @type {RegExp} */ + options.test = defaultTrueMockRegExp; + } + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap("LoaderOptionsPlugin", compilation => { + NormalModule.getCompilationHooks(compilation).loader.tap( + "LoaderOptionsPlugin", + (context, module) => { + const resource = module.resource; + if (!resource) return; + const i = resource.indexOf("?"); + if ( + ModuleFilenameHelpers.matchObject( + options, + i < 0 ? resource : resource.slice(0, i) + ) + ) { + for (const key of Object.keys(options)) { + if (key === "include" || key === "exclude" || key === "test") { + continue; + } + + /** @type {any} */ + (context)[key] = options[key]; + } + } + } + ); + }); + } +} + +module.exports = LoaderOptionsPlugin; diff --git a/webpack-lib/lib/LoaderTargetPlugin.js b/webpack-lib/lib/LoaderTargetPlugin.js new file mode 100644 index 00000000000..e7d3b38c18a --- /dev/null +++ b/webpack-lib/lib/LoaderTargetPlugin.js @@ -0,0 +1,37 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NormalModule = require("./NormalModule"); + +/** @typedef {import("./Compiler")} Compiler */ + +class LoaderTargetPlugin { + /** + * @param {string} target the target + */ + constructor(target) { + this.target = target; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("LoaderTargetPlugin", compilation => { + NormalModule.getCompilationHooks(compilation).loader.tap( + "LoaderTargetPlugin", + loaderContext => { + loaderContext.target = this.target; + } + ); + }); + } +} + +module.exports = LoaderTargetPlugin; diff --git a/webpack-lib/lib/MainTemplate.js b/webpack-lib/lib/MainTemplate.js new file mode 100644 index 00000000000..d05ebad2bf9 --- /dev/null +++ b/webpack-lib/lib/MainTemplate.js @@ -0,0 +1,382 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const util = require("util"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const memoize = require("./util/memoize"); + +/** @typedef {import("tapable").Tap} Tap */ +/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").Output} OutputOptions */ +/** @typedef {import("./ModuleTemplate")} ModuleTemplate */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */ +/** @typedef {import("./Module")} Module} */ +/** @typedef {import("./util/Hash")} Hash} */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates} */ +/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderContext} RenderContext} */ +/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderBootstrapContext} RenderBootstrapContext} */ +/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkHashContext} ChunkHashContext} */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate} */ +/** @typedef {import("./ModuleGraph")} ModuleGraph} */ +/** @typedef {import("./ChunkGraph")} ChunkGraph} */ +/** @typedef {import("./Template").RenderManifestOptions} RenderManifestOptions} */ +/** @typedef {import("./Template").RenderManifestEntry} RenderManifestEntry} */ +/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath} */ +/** @typedef {import("./TemplatedPathPlugin").PathData} PathData} */ +/** + * @template T + * @typedef {import("tapable").IfSet} IfSet + */ + +const getJavascriptModulesPlugin = memoize(() => + require("./javascript/JavascriptModulesPlugin") +); +const getJsonpTemplatePlugin = memoize(() => + require("./web/JsonpTemplatePlugin") +); +const getLoadScriptRuntimeModule = memoize(() => + require("./runtime/LoadScriptRuntimeModule") +); + +// TODO webpack 6 remove this class +class MainTemplate { + /** + * @param {OutputOptions} outputOptions output options for the MainTemplate + * @param {Compilation} compilation the compilation + */ + constructor(outputOptions, compilation) { + /** @type {OutputOptions} */ + this._outputOptions = outputOptions || {}; + this.hooks = Object.freeze({ + renderManifest: { + tap: util.deprecate( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(RenderManifestEntry[], 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(string, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Chunk, string | undefined, ModuleTemplate, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Chunk, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(string, object, 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( + /** + * @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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Hash, 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", + "hash", + "moduleTemplate", + "dependencyTemplates" + ]), + /** @type {SyncWaterfallHook<[string, Chunk, string]>} */ + localVars: new SyncWaterfallHook(["source", "chunk", "hash"]), + /** @type {SyncWaterfallHook<[string, Chunk, string]>} */ + requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]), + /** @type {SyncWaterfallHook<[string, Chunk, string, string]>} */ + requireEnsure: new SyncWaterfallHook([ + "source", + "chunk", + "hash", + "chunkIdExpression" + ]), + 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; + } + }); + + this.renderCurrentHashCode = util.deprecate( + /** + * @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" + ); + + this.getPublicPath = util.deprecate( + /** + * @param {PathData} options context data + * @returns {string} interpolated path + */ options => + compilation.getAssetPath( + /** @type {string} */ + (compilation.outputOptions.publicPath), + options + ), + "MainTemplate.getPublicPath is deprecated (use Compilation.getAssetPath(compilation.outputOptions.publicPath, options) instead)", + "DEP_WEBPACK_MAIN_TEMPLATE_GET_PUBLIC_PATH" + ); + + this.getAssetPath = util.deprecate( + /** + * @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" + ); + + this.getAssetPathWithInfo = util.deprecate( + /** + * @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" + ); + } +} + +Object.defineProperty(MainTemplate.prototype, "requireFn", { + get: util.deprecate( + () => RuntimeGlobals.require, + `MainTemplate.requireFn is deprecated (use "${RuntimeGlobals.require}")`, + "DEP_WEBPACK_MAIN_TEMPLATE_REQUIRE_FN" + ) +}); + +Object.defineProperty(MainTemplate.prototype, "outputOptions", { + get: util.deprecate( + /** + * @this {MainTemplate} + * @returns {OutputOptions} output options + */ + function () { + return this._outputOptions; + }, + "MainTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)", + "DEP_WEBPACK_MAIN_TEMPLATE_OUTPUT_OPTIONS" + ) +}); + +module.exports = MainTemplate; diff --git a/webpack-lib/lib/Module.js b/webpack-lib/lib/Module.js new file mode 100644 index 00000000000..b07066f38bc --- /dev/null +++ b/webpack-lib/lib/Module.js @@ -0,0 +1,1198 @@ +/* + 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 ModuleGraph = require("./ModuleGraph"); +const { JS_TYPES } = require("./ModuleSourceTypesConstants"); +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("../declarations/WebpackOptions").WebpackOptionsNormalized} 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("./ConcatenationScope")} ConcatenationScope */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./ExportsInfo").UsageStateType} UsageStateType */ +/** @typedef {import("./FileSystemInfo")} FileSystemInfo */ +/** @typedef {import("./FileSystemInfo").Snapshot} Snapshot */ +/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("./ModuleTypeConstants").ModuleTypes} ModuleTypes */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./util/Hash")} Hash */ +/** @template T @typedef {import("./util/LazySet")} LazySet */ +/** @template T @typedef {import("./util/SortableSet")} SortableSet */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @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 {ReadonlySet} SourceTypes */ + +// TODO webpack 6: compilation will be required in CodeGenerationContext +/** + * @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 {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 + */ + +/** + * @typedef {object} ConcatenationBailoutReasonContext + * @property {ModuleGraph} moduleGraph the module graph + * @property {ChunkGraph} chunkGraph the chunk graph + */ + +/** @typedef {Set} RuntimeRequirements */ +/** @typedef {ReadonlySet} ReadOnlyRuntimeRequirements */ + +/** + * @typedef {object} CodeGenerationResult + * @property {Map} sources the resulting sources for all source types + * @property {Map=} 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) + */ + +/** + * @typedef {object} LibIdentOptions + * @property {string} context absolute context path to which lib ident is relative to + * @property {object=} associatedObjectForCache object for caching + */ + +/** + * @typedef {object} KnownBuildMeta + * @property {string=} moduleArgument + * @property {string=} exportsArgument + * @property {boolean=} strict + * @property {string=} moduleConcatenationBailout + * @property {("default" | "namespace" | "flagged" | "dynamic")=} exportsType + * @property {(false | "redirect" | "redirect-warn")=} defaultObject + * @property {boolean=} strictHarmonyModule + * @property {boolean=} async + * @property {boolean=} sideEffectFree + * @property {Record=} exportsFinalName + */ + +/** + * @typedef {object} KnownBuildInfo + * @property {boolean=} cacheable + * @property {boolean=} parsed + * @property {LazySet=} fileDependencies + * @property {LazySet=} contextDependencies + * @property {LazySet=} missingDependencies + * @property {LazySet=} buildDependencies + * @property {ValueCacheVersions=} valueDependencies + * @property {TODO=} hash + * @property {Record=} assets + * @property {Map=} assetsInfo + * @property {(Snapshot | null)=} snapshot + */ + +/** @typedef {Map>} ValueCacheVersions */ + +/** + * @typedef {object} NeedBuildContext + * @property {Compilation} compilation + * @property {FileSystemInfo} fileSystemInfo + * @property {ValueCacheVersions} valueCacheVersions + */ + +/** @typedef {KnownBuildMeta & Record} BuildMeta */ +/** @typedef {KnownBuildInfo & Record} BuildInfo */ + +/** + * @typedef {object} FactoryMeta + * @property {boolean=} sideEffectFree + */ + +/** @typedef {{ factoryMeta: FactoryMeta | undefined, resolveOptions: ResolveOptions | undefined }} UnsafeCacheData */ + +const EMPTY_RESOLVE_OPTIONS = {}; + +let debugId = 1000; + +const DEFAULT_TYPES_UNKNOWN = new Set(["unknown"]); + +const deprecatedNeedRebuild = util.deprecate( + /** + * @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" +); + +/** @typedef {(requestShortener: RequestShortener) => string} OptimizationBailoutFunction */ + +class Module extends DependenciesBlock { + /** + * @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 {ModuleTypes} */ + this.type = type; + /** @type {string | null} */ + this.context = context; + /** @type {string | null} */ + this.layer = layer; + /** @type {boolean} */ + this.needId = true; + + // Unique Id + /** @type {number} */ + this.debugId = debugId++; + + // Info from Factory + /** @type {ResolveOptions | undefined} */ + this.resolveOptions = EMPTY_RESOLVE_OPTIONS; + /** @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; + + // Is in hot context, i.e. HotModuleReplacementPlugin.js enabled + /** @type {boolean} */ + this.hot = false; + // Info from Build + /** @type {WebpackError[] | undefined} */ + this._warnings = undefined; + /** @type {WebpackError[] | undefined} */ + this._errors = undefined; + /** @type {BuildMeta | undefined} */ + this.buildMeta = undefined; + /** @type {BuildInfo | undefined} */ + this.buildInfo = undefined; + /** @type {Dependency[] | undefined} */ + this.presentationalDependencies = undefined; + /** @type {Dependency[] | undefined} */ + this.codeGenerationDependencies = undefined; + } + + // TODO remove in webpack 6 + // BACKWARD-COMPAT START + /** + * @returns {ModuleId | null} module id + */ + get id() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.id", + "DEP_WEBPACK_MODULE_ID" + ).getModuleId(this); + } + + /** + * @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); + } + + /** + * @returns {string} the hash of the module + */ + get hash() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.hash", + "DEP_WEBPACK_MODULE_HASH" + ).getModuleHash(this, undefined); + } + + /** + * @returns {string} the shortened hash of the module + */ + get renderedHash() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.renderedHash", + "DEP_WEBPACK_MODULE_RENDERED_HASH" + ).getRenderedModuleHash(this, undefined); + } + + get profile() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.profile", + "DEP_WEBPACK_MODULE_PROFILE" + ).getProfile(this); + } + + set profile(value) { + ModuleGraph.getModuleGraphForModule( + this, + "Module.profile", + "DEP_WEBPACK_MODULE_PROFILE" + ).setProfile(this, value); + } + + /** + * @returns {number | null} the pre order index + */ + get index() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.index", + "DEP_WEBPACK_MODULE_INDEX" + ).getPreOrderIndex(this); + } + + /** + * @param {number} value the pre order index + */ + set index(value) { + ModuleGraph.getModuleGraphForModule( + this, + "Module.index", + "DEP_WEBPACK_MODULE_INDEX" + ).setPreOrderIndex(this, value); + } + + /** + * @returns {number | null} the post order index + */ + get index2() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.index2", + "DEP_WEBPACK_MODULE_INDEX2" + ).getPostOrderIndex(this); + } + + /** + * @param {number} value the post order index + */ + set index2(value) { + ModuleGraph.getModuleGraphForModule( + this, + "Module.index2", + "DEP_WEBPACK_MODULE_INDEX2" + ).setPostOrderIndex(this, value); + } + + /** + * @returns {number | null} the depth + */ + get depth() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.depth", + "DEP_WEBPACK_MODULE_DEPTH" + ).getDepth(this); + } + + /** + * @param {number} value the depth + */ + set depth(value) { + ModuleGraph.getModuleGraphForModule( + this, + "Module.depth", + "DEP_WEBPACK_MODULE_DEPTH" + ).setDepth(this, value); + } + + /** + * @returns {Module | null | undefined} issuer + */ + get issuer() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.issuer", + "DEP_WEBPACK_MODULE_ISSUER" + ).getIssuer(this); + } + + /** + * @param {Module | null} value issuer + */ + set issuer(value) { + ModuleGraph.getModuleGraphForModule( + this, + "Module.issuer", + "DEP_WEBPACK_MODULE_ISSUER" + ).setIssuer(this, value); + } + + get usedExports() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.usedExports", + "DEP_WEBPACK_MODULE_USED_EXPORTS" + ).getUsedExports(this, undefined); + } + + /** + * @deprecated + * @returns {(string | OptimizationBailoutFunction)[]} list + */ + get optimizationBailout() { + return ModuleGraph.getModuleGraphForModule( + this, + "Module.optimizationBailout", + "DEP_WEBPACK_MODULE_OPTIMIZATION_BAILOUT" + ).getOptimizationBailout(this); + } + + get optional() { + return this.isOptional( + ModuleGraph.getModuleGraphForModule( + this, + "Module.optional", + "DEP_WEBPACK_MODULE_OPTIONAL" + ) + ); + } + + /** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, when the module was added + */ + addChunk(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; + } + + /** + * @param {Chunk} chunk the chunk + * @returns {void} + */ + removeChunk(chunk) { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.removeChunk", + "DEP_WEBPACK_MODULE_REMOVE_CHUNK" + ).disconnectChunkAndModule(chunk, this); + } + + /** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, when the module is in the chunk + */ + isInChunk(chunk) { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.isInChunk", + "DEP_WEBPACK_MODULE_IS_IN_CHUNK" + ).isModuleInChunk(this, chunk); + } + + isEntryModule() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.isEntryModule", + "DEP_WEBPACK_MODULE_IS_ENTRY_MODULE" + ).isEntryModule(this); + } + + getChunks() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.getChunks", + "DEP_WEBPACK_MODULE_GET_CHUNKS" + ).getModuleChunks(this); + } + + getNumberOfChunks() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.getNumberOfChunks", + "DEP_WEBPACK_MODULE_GET_NUMBER_OF_CHUNKS" + ).getNumberOfModuleChunks(this); + } + + get chunksIterable() { + return ChunkGraph.getChunkGraphForModule( + this, + "Module.chunksIterable", + "DEP_WEBPACK_MODULE_CHUNKS_ITERABLE" + ).getOrderedModuleChunksIterable(this, compareChunksById); + } + + /** + * @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 + + /** + * @returns {string} name of the exports argument + */ + get exportsArgument() { + return (this.buildInfo && this.buildInfo.exportsArgument) || "exports"; + } + + /** + * @returns {string} name of the module argument + */ + get moduleArgument() { + return (this.buildInfo && this.buildInfo.moduleArgument) || "module"; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {boolean | undefined} strict the importing module is strict + * @returns {"namespace" | "default-only" | "default-with-named" | "dynamic"} 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"; + } + } + + /** + * @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 = []; + } + this.presentationalDependencies.push(presentationalDependency); + } + + /** + * @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 = []; + } + this.codeGenerationDependencies.push(codeGenerationDependency); + } + + /** + * Removes all dependencies and blocks + * @returns {void} + */ + clearDependenciesAndBlocks() { + if (this.presentationalDependencies !== undefined) { + this.presentationalDependencies.length = 0; + } + if (this.codeGenerationDependencies !== undefined) { + this.codeGenerationDependencies.length = 0; + } + super.clearDependenciesAndBlocks(); + } + + /** + * @param {WebpackError} warning the warning + * @returns {void} + */ + addWarning(warning) { + if (this._warnings === undefined) { + this._warnings = []; + } + this._warnings.push(warning); + } + + /** + * @returns {Iterable | undefined} list of warnings if any + */ + getWarnings() { + return this._warnings; + } + + /** + * @returns {number} number of warnings + */ + getNumberOfWarnings() { + return this._warnings !== undefined ? this._warnings.length : 0; + } + + /** + * @param {WebpackError} error the error + * @returns {void} + */ + addError(error) { + if (this._errors === undefined) { + this._errors = []; + } + this._errors.push(error); + } + + /** + * @returns {Iterable | undefined} list of errors if any + */ + getErrors() { + return this._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; + } + } + + /** + * @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 ( + !r.dependency || + !r.dependency.optional || + !r.isTargetActive(undefined) + ) { + return false; + } + hasConnections = true; + } + return hasConnections; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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 {string} for debugging + */ + toString() { + return `Module[${this.debugId}: ${this.identifier()}]`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} 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) + ); + } + + /** + * @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; + } + + /** + * @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); + } + + /** + * @returns {void} + */ + invalidateBuild() { + // should be overridden to support this feature + } + + /* istanbul ignore next */ + /** + * @abstract + * @returns {string} a unique identifier of the module + */ + identifier() { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @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 JS_TYPES; + } + + /** + * @abstract + * @deprecated Use codeGeneration() instead + * @param {DependencyTemplates} dependencyTemplates the dependency templates + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {string=} type the type of source that should be generated + * @returns {Source} generated source + */ + source(dependencyTemplates, runtimeTemplate, type = "javascript") { + if (this.codeGeneration === Module.prototype.codeGeneration) { + const AbstractMethodError = require("./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, + codeGenerationResults: undefined + }; + const sources = this.codeGeneration(codeGenContext).sources; + + return /** @type {Source} */ ( + type + ? sources.get(type) + : sources.get(/** @type {string} */ (first(this.getSourceTypes()))) + ); + } + + /* istanbul ignore next */ + /** + * @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("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return null; + } + + /** + * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) + */ + nameForCondition() { + return null; + } + + /** + * @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}`; + } + + /** + * @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; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + // Best override this method + const sources = new Map(); + for (const type of this.getSourceTypes()) { + if (type !== "unknown") { + sources.set( + type, + this.source( + context.dependencyTemplates, + context.runtimeTemplate, + type + ) + ); + } + } + return { + sources, + runtimeRequirements: new Set([ + RuntimeGlobals.module, + RuntimeGlobals.exports, + RuntimeGlobals.require + ]) + }; + } + + /** + * @param {Chunk} chunk the chunk which condition should be checked + * @param {Compilation} compilation the compilation + * @returns {boolean} true, if the chunk is ok for the module + */ + chunkCondition(chunk, compilation) { + return true; + } + + hasChunkCondition() { + return this.chunkCondition !== Module.prototype.chunkCondition; + } + + /** + * Assuming this module is in the cache. Update the (cached) module with + * the fresh module from the factory. Usually updates internal references + * and properties. + * @param {Module} module fresh module + * @returns {void} + */ + updateCacheModule(module) { + this.type = module.type; + this.layer = module.layer; + this.context = module.context; + this.factoryMeta = module.factoryMeta; + this.resolveOptions = module.resolveOptions; + } + + /** + * Module should be unsafe cached. Get data that's needed for that. + * This data will be passed to restoreFromUnsafeCache later. + * @returns {UnsafeCacheData} cached data + */ + getUnsafeCacheData() { + return { + factoryMeta: this.factoryMeta, + resolveOptions: this.resolveOptions + }; + } + + /** + * restore unsafe cache data + * @param {object} 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; + } + + /** + * @returns {Source | null} the original source for the module before webpack transformation + */ + originalSource() { + return null; + } + + /** + * @param {LazySet} fileDependencies set where file dependencies are added to + * @param {LazySet} contextDependencies set where context dependencies are added to + * @param {LazySet} missingDependencies set where missing dependencies are added to + * @param {LazySet} buildDependencies set where build dependencies are added to + */ + addCacheDependencies( + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + ) {} + + /** + * @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.hot); + 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); + } + + /** + * @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.hot = read(); + this._warnings = read(); + this._errors = read(); + this.buildMeta = read(); + this.buildInfo = read(); + this.presentationalDependencies = read(); + this.codeGenerationDependencies = read(); + super.deserialize(context); + } +} + +makeSerializable(Module, "webpack/lib/Module"); + +// TODO remove in webpack 6 +// eslint-disable-next-line no-warning-comments +// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 +Object.defineProperty(Module.prototype, "hasEqualsChunks", { + get() { + throw new Error( + "Module.hasEqualsChunks was renamed (use hasEqualChunks instead)" + ); + } +}); + +// TODO remove in webpack 6 +// eslint-disable-next-line no-warning-comments +// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 +Object.defineProperty(Module.prototype, "isUsed", { + get() { + throw new Error( + "Module.isUsed was renamed (use getUsedName, isExportUsed or isModuleUsed instead)" + ); + } +}); + +// TODO remove in webpack 6 +Object.defineProperty(Module.prototype, "errors", { + get: util.deprecate( + /** + * @this {Module} + * @returns {WebpackError[]} array + */ + function () { + if (this._errors === undefined) { + this._errors = []; + } + return this._errors; + }, + "Module.errors was removed (use getErrors instead)", + "DEP_WEBPACK_MODULE_ERRORS" + ) +}); + +// TODO remove in webpack 6 +Object.defineProperty(Module.prototype, "warnings", { + get: util.deprecate( + /** + * @this {Module} + * @returns {WebpackError[]} array + */ + function () { + if (this._warnings === undefined) { + this._warnings = []; + } + return this._warnings; + }, + "Module.warnings was removed (use getWarnings instead)", + "DEP_WEBPACK_MODULE_WARNINGS" + ) +}); + +// TODO remove in webpack 6 +// eslint-disable-next-line no-warning-comments +// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 +Object.defineProperty(Module.prototype, "used", { + get() { + throw new Error( + "Module.used was refactored (use ModuleGraph.getUsedExports instead)" + ); + }, + set(value) { + throw new Error( + "Module.used was refactored (use ModuleGraph.setUsedExports instead)" + ); + } +}); + +module.exports = Module; diff --git a/webpack-lib/lib/ModuleBuildError.js b/webpack-lib/lib/ModuleBuildError.js new file mode 100644 index 00000000000..b97daa14a18 --- /dev/null +++ b/webpack-lib/lib/ModuleBuildError.js @@ -0,0 +1,79 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { cutOffLoaderExecution } = require("./ErrorHelpers"); +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ModuleBuildError extends WebpackError { + /** + * @param {string | Error&any} err error thrown + * @param {{from?: string|null}} info additional info + */ + constructor(err, { from = null } = {}) { + let message = "Module build failed"; + let details; + + message += from ? ` (from ${from}):\n` : ": "; + + 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; + + message += + typeof err.message === "string" && err.message ? err.message : err; + } + } else if (typeof err.message === "string" && err.message) { + message += err.message; + } else { + message += String(err); + } + } else { + message += String(err); + } + + super(message); + + this.name = "ModuleBuildError"; + this.details = details; + this.error = err; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.error); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.error = read(); + + super.deserialize(context); + } +} + +makeSerializable(ModuleBuildError, "webpack/lib/ModuleBuildError"); + +module.exports = ModuleBuildError; diff --git a/webpack-lib/lib/ModuleDependencyError.js b/webpack-lib/lib/ModuleDependencyError.js new file mode 100644 index 00000000000..bb7341db762 --- /dev/null +++ b/webpack-lib/lib/ModuleDependencyError.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @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 {DependencyLocation} loc location of dependency + */ + constructor(module, err, loc) { + super(err.message); + + this.name = "ModuleDependencyError"; + this.details = + err && !(/** @type {any} */ (err).hideStack) + ? /** @type {string} */ (err.stack).split("\n").slice(1).join("\n") + : undefined; + this.module = module; + this.loc = loc; + /** error is not (de)serialized, so it might be undefined after deserialization */ + this.error = err; + + if (err && /** @type {any} */ (err).hideStack && err.stack) { + this.stack = /** @type {string} */ `${err.stack + .split("\n") + .slice(1) + .join("\n")}\n\n${this.stack}`; + } + } +} + +module.exports = ModuleDependencyError; diff --git a/webpack-lib/lib/ModuleDependencyWarning.js b/webpack-lib/lib/ModuleDependencyWarning.js new file mode 100644 index 00000000000..2fc403b9d66 --- /dev/null +++ b/webpack-lib/lib/ModuleDependencyWarning.js @@ -0,0 +1,47 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ + +class ModuleDependencyWarning extends WebpackError { + /** + * @param {Module} module module tied to dependency + * @param {Error} err error thrown + * @param {DependencyLocation} loc location of dependency + */ + constructor(module, err, loc) { + super(err ? err.message : ""); + + this.name = "ModuleDependencyWarning"; + this.details = + err && !(/** @type {any} */ (err).hideStack) + ? /** @type {string} */ (err.stack).split("\n").slice(1).join("\n") + : undefined; + this.module = module; + this.loc = loc; + /** error is not (de)serialized, so it might be undefined after deserialization */ + this.error = err; + + if (err && /** @type {any} */ (err).hideStack && err.stack) { + this.stack = /** @type {string} */ `${err.stack + .split("\n") + .slice(1) + .join("\n")}\n\n${this.stack}`; + } + } +} + +makeSerializable( + ModuleDependencyWarning, + "webpack/lib/ModuleDependencyWarning" +); + +module.exports = ModuleDependencyWarning; diff --git a/webpack-lib/lib/ModuleError.js b/webpack-lib/lib/ModuleError.js new file mode 100644 index 00000000000..f8227a8fc48 --- /dev/null +++ b/webpack-lib/lib/ModuleError.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { cleanUp } = require("./ErrorHelpers"); +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ModuleError extends WebpackError { + /** + * @param {Error} err error thrown + * @param {{from?: string|null}} info additional info + */ + constructor(err, { from = null } = {}) { + let message = "Module Error"; + + message += from ? ` (from ${from}):\n` : ": "; + + if (err && typeof err === "object" && err.message) { + message += err.message; + } else if (err) { + message += err; + } + + super(message); + + this.name = "ModuleError"; + this.error = err; + this.details = + err && typeof err === "object" && err.stack + ? cleanUp(err.stack, this.message) + : undefined; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.error); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.error = read(); + + super.deserialize(context); + } +} + +makeSerializable(ModuleError, "webpack/lib/ModuleError"); + +module.exports = ModuleError; diff --git a/webpack-lib/lib/ModuleFactory.js b/webpack-lib/lib/ModuleFactory.js new file mode 100644 index 00000000000..7b08be28be5 --- /dev/null +++ b/webpack-lib/lib/ModuleFactory.js @@ -0,0 +1,50 @@ +/* + 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 */ + +/** + * @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 {object} ModuleFactoryCreateDataContextInfo + * @property {string} issuer + * @property {string | null=} issuerLayer + * @property {string} compiler + */ + +/** + * @typedef {object} ModuleFactoryCreateData + * @property {ModuleFactoryCreateDataContextInfo} contextInfo + * @property {ResolveOptions=} resolveOptions + * @property {string} context + * @property {Dependency[]} dependencies + */ + +class ModuleFactory { + /* istanbul ignore next */ + /** + * @abstract + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } +} + +module.exports = ModuleFactory; diff --git a/webpack-lib/lib/ModuleFilenameHelpers.js b/webpack-lib/lib/ModuleFilenameHelpers.js new file mode 100644 index 00000000000..afe3d345338 --- /dev/null +++ b/webpack-lib/lib/ModuleFilenameHelpers.js @@ -0,0 +1,383 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NormalModule = require("./NormalModule"); +const createHash = require("./util/createHash"); +const memoize = require("./util/memoize"); + +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {typeof import("./util/Hash")} Hash */ + +/** @typedef {string | RegExp | (string | RegExp)[]} Matcher */ +/** @typedef {{test?: Matcher, include?: Matcher, exclude?: Matcher }} MatchObject */ + +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.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]"; +// 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]"; +ModuleFilenameHelpers.REGEXP_ALL_LOADERS = /\[all-?loaders\]/gi; +ModuleFilenameHelpers.LOADERS = "[loaders]"; +ModuleFilenameHelpers.REGEXP_LOADERS = /\[loaders\]/gi; +ModuleFilenameHelpers.QUERY = "[query]"; +ModuleFilenameHelpers.REGEXP_QUERY = /\[query\]/gi; +ModuleFilenameHelpers.ID = "[id]"; +ModuleFilenameHelpers.REGEXP_ID = /\[id\]/gi; +ModuleFilenameHelpers.HASH = "[hash]"; +ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi; +ModuleFilenameHelpers.NAMESPACE = "[namespace]"; +ModuleFilenameHelpers.REGEXP_NAMESPACE = /\[namespace\]/gi; + +/** @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.slice(idx); +}; + +/** + * 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.slice(0, idx); +}; + +/** + * Returns a function that returns a hash of the string + * @param {ReturnStringCallback} strFn the function to get the string + * @param {string | Hash=} hashFunction the hash function to use + * @returns {ReturnStringCallback} a function that returns the hash of the string + */ +const getHash = + (strFn, hashFunction = "md4") => + () => { + const hash = createHash(hashFunction); + hash.update(strFn()); + const digest = /** @type {string} */ (hash.digest("hex")); + return digest.slice(0, 4); + }; + +/** + * Returns a function that returns the string with the token replaced with the replacement + * @param {string|RegExp} test A regular expression string or Regular Expression object + * @returns {RegExp} A regular expression object + * @example + * ```js + * const test = asRegExp("test"); + * test.test("test"); // true + * + * const test2 = asRegExp(/test/); + * test2.test("test"); // true + * ``` + */ +const asRegExp = test => { + if (typeof test === "string") { + // Escape special characters in the string to prevent them from being interpreted as special characters in a regular expression. Do this by + // adding a backslash before each special character + test = new RegExp(`^${test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")}`); + } + return test; +}; + +/** + * @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 {object} the lazy access object + */ +const lazyObject = obj => { + const newObj = {}; + 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 newObj; +}; + +const SQUARE_BRACKET_TAG_REGEXP = /\[\\*([\w-]+)\\*\]/gi; + +/** + * @param {Module | string} module the module + * @param {TODO} options options + * @param {object} contextInfo context info + * @param {RequestShortener} contextInfo.requestShortener requestShortener + * @param {ChunkGraph} contextInfo.chunkGraph chunk graph + * @param {string | Hash=} contextInfo.hashFunction the hash function to use + * @returns {string} the filename + */ +ModuleFilenameHelpers.createFilename = ( + // eslint-disable-next-line default-param-last + module = "", + options, + { requestShortener, chunkGraph, hashFunction = "md4" } +) => { + const opts = { + namespace: "", + moduleFilenameTemplate: "", + ...(typeof options === "object" + ? options + : { + moduleFilenameTemplate: options + }) + }; + + let absoluteResourcePath; + let hash; + /** @type {ReturnStringCallback} */ + let identifier; + /** @type {ReturnStringCallback} */ + let moduleId; + /** @type {ReturnStringCallback} */ + let shortIdentifier; + if (typeof module === "string") { + shortIdentifier = + /** @type {ReturnStringCallback} */ + (memoize(() => requestShortener.shorten(module))); + identifier = shortIdentifier; + moduleId = () => ""; + absoluteResourcePath = () => module.split("!").pop(); + hash = getHash(identifier, hashFunction); + } else { + 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 + : module.identifier().split("!").pop(); + hash = getHash(identifier, hashFunction); + } + const resource = + /** @type {ReturnStringCallback} */ + (memoize(() => shortIdentifier().split("!").pop())); + + const loaders = getBefore(shortIdentifier, "!"); + const allLoaders = getBefore(identifier, "!"); + const query = getAfter(resource, "?"); + const resourcePath = () => { + const q = query().length; + return q === 0 ? resource() : resource().slice(0, -q); + }; + if (typeof opts.moduleFilenameTemplate === "function") { + return opts.moduleFilenameTemplate( + 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 + }) + ); + } + + // TODO webpack 6: consider removing alternatives without dashes + /** @type {Map} */ + 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_LOADERS_RESOURCE, + "[short-identifier]" + ) + .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); + + for (const [idx, item] of array.entries()) { + countMap[item] = countMap[item] || []; + countMap[item].push(idx); + posMap[item] = 0; + } + if (comparator) { + 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]++); + } + return item; + }); +}; + +/** + * 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 + * ``` + */ +ModuleFilenameHelpers.matchPart = (str, test) => { + if (!test) return true; + + if (Array.isArray(test)) { + return test.map(asRegExp).some(regExp => regExp.test(str)); + } + return asRegExp(test).test(str); +}; + +/** + * 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 && !ModuleFilenameHelpers.matchPart(str, obj.test)) { + return false; + } + if (obj.include && !ModuleFilenameHelpers.matchPart(str, obj.include)) { + return false; + } + if (obj.exclude && ModuleFilenameHelpers.matchPart(str, obj.exclude)) { + return false; + } + return true; +}; diff --git a/webpack-lib/lib/ModuleGraph.js b/webpack-lib/lib/ModuleGraph.js new file mode 100644 index 00000000000..783c6e414d6 --- /dev/null +++ b/webpack-lib/lib/ModuleGraph.js @@ -0,0 +1,896 @@ +/* + 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 SortableSet = require("./util/SortableSet"); +const WeakTupleMap = require("./util/WeakTupleMap"); + +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleProfile")} ModuleProfile */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @callback OptimizationBailoutFunction + * @param {RequestShortener} requestShortener + * @returns {string} + */ + +const EMPTY_SET = new Set(); + +/** + * @param {SortableSet} set input + * @returns {readonly Map} mapped by origin module + */ +const getConnectionsByOriginModule = set => { + const map = new Map(); + /** @type {Module | 0} */ + let lastModule = 0; + /** @type {ModuleGraphConnection[] | undefined} */ + let lastList; + for (const connection of set) { + const { originModule } = connection; + if (lastModule === originModule) { + /** @type {ModuleGraphConnection[]} */ + (lastList).push(connection); + } else { + lastModule = /** @type {Module} */ (originModule); + const list = map.get(originModule); + if (list !== undefined) { + lastList = list; + list.push(connection); + } else { + const list = [connection]; + lastList = list; + map.set(originModule, list); + } + } + } + return map; +}; + +/** + * @param {SortableSet} set input + * @returns {readonly Map} mapped by module + */ +const getConnectionsByModule = set => { + const map = new Map(); + /** @type {Module | 0} */ + let lastModule = 0; + /** @type {ModuleGraphConnection[] | undefined} */ + let lastList; + for (const connection of set) { + const { module } = connection; + if (lastModule === module) { + /** @type {ModuleGraphConnection[]} */ + (lastList).push(connection); + } else { + lastModule = module; + const list = map.get(module); + if (list !== undefined) { + lastList = list; + list.push(connection); + } else { + const list = [connection]; + lastList = list; + map.set(module, list); + } + } + } + return map; +}; + +/** @typedef {SortableSet} IncomingConnections */ +/** @typedef {SortableSet} OutgoingConnections */ + +class ModuleGraphModule { + constructor() { + /** @type {IncomingConnections} */ + this.incomingConnections = new SortableSet(); + /** @type {OutgoingConnections | undefined} */ + this.outgoingConnections = undefined; + /** @type {Module | null | undefined} */ + this.issuer = undefined; + /** @type {(string | OptimizationBailoutFunction)[]} */ + 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; + } +} + +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 {Map> | undefined} + * @private + */ + this._moduleMemCaches = undefined; + + /** + * @type {string | undefined} + * @private + */ + this._cacheStage = undefined; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Dependency} dependency the dependency + * @returns {Module | undefined} parent module + */ + getParentModule(dependency) { + return dependency._parentModule; + } + + /** + * @param {Dependency} dependency the dependency + * @returns {DependenciesBlock | undefined} parent block + */ + getParentBlock(dependency) { + return dependency._parentDependenciesBlock; + } + + /** + * @param {Dependency} dependency the dependency + * @returns {number} index + */ + getParentBlockIndex(dependency) { + return dependency._parentDependenciesBlockIndex; + } + + /** + * @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, + 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); + } + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @returns {void} + */ + removeAllModuleAttributes() { + for (const mgm of this._moduleMap.values()) { + mgm.postOrderIndex = null; + mgm.preOrderIndex = null; + mgm.depth = null; + mgm.async = false; + } + } + + /** + * @param {Module} oldModule the old referencing module + * @param {Module} newModule the new referencing module + * @param {function(ModuleGraphConnection): boolean} 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); + } + } + } + + /** + * @param {Module} oldModule the old referencing module + * @param {Module} newModule the new referencing module + * @param {function(ModuleGraphConnection): boolean} 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); + } + } + } + } + } + + /** + * @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)); + } + + /** + * @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; + } + + /** + * @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 + ) { + 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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {Iterable} reasons why a module is included + */ + getIncomingConnections(module) { + const connections = this._getModuleGraphModule(module).incomingConnections; + return 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; + } + + /** + * @param {Module} module the module + * @returns {readonly Map} reasons why a module is included, in a map by source module + */ + getIncomingConnectionsByOriginModule(module) { + const connections = this._getModuleGraphModule(module).incomingConnections; + return connections.getFromUnorderedCache(getConnectionsByOriginModule); + } + + /** + * @param {Module} module the module + * @returns {readonly Map | undefined} connections to modules, in a map by module + */ + getOutgoingConnectionsByModule(module) { + const connections = this._getModuleGraphModule(module).outgoingConnections; + return connections === undefined + ? undefined + : connections.getFromUnorderedCache(getConnectionsByModule); + } + + /** + * @param {Module} module the module + * @returns {ModuleProfile | undefined} the module profile + */ + getProfile(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.profile; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {Module | null | undefined} the issuer module + */ + getIssuer(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.issuer; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {(string | OptimizationBailoutFunction)[]} optimization bailouts + */ + getOptimizationBailout(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.optimizationBailout; + } + + /** + * @param {Module} module the module + * @returns {true | string[] | null} the provided exports + */ + getProvidedExports(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.exports.getProvidedExports(); + } + + /** + * @param {Module} module the module + * @param {string | string[]} 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; + } + + /** + * @param {Module} module the module + * @returns {ExportsInfo} info about the exports + */ + getExportsInfo(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.exports; + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @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); + } + + /** + * @param {Module} module the module + * @returns {number | null} the index of the module + */ + getPreOrderIndex(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.preOrderIndex; + } + + /** + * @param {Module} module the module + * @returns {number | null} the index of the module + */ + getPostOrderIndex(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.postOrderIndex; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {number | null} the depth of the module + */ + getDepth(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.depth; + } + + /** + * @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; + } + + /** + * @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; + } + + /** + * @param {Module} module the module + * @returns {boolean} true, if the module is async + */ + isAsync(module) { + const mgm = this._getModuleGraphModule(module); + return mgm.async; + } + + /** + * @param {Module} module the module + * @returns {void} + */ + setAsync(module) { + const mgm = this._getModuleGraphModule(module); + mgm.async = true; + } + + /** + * @param {any} thing any thing + * @returns {object} metadata + */ + getMeta(thing) { + let meta = this._metaMap.get(thing); + if (meta === undefined) { + meta = Object.create(null); + this._metaMap.set(thing, /** @type {object} */ (meta)); + } + return /** @type {object} */ (meta); + } + + /** + * @param {any} thing any thing + * @returns {object | undefined} metadata + */ + getMetaIfExisting(thing) { + return this._metaMap.get(thing); + } + + /** + * @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; + } + + /** + * @template {any[]} T + * @template V + * @param {(moduleGraph: ModuleGraph, ...args: T) => V} fn computer + * @param {T} args arguments + * @returns {V} computed value or cached + */ + cached(fn, ...args) { + if (this._cache === undefined) return fn(this, ...args); + return this._cache.provide(fn, ...args, () => fn(this, ...args)); + } + + /** + * @param {Map>} moduleMemCaches mem caches for modules for better caching + */ + setModuleMemCaches(moduleMemCaches) { + this._moduleMemCaches = moduleMemCaches; + } + + /** + * @param {Dependency} dependency dependency + * @param {...any} args arguments, last argument is a function called with moduleGraph, dependency, ...args + * @returns {any} computed value or cached + */ + dependencyCacheProvide(dependency, ...args) { + /** @type {(moduleGraph: ModuleGraph, dependency: Dependency, ...args: any[]) => any} */ + const fn = 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 + /** + * @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( + /** + * @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 + /** + * @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 + /** + * @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/webpack-lib/lib/ModuleGraphConnection.js b/webpack-lib/lib/ModuleGraphConnection.js new file mode 100644 index 00000000000..1f12ac9e5cc --- /dev/null +++ b/webpack-lib/lib/ModuleGraphConnection.js @@ -0,0 +1,205 @@ +/* + 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 */ + +/** + * @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; +}; + +/** + * @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 { + /** + * @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 | undefined} condition condition for the connection + */ + constructor( + originModule, + dependency, + module, + explanation, + weak = false, + condition = undefined + ) { + this.originModule = originModule; + this.resolvedOriginModule = originModule; + this.dependency = dependency; + this.resolvedModule = module; + this.module = module; + this.weak = weak; + this.conditional = Boolean(condition); + this._active = condition !== false; + /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState) | 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; + } + + /** + * @param {function(ModuleGraphConnection, RuntimeSpec): ConnectionState} condition condition for the connection + * @returns {void} + */ + addCondition(condition) { + if (this.conditional) { + const old = + /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ + (this.condition); + this.condition = (c, r) => + intersectConnectionStates(old(c, r), condition(c, r)); + } else if (this._active) { + this.conditional = true; + this.condition = condition; + } + } + + /** + * @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 Array.from(this.explanations).join(" "); + } + + /** + * @param {RuntimeSpec} runtime the runtime + * @returns {boolean} true, if the connection is active + */ + isActive(runtime) { + if (!this.conditional) return this._active; + + return ( + /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( + this.condition + )(this, runtime) !== false + ); + } + + /** + * @param {RuntimeSpec} runtime the runtime + * @returns {boolean} true, if the connection is active + */ + isTargetActive(runtime) { + if (!this.conditional) return this._active; + return ( + /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( + this.condition + )(this, runtime) === true + ); + } + + /** + * @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 {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( + this.condition + )(this, runtime); + } + + /** + * @param {boolean} value active or not + * @returns {void} + */ + setActive(value) { + this.conditional = false; + this._active = value; + } + + // TODO webpack 5 remove + get active() { + throw new Error("Use getActiveState instead"); + } + + set active(value) { + throw new Error("Use setActive instead"); + } +} + +/** @typedef {typeof TRANSITIVE_ONLY} TRANSITIVE_ONLY */ +/** @typedef {typeof CIRCULAR_CONNECTION} CIRCULAR_CONNECTION */ + +module.exports = ModuleGraphConnection; +module.exports.addConnectionStates = addConnectionStates; +module.exports.TRANSITIVE_ONLY = /** @type {typeof TRANSITIVE_ONLY} */ ( + TRANSITIVE_ONLY +); +module.exports.CIRCULAR_CONNECTION = /** @type {typeof CIRCULAR_CONNECTION} */ ( + CIRCULAR_CONNECTION +); diff --git a/webpack-lib/lib/ModuleHashingError.js b/webpack-lib/lib/ModuleHashingError.js new file mode 100644 index 00000000000..77c8f415aff --- /dev/null +++ b/webpack-lib/lib/ModuleHashingError.js @@ -0,0 +1,29 @@ +/* + 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 ModuleHashingError extends WebpackError { + /** + * Create a new ModuleHashingError + * @param {Module} module related module + * @param {Error} error Original error + */ + constructor(module, error) { + super(); + + this.name = "ModuleHashingError"; + this.error = error; + this.message = error.message; + this.details = error.stack; + this.module = module; + } +} + +module.exports = ModuleHashingError; diff --git a/webpack-lib/lib/ModuleInfoHeaderPlugin.js b/webpack-lib/lib/ModuleInfoHeaderPlugin.js new file mode 100644 index 00000000000..994bfed88cb --- /dev/null +++ b/webpack-lib/lib/ModuleInfoHeaderPlugin.js @@ -0,0 +1,314 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource, RawSource, CachedSource } = 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("./ModuleTemplate")} ModuleTemplate */ +/** @typedef {import("./RequestShortener")} RequestShortener */ + +/** + * @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; +}; + +/** + * @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 + 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` + ); + } + } +}; + +/** @type {WeakMap }>>} */ +const caches = new WeakMap(); + +class ModuleInfoHeaderPlugin { + /** + * @param {boolean=} verbose add more information like exports, runtime requirements and bailouts + */ + constructor(verbose = true) { + this._verbose = verbose; + } + + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + const { _verbose: verbose } = this; + compiler.hooks.compilation.tap("ModuleInfoHeaderPlugin", compilation => { + const javascriptHooks = + JavascriptModulesPlugin.getCompilationHooks(compilation); + javascriptHooks.renderModulePackage.tap( + "ModuleInfoHeaderPlugin", + ( + moduleSource, + module, + { chunk, chunkGraph, moduleGraph, runtimeTemplate } + ) => { + const { requestShortener } = runtimeTemplate; + 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( + "ModuleInfoHeaderPlugin", + (_chunk, hash) => { + hash.update("ModuleInfoHeaderPlugin"); + hash.update("1"); + } + ); + const cssHooks = CssModulesPlugin.getCompilationHooks(compilation); + cssHooks.renderModulePackage.tap( + "ModuleInfoHeaderPlugin", + (moduleSource, module, { runtimeTemplate }) => { + const { requestShortener } = runtimeTemplate; + 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("ModuleInfoHeaderPlugin", (_chunk, hash) => { + hash.update("ModuleInfoHeaderPlugin"); + hash.update("1"); + }); + }); + } + + /** + * @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/webpack-lib/lib/ModuleNotFoundError.js b/webpack-lib/lib/ModuleNotFoundError.js new file mode 100644 index 00000000000..6fdf241dee8 --- /dev/null +++ b/webpack-lib/lib/ModuleNotFoundError.js @@ -0,0 +1,89 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ + +const previouslyPolyfilledBuiltinModules = { + assert: "assert/", + buffer: "buffer/", + console: "console-browserify", + constants: "constants-browserify", + crypto: "crypto-browserify", + domain: "domain-browser", + events: "events/", + http: "stream-http", + https: "https-browserify", + os: "os-browserify/browser", + path: "path-browserify", + punycode: "punycode/", + process: "process/browser", + querystring: "querystring-es3", + stream: "stream-browserify", + _stream_duplex: "readable-stream/duplex", + _stream_passthrough: "readable-stream/passthrough", + _stream_readable: "readable-stream/readable", + _stream_transform: "readable-stream/transform", + _stream_writable: "readable-stream/writable", + string_decoder: "string_decoder/", + sys: "util/", + timers: "timers-browserify", + tty: "tty-browserify", + url: "url/", + util: "util/", + vm: "vm-browserify", + zlib: "browserify-zlib" +}; + +class ModuleNotFoundError extends WebpackError { + /** + * @param {Module | null} module module tied to dependency + * @param {Error&any} err error thrown + * @param {DependencyLocation} loc location of dependency + */ + constructor(module, err, loc) { + let message = `Module not found: ${err.toString()}`; + + // TODO remove in webpack 6 + const match = err.message.match(/Can't resolve '([^']+)'/); + if (match) { + const request = match[1]; + const alias = + previouslyPolyfilledBuiltinModules[ + /** @type {keyof previouslyPolyfilledBuiltinModules} */ (request) + ]; + if (alias) { + const pathIndex = alias.indexOf("/"); + const dependency = pathIndex > 0 ? alias.slice(0, pathIndex) : alias; + message += + "\n\n" + + "BREAKING CHANGE: " + + "webpack < 5 used to include polyfills for node.js core modules by default.\n" + + "This is no longer the case. Verify if you need this module and configure a polyfill for it.\n\n"; + message += + "If you want to include a polyfill, you need to:\n" + + `\t- add a fallback 'resolve.fallback: { "${request}": require.resolve("${alias}") }'\n` + + `\t- install '${dependency}'\n`; + message += + "If you don't want to include a polyfill, you can use an empty module like this:\n" + + `\tresolve.fallback: { "${request}": false }`; + } + } + + super(message); + + this.name = "ModuleNotFoundError"; + this.details = err.details; + this.module = module; + this.error = err; + this.loc = loc; + } +} + +module.exports = ModuleNotFoundError; diff --git a/webpack-lib/lib/ModuleParseError.js b/webpack-lib/lib/ModuleParseError.js new file mode 100644 index 00000000000..d14c763aec8 --- /dev/null +++ b/webpack-lib/lib/ModuleParseError.js @@ -0,0 +1,117 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +const WASM_HEADER = Buffer.from([0x00, 0x61, 0x73, 0x6d]); + +class ModuleParseError extends WebpackError { + /** + * @param {string | Buffer} source source code + * @param {Error & any} err the parse error + * @param {string[]} loaders the loaders used + * @param {string} type module type + */ + constructor(source, err, loaders, type) { + let message = `Module parse failed: ${err && err.message}`; + let loc; + + if ( + ((Buffer.isBuffer(source) && source.slice(0, 4).equals(WASM_HEADER)) || + (typeof source === "string" && /^\0asm/.test(source))) && + !type.startsWith("webassembly") + ) { + message += + "\nThe module seem to be a WebAssembly module, but module is not flagged as WebAssembly module for webpack."; + message += + "\nBREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental feature."; + message += + "\nYou need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated)."; + message += + "\nFor files that transpile to WebAssembly, make sure to set the module type in the 'module.rules' section of the config (e. g. 'type: \"webassembly/async\"')."; + } else if (!loaders) { + message += + "\nYou may need an appropriate loader to handle this file type."; + } else if (loaders.length >= 1) { + message += `\nFile was processed with these loaders:${loaders + .map(loader => `\n * ${loader}`) + .join("")}`; + message += + "\nYou may need an additional loader to handle the result of these loaders."; + } else { + message += + "\nYou may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders"; + } + + if ( + err && + err.loc && + typeof err.loc === "object" && + typeof err.loc.line === "number" + ) { + const lineNumber = err.loc.line; + + if ( + Buffer.isBuffer(source) || + /[\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(/\r?\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 = { start: err.loc }; + } else if (err && err.stack) { + message += `\n${err.stack}`; + } + + super(message); + + this.name = "ModuleParseError"; + this.loc = loc; + this.error = err; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.error); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.error = read(); + + super.deserialize(context); + } +} + +makeSerializable(ModuleParseError, "webpack/lib/ModuleParseError"); + +module.exports = ModuleParseError; diff --git a/webpack-lib/lib/ModuleProfile.js b/webpack-lib/lib/ModuleProfile.js new file mode 100644 index 00000000000..360991ab005 --- /dev/null +++ b/webpack-lib/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/webpack-lib/lib/ModuleRestoreError.js b/webpack-lib/lib/ModuleRestoreError.js new file mode 100644 index 00000000000..2570862d421 --- /dev/null +++ b/webpack-lib/lib/ModuleRestoreError.js @@ -0,0 +1,44 @@ +/* + 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 ModuleRestoreError extends WebpackError { + /** + * @param {Module} module module tied to dependency + * @param {string | Error} err error thrown + */ + constructor(module, err) { + let message = "Module restore failed: "; + /** @type {string | undefined} */ + const details = undefined; + if (err !== null && typeof err === "object") { + if (typeof err.stack === "string" && err.stack) { + const stack = err.stack; + message += stack; + } else if (typeof err.message === "string" && err.message) { + message += err.message; + } else { + message += err; + } + } else { + message += String(err); + } + + super(message); + + this.name = "ModuleRestoreError"; + /** @type {string | undefined} */ + this.details = details; + this.module = module; + this.error = err; + } +} + +module.exports = ModuleRestoreError; diff --git a/webpack-lib/lib/ModuleSourceTypesConstants.js b/webpack-lib/lib/ModuleSourceTypesConstants.js new file mode 100644 index 00000000000..ec5b6706d84 --- /dev/null +++ b/webpack-lib/lib/ModuleSourceTypesConstants.js @@ -0,0 +1,112 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +/** + * @type {ReadonlySet} + */ +const NO_TYPES = new Set(); + +/** + * @type {ReadonlySet<"asset">} + */ +const ASSET_TYPES = new Set(["asset"]); + +/** + * @type {ReadonlySet<"asset" | "javascript" | "asset">} + */ +const ASSET_AND_JS_TYPES = new Set(["asset", "javascript"]); + +/** + * @type {ReadonlySet<"css-url" | "asset">} + */ +const ASSET_AND_CSS_URL_TYPES = new Set(["asset", "css-url"]); + +/** + * @type {ReadonlySet<"javascript" | "css-url" | "asset">} + */ +const ASSET_AND_JS_AND_CSS_URL_TYPES = new Set([ + "asset", + "javascript", + "css-url" +]); + +/** + * @type {ReadonlySet<"javascript">} + */ +const JS_TYPES = new Set(["javascript"]); + +/** + * @type {ReadonlySet<"javascript" | "css-export">} + */ +const JS_AND_CSS_EXPORT_TYPES = new Set(["javascript", "css-export"]); + +/** + * @type {ReadonlySet<"javascript" | "css-url">} + */ +const JS_AND_CSS_URL_TYPES = new Set(["javascript", "css-url"]); + +/** + * @type {ReadonlySet<"javascript" | "css">} + */ +const JS_AND_CSS_TYPES = new Set(["javascript", "css"]); + +/** + * @type {ReadonlySet<"css">} + */ +const CSS_TYPES = new Set(["css"]); + +/** + * @type {ReadonlySet<"css-url">} + */ +const CSS_URL_TYPES = new Set(["css-url"]); +/** + * @type {ReadonlySet<"css-import">} + */ +const CSS_IMPORT_TYPES = new Set(["css-import"]); + +/** + * @type {ReadonlySet<"webassembly">} + */ +const WEBASSEMBLY_TYPES = new Set(["webassembly"]); + +/** + * @type {ReadonlySet<"runtime">} + */ +const RUNTIME_TYPES = new Set(["runtime"]); + +/** + * @type {ReadonlySet<"remote" | "share-init">} + */ +const REMOTE_AND_SHARE_INIT_TYPES = new Set(["remote", "share-init"]); + +/** + * @type {ReadonlySet<"consume-shared">} + */ +const CONSUME_SHARED_TYPES = new Set(["consume-shared"]); + +/** + * @type {ReadonlySet<"share-init">} + */ +const SHARED_INIT_TYPES = new Set(["share-init"]); + +module.exports.NO_TYPES = NO_TYPES; +module.exports.JS_TYPES = JS_TYPES; +module.exports.JS_AND_CSS_TYPES = JS_AND_CSS_TYPES; +module.exports.JS_AND_CSS_URL_TYPES = JS_AND_CSS_URL_TYPES; +module.exports.JS_AND_CSS_EXPORT_TYPES = JS_AND_CSS_EXPORT_TYPES; +module.exports.ASSET_TYPES = ASSET_TYPES; +module.exports.ASSET_AND_JS_TYPES = ASSET_AND_JS_TYPES; +module.exports.ASSET_AND_CSS_URL_TYPES = ASSET_AND_CSS_URL_TYPES; +module.exports.ASSET_AND_JS_AND_CSS_URL_TYPES = ASSET_AND_JS_AND_CSS_URL_TYPES; +module.exports.CSS_TYPES = CSS_TYPES; +module.exports.CSS_URL_TYPES = CSS_URL_TYPES; +module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES; +module.exports.WEBASSEMBLY_TYPES = WEBASSEMBLY_TYPES; +module.exports.RUNTIME_TYPES = RUNTIME_TYPES; +module.exports.REMOTE_AND_SHARE_INIT_TYPES = REMOTE_AND_SHARE_INIT_TYPES; +module.exports.CONSUME_SHARED_TYPES = CONSUME_SHARED_TYPES; +module.exports.SHARED_INIT_TYPES = SHARED_INIT_TYPES; diff --git a/webpack-lib/lib/ModuleStoreError.js b/webpack-lib/lib/ModuleStoreError.js new file mode 100644 index 00000000000..26ca0c8b5d7 --- /dev/null +++ b/webpack-lib/lib/ModuleStoreError.js @@ -0,0 +1,43 @@ +/* + 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 ModuleStoreError extends WebpackError { + /** + * @param {Module} module module tied to dependency + * @param {string | Error} err error thrown + */ + constructor(module, err) { + let message = "Module storing failed: "; + /** @type {string | undefined} */ + const details = undefined; + if (err !== null && typeof err === "object") { + if (typeof err.stack === "string" && err.stack) { + const stack = err.stack; + message += stack; + } else if (typeof err.message === "string" && err.message) { + message += err.message; + } else { + message += err; + } + } else { + message += String(err); + } + + super(message); + + this.name = "ModuleStoreError"; + this.details = /** @type {string | undefined} */ (details); + this.module = module; + this.error = err; + } +} + +module.exports = ModuleStoreError; diff --git a/webpack-lib/lib/ModuleTemplate.js b/webpack-lib/lib/ModuleTemplate.js new file mode 100644 index 00000000000..799037710d7 --- /dev/null +++ b/webpack-lib/lib/ModuleTemplate.js @@ -0,0 +1,174 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ +/** @typedef {import("./util/Hash")} Hash */ + +/** + * @template T + * @typedef {import("tapable").IfSet} IfSet + */ + +const getJavascriptModulesPlugin = memoize(() => + require("./javascript/JavascriptModulesPlugin") +); + +// TODO webpack 6: remove this class +class ModuleTemplate { + /** + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {Compilation} compilation the compilation + */ + constructor(runtimeTemplate, compilation) { + this._runtimeTemplate = runtimeTemplate; + this.type = "javascript"; + this.hooks = Object.freeze({ + content: { + tap: util.deprecate( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Module, ChunkRenderContext, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Module, ChunkRenderContext, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Module, ChunkRenderContext, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(Source, Module, ChunkRenderContext, 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( + /** + * @template AdditionalOptions + * @param {string | Tap & IfSet} options options + * @param {function(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" + ) + } + }); + } +} + +Object.defineProperty(ModuleTemplate.prototype, "runtimeTemplate", { + get: util.deprecate( + /** + * @this {ModuleTemplate} + * @returns {RuntimeTemplate} output options + */ + function () { + return this._runtimeTemplate; + }, + "ModuleTemplate.runtimeTemplate is deprecated (use Compilation.runtimeTemplate instead)", + "DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS" + ) +}); + +module.exports = ModuleTemplate; diff --git a/webpack-lib/lib/ModuleTypeConstants.js b/webpack-lib/lib/ModuleTypeConstants.js new file mode 100644 index 00000000000..dee3ae9f001 --- /dev/null +++ b/webpack-lib/lib/ModuleTypeConstants.js @@ -0,0 +1,168 @@ +/* + 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<"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/raw-data-url">} + * TODO: Document what this asset type is for. See css-loader tests for its usage. + */ +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. + * TODO: Document this better. + */ +const WEBPACK_MODULE_TYPE_FALLBACK = "fallback-module"; + +/** + * @type {Readonly<"remote-module">} + * This is the module type used for the ModuleFederation feature's RemoteModule class. + * TODO: Document this better. + */ +const WEBPACK_MODULE_TYPE_REMOTE = "remote-module"; + +/** + * @type {Readonly<"provide-module">} + * This is the module type used for the ModuleFederation feature's ProvideModule class. + * TODO: Document this better. + */ +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"} CSSModuleTypes */ +/** @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 | AssetModuleTypes | WebpackModuleTypes | UnknownModuleTypes} ModuleTypes */ + +module.exports.ASSET_MODULE_TYPE = ASSET_MODULE_TYPE; +module.exports.ASSET_MODULE_TYPE_RAW_DATA_URL = ASSET_MODULE_TYPE_RAW_DATA_URL; +module.exports.ASSET_MODULE_TYPE_SOURCE = ASSET_MODULE_TYPE_SOURCE; +module.exports.ASSET_MODULE_TYPE_RESOURCE = ASSET_MODULE_TYPE_RESOURCE; +module.exports.ASSET_MODULE_TYPE_INLINE = ASSET_MODULE_TYPE_INLINE; +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; +module.exports.WEBASSEMBLY_MODULE_TYPE_ASYNC = WEBASSEMBLY_MODULE_TYPE_ASYNC; +module.exports.WEBASSEMBLY_MODULE_TYPE_SYNC = WEBASSEMBLY_MODULE_TYPE_SYNC; +module.exports.CSS_MODULE_TYPE = CSS_MODULE_TYPE; +module.exports.CSS_MODULE_TYPE_GLOBAL = CSS_MODULE_TYPE_GLOBAL; +module.exports.CSS_MODULE_TYPE_MODULE = CSS_MODULE_TYPE_MODULE; +module.exports.CSS_MODULE_TYPE_AUTO = CSS_MODULE_TYPE_AUTO; +module.exports.WEBPACK_MODULE_TYPE_RUNTIME = WEBPACK_MODULE_TYPE_RUNTIME; +module.exports.WEBPACK_MODULE_TYPE_FALLBACK = WEBPACK_MODULE_TYPE_FALLBACK; +module.exports.WEBPACK_MODULE_TYPE_REMOTE = WEBPACK_MODULE_TYPE_REMOTE; +module.exports.WEBPACK_MODULE_TYPE_PROVIDE = WEBPACK_MODULE_TYPE_PROVIDE; +module.exports.WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE = + WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE; +module.exports.WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY = + WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY; diff --git a/webpack-lib/lib/ModuleWarning.js b/webpack-lib/lib/ModuleWarning.js new file mode 100644 index 00000000000..9b45a9afdd0 --- /dev/null +++ b/webpack-lib/lib/ModuleWarning.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { cleanUp } = require("./ErrorHelpers"); +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ModuleWarning extends WebpackError { + /** + * @param {Error} warning error thrown + * @param {{from?: string|null}} info additional info + */ + constructor(warning, { from = null } = {}) { + let message = "Module Warning"; + + message += from ? ` (from ${from}):\n` : ": "; + + if (warning && typeof warning === "object" && warning.message) { + message += warning.message; + } else if (warning) { + message += String(warning); + } + + super(message); + + this.name = "ModuleWarning"; + this.warning = warning; + this.details = + warning && typeof warning === "object" && warning.stack + ? cleanUp(warning.stack, this.message) + : undefined; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.warning); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.warning = read(); + + super.deserialize(context); + } +} + +makeSerializable(ModuleWarning, "webpack/lib/ModuleWarning"); + +module.exports = ModuleWarning; diff --git a/webpack-lib/lib/MultiCompiler.js b/webpack-lib/lib/MultiCompiler.js new file mode 100644 index 00000000000..8c72da319d9 --- /dev/null +++ b/webpack-lib/lib/MultiCompiler.js @@ -0,0 +1,659 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const { SyncHook, MultiHook } = require("tapable"); + +const ConcurrentCompilationError = require("./ConcurrentCompilationError"); +const MultiStats = require("./MultiStats"); +const MultiWatching = require("./MultiWatching"); +const WebpackError = require("./WebpackError"); +const ArrayQueue = require("./util/ArrayQueue"); + +/** @template T @typedef {import("tapable").AsyncSeriesHook} AsyncSeriesHook */ +/** @template T @template R @typedef {import("tapable").SyncBailHook} SyncBailHook */ +/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Stats")} Stats */ +/** @typedef {import("./Watching")} Watching */ +/** @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 */ + +/** + * @template T + * @callback Callback + * @param {Error | null} err + * @param {T=} result + */ + +/** + * @callback RunWithDependenciesHandler + * @param {Compiler} compiler + * @param {Callback} callback + */ + +/** + * @typedef {object} MultiCompilerOptions + * @property {number=} parallelism how many Compilers are allows to run at the same time in parallel + */ + +module.exports = class MultiCompiler { + /** + * @param {Compiler[] | Record} compilers child compilers + * @param {MultiCompilerOptions} options options + */ + constructor(compilers, options) { + if (!Array.isArray(compilers)) { + /** @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; + for (let index = 0; index < this.compilers.length; index++) { + const compiler = this.compilers[index]; + const compilerIndex = index; + let compilerDone = false; + // eslint-disable-next-line no-loop-func + compiler.hooks.done.tap("MultiCompiler", stats => { + if (!compilerDone) { + compilerDone = true; + doneCompilers++; + } + compilerStats[compilerIndex] = stats; + if (doneCompilers === this.compilers.length) { + this.hooks.done.call( + new MultiStats(/** @type {Stats[]} */ (compilerStats)) + ); + } + }); + // eslint-disable-next-line no-loop-func + compiler.hooks.invalid.tap("MultiCompiler", () => { + if (compilerDone) { + compilerDone = false; + doneCompilers--; + } + }); + } + this._validateCompilersOptions(); + } + + _validateCompilersOptions() { + if (this.compilers.length < 2) return; + /** + * @param {Compiler} compiler compiler + * @param {WebpackError} warning warning + */ + const addWarning = (compiler, warning) => { + compiler.hooks.thisCompilation.tap("MultiCompiler", compilation => { + compilation.warnings.push(warning); + }); + }; + const cacheNames = new Set(); + for (const compiler of this.compilers) { + if (compiler.options.cache && "name" in compiler.options.cache) { + const name = 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() { + let commonPath = this.compilers[0].outputPath; + for (const compiler of this.compilers) { + while ( + compiler.outputPath.indexOf(commonPath) !== 0 && + /[/\\]/.test(commonPath) + ) { + commonPath = commonPath.replace(/[/\\][^/\\]*$/, ""); + } + } + + if (!commonPath && this.compilers[0].outputPath[0] === "/") return "/"; + return commonPath; + } + + get inputFileSystem() { + throw new Error("Cannot read inputFileSystem of a MultiCompiler"); + } + + /** + * @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"); + } + + /** + * @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"); + } + + /** + * @param {WatchFileSystem} value the new watch file system + */ + set watchFileSystem(value) { + for (const compiler of this.compilers) { + compiler.watchFileSystem = value; + } + } + + /** + * @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"); + } + + /** + * @param {string | (function(): 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); + } + + /** + * @param {Compiler} compiler the child compiler + * @param {string[]} dependencies its dependencies + * @returns {void} + */ + setDependencies(compiler, dependencies) { + this.dependencies.set(compiler, 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 = []; + /** + * @param {Compiler} compiler compiler + * @returns {boolean} target was found + */ + const targetFound = compiler => { + for (const edge of edges) { + if (edge.target === compiler) { + return true; + } + } + return false; + }; + /** + * @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) { + 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 { + edges.add({ + source, + target + }); + } + } + } + } + /** @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) { + if (edge.source === current) { + edges.delete(edge); + const target = edge.target; + if (!targetFound(target)) { + stack.push(target); + } + } + } + } + if (edges.size > 0) { + /** @type {string[]} */ + const lines = Array.from(edges) + .sort(sortEdges) + .map(edge => `${edge.source.name} -> ${edge.target.name}`); + lines.unshift("Circular dependency found in compiler dependencies."); + errors.unshift(lines.join("\n")); + } + if (errors.length > 0) { + const message = errors.join("\n"); + callback(new Error(message)); + return false; + } + return true; + } + + // TODO webpack 6 remove + /** + * @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) { + const fulfilledNames = new Set(); + let remainingCompilers = compilers; + /** + * @param {string} d dependency + * @returns {boolean} when dependency was fulfilled + */ + const isDependencyFulfilled = d => fulfilledNames.has(d); + /** + * @returns {Compiler[]} compilers + */ + const getReadyCompilers = () => { + const readyCompilers = []; + const list = remainingCompilers; + remainingCompilers = []; + for (const c of list) { + const dependencies = this.dependencies.get(c); + const ready = + !dependencies || dependencies.every(isDependencyFulfilled); + if (ready) { + readyCompilers.push(c); + } else { + remainingCompilers.push(c); + } + } + return readyCompilers; + }; + /** + * @param {Callback} callback callback + * @returns {void} + */ + const runCompilers = callback => { + if (remainingCompilers.length === 0) return callback(null); + asyncLib.map( + getReadyCompilers(), + (compiler, callback) => { + fn(compiler, err => { + if (err) return callback(err); + fulfilledNames.add(compiler.name); + runCompilers(callback); + }); + }, + (err, results) => { + callback(err, /** @type {TODO} */ (results)); + } + ); + }; + runCompilers(callback); + } + + /** + * @template SetupResult + * @param {function(Compiler, number, Callback, function(): boolean, function(): void, function(): void): SetupResult} setup setup a single compiler + * @param {function(Compiler, SetupResult, 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); + /** + * @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(); + }; + /** + * @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); + } + }; + /** + * @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); + } + }; + /** + * @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") + ) { + 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; + } + + /** + * @param {WatchOptions|WatchOptions[]} watchOptions the watcher's options + * @param {Callback} handler signals when the call finishes + * @returns {MultiWatching} a compiler watcher + */ + watch(watchOptions, handler) { + if (this.running) { + return handler(new ConcurrentCompilationError()); + } + this.running = true; + + if (this.validateDependencies(handler)) { + const watchings = this._runGraph( + (compiler, idx, callback, isBlocked, setChanged, setInvalid) => { + const watching = compiler.watch( + Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions, + callback + ); + if (watching) { + watching._onInvalid = setInvalid; + watching._onChange = setChanged; + watching._isBlocked = isBlocked; + } + return watching; + }, + (compiler, watching, callback) => { + if (compiler.watching !== watching) return; + if (!watching.running) watching.invalidate(); + }, + handler + ); + return new MultiWatching(watchings, this); + } + + return new MultiWatching([], this); + } + + /** + * @param {Callback} callback signals when the call finishes + * @returns {void} + */ + run(callback) { + if (this.running) { + return callback(new ConcurrentCompilationError()); + } + this.running = true; + + if (this.validateDependencies(callback)) { + this._runGraph( + () => {}, + (compiler, setupResult, callback) => compiler.run(callback), + (err, stats) => { + this.running = false; + + if (callback !== undefined) { + return callback(err, stats); + } + } + ); + } + } + + purgeInputFileSystem() { + for (const compiler of this.compilers) { + if (compiler.inputFileSystem && compiler.inputFileSystem.purge) { + compiler.inputFileSystem.purge(); + } + } + } + + /** + * @param {Callback} callback signals when the compiler closes + * @returns {void} + */ + close(callback) { + asyncLib.each( + this.compilers, + (compiler, callback) => { + compiler.close(callback); + }, + error => { + callback(error); + } + ); + } +}; diff --git a/webpack-lib/lib/MultiStats.js b/webpack-lib/lib/MultiStats.js new file mode 100644 index 00000000000..bf4771a5fef --- /dev/null +++ b/webpack-lib/lib/MultiStats.js @@ -0,0 +1,208 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const identifierUtils = require("./util/identifier"); + +/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ +/** @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 */ + +/** + * @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; +}; + +/** @typedef {{ version: boolean, hash: boolean, errorsCount: boolean, warningsCount: boolean, errors: boolean, warnings: boolean, children: NormalizedStatsOptions[] }} ChildOptions */ + +class MultiStats { + /** + * @param {Stats[]} stats the child stats + */ + constructor(stats) { + this.stats = stats; + } + + get hash() { + return this.stats.map(stat => stat.hash).join(""); + } + + /** + * @returns {boolean} true if a child compilation encountered an error + */ + hasErrors() { + return this.stats.some(stat => stat.hasErrors()); + } + + /** + * @returns {boolean} true if a child compilation had a warning + */ + hasWarnings() { + return this.stats.some(stat => stat.hasWarnings()); + } + + /** + * @param {string | boolean | StatsOptions | undefined} 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; + 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 + }; + } + + /** + * @param {(string | boolean | StatsOptions)=} 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; + }); + if (childOptions.version) { + obj.version = obj.children[0].version; + } + if (childOptions.hash) { + obj.hash = obj.children.map(j => j.hash).join(""); + } + /** + * @param {StatsCompilation} j stats error + * @param {StatsError} obj Stats error + * @returns {TODO} 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); + } + } + return obj; + } + + /** + * @param {(string | boolean | StatsOptions)=} options stats options + * @returns {string} string output + */ + toString(options) { + 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"); + } +} + +module.exports = MultiStats; diff --git a/webpack-lib/lib/MultiWatching.js b/webpack-lib/lib/MultiWatching.js new file mode 100644 index 00000000000..cfe95f17b28 --- /dev/null +++ b/webpack-lib/lib/MultiWatching.js @@ -0,0 +1,81 @@ +/* + 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 */ + +/** + * @template T + * @callback Callback + * @param {(Error | null)=} err + * @param {T=} result + */ + +class MultiWatching { + /** + * @param {Watching[]} watchings child compilers' watchers + * @param {MultiCompiler} compiler the compiler + */ + constructor(watchings, compiler) { + this.watchings = watchings; + this.compiler = compiler; + } + + /** + * @param {Callback=} callback signals when the build has completed again + * @returns {void} + */ + invalidate(callback) { + if (callback) { + asyncLib.each( + this.watchings, + (watching, callback) => watching.invalidate(callback), + callback + ); + } 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.resume(); + } + } + + /** + * @param {Callback} callback signals when the watcher is closed + * @returns {void} + */ + close(callback) { + asyncLib.each( + this.watchings, + (watching, finishedCallback) => { + watching.close(finishedCallback); + }, + err => { + this.compiler.hooks.watchClose.call(); + if (typeof callback === "function") { + this.compiler.running = false; + callback(err); + } + } + ); + } +} + +module.exports = MultiWatching; diff --git a/webpack-lib/lib/NoEmitOnErrorsPlugin.js b/webpack-lib/lib/NoEmitOnErrorsPlugin.js new file mode 100644 index 00000000000..a84eb56c753 --- /dev/null +++ b/webpack-lib/lib/NoEmitOnErrorsPlugin.js @@ -0,0 +1,28 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("./Compiler")} Compiler */ + +class NoEmitOnErrorsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.shouldEmit.tap("NoEmitOnErrorsPlugin", compilation => { + if (compilation.getStats().hasErrors()) return false; + }); + compiler.hooks.compilation.tap("NoEmitOnErrorsPlugin", compilation => { + compilation.hooks.shouldRecord.tap("NoEmitOnErrorsPlugin", () => { + if (compilation.getStats().hasErrors()) return false; + }); + }); + } +} + +module.exports = NoEmitOnErrorsPlugin; diff --git a/webpack-lib/lib/NoModeWarning.js b/webpack-lib/lib/NoModeWarning.js new file mode 100644 index 00000000000..fdd3fadf9c6 --- /dev/null +++ b/webpack-lib/lib/NoModeWarning.js @@ -0,0 +1,22 @@ +/* + 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() { + super(); + + 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/"; + } +}; diff --git a/webpack-lib/lib/NodeStuffInWebError.js b/webpack-lib/lib/NodeStuffInWebError.js new file mode 100644 index 00000000000..02b048ec4fd --- /dev/null +++ b/webpack-lib/lib/NodeStuffInWebError.js @@ -0,0 +1,34 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ + +class NodeStuffInWebError extends WebpackError { + /** + * @param {DependencyLocation} loc loc + * @param {string} expression expression + * @param {string} description description + */ + constructor(loc, expression, description) { + super( + `${JSON.stringify( + expression + )} has been used, it will be undefined in next major version. +${description}` + ); + + this.name = "NodeStuffInWebError"; + this.loc = loc; + } +} + +makeSerializable(NodeStuffInWebError, "webpack/lib/NodeStuffInWebError"); + +module.exports = NodeStuffInWebError; diff --git a/webpack-lib/lib/NodeStuffPlugin.js b/webpack-lib/lib/NodeStuffPlugin.js new file mode 100644 index 00000000000..87a5cd61405 --- /dev/null +++ b/webpack-lib/lib/NodeStuffPlugin.js @@ -0,0 +1,275 @@ +/* + 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 +} = require("./ModuleTypeConstants"); +const NodeStuffInWebError = require("./NodeStuffInWebError"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const CachedConstDependency = require("./dependencies/CachedConstDependency"); +const ConstDependency = require("./dependencies/ConstDependency"); +const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency"); +const { + evaluateToString, + expressionIsUnsupported +} = require("./javascript/JavascriptParserHelpers"); +const { relative } = require("./util/fs"); +const { parseResource } = require("./util/identifier"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Dependency")} Dependency */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./NormalModule")} NormalModule */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +const PLUGIN_NAME = "NodeStuffPlugin"; + +class NodeStuffPlugin { + /** + * @param {NodeOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + ExternalModuleDependency, + new ExternalModuleDependency.Template() + ); + + /** + * @param {JavascriptParser} parser the parser + * @param {JavascriptParserOptions} parserOptions options + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.node === false) return; + + let localOptions = options; + if (parserOptions.node) { + localOptions = { ...localOptions, ...parserOptions.node }; + } + + if (localOptions.global !== false) { + const withWarning = localOptions.global === "warn"; + parser.hooks.expression.for("global").tap(PLUGIN_NAME, expr => { + const dep = new ConstDependency( + RuntimeGlobals.global, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.global] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + + // TODO webpack 6 remove + 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 = new ConstDependency( + RuntimeGlobals.global, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.global] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return false; + }); + } + + /** + * @param {string} expressionName expression name + * @param {(module: NormalModule) => string} fn function + * @param {string=} warning warning + * @returns {void} + */ + const setModuleConstant = (expressionName, fn, warning) => { + parser.hooks.expression + .for(expressionName) + .tap(PLUGIN_NAME, expr => { + const dep = new CachedConstDependency( + JSON.stringify(fn(parser.state.module)), + /** @type {Range} */ (expr.range), + expressionName + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + + // TODO webpack 6 remove + if (warning) { + parser.state.module.addWarning( + new NodeStuffInWebError(dep.loc, expressionName, warning) + ); + } + + return true; + }); + }; + + /** + * @param {string} expressionName expression name + * @param {(value: string) => string} fn function + * @returns {void} + */ + const setUrlModuleConstant = (expressionName, fn) => { + parser.hooks.expression + .for(expressionName) + .tap(PLUGIN_NAME, expr => { + const dep = new ExternalModuleDependency( + "url", + [ + { + name: "fileURLToPath", + value: "__webpack_fileURLToPath__" + } + ], + undefined, + fn("__webpack_fileURLToPath__"), + /** @type {Range} */ (expr.range), + expressionName + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + + return true; + }); + }; + + /** + * @param {string} expressionName expression name + * @param {string} value value + * @param {string=} warning warning + * @returns {void} + */ + const setConstant = (expressionName, value, warning) => + setModuleConstant(expressionName, () => value, warning); + + const context = compiler.context; + if (localOptions.__filename) { + switch (localOptions.__filename) { + case "mock": + setConstant("__filename", "/index.js"); + break; + case "warn-mock": + setConstant( + "__filename", + "/index.js", + "__filename is a Node.js feature and isn't available in browsers." + ); + break; + case "node-module": + setUrlModuleConstant( + "__filename", + functionName => `${functionName}(import.meta.url)` + ); + break; + case true: + setModuleConstant("__filename", module => + relative( + /** @type {InputFileSystem} */ (compiler.inputFileSystem), + context, + module.resource + ) + ); + break; + } + + 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); + }); + } + if (localOptions.__dirname) { + switch (localOptions.__dirname) { + case "mock": + setConstant("__dirname", "/"); + break; + case "warn-mock": + setConstant( + "__dirname", + "/", + "__dirname is a Node.js feature and isn't available in browsers." + ); + break; + case "node-module": + setUrlModuleConstant( + "__dirname", + functionName => + `${functionName}(import.meta.url + "/..").slice(0, -1)` + ); + break; + case true: + setModuleConstant("__dirname", module => + relative( + /** @type {InputFileSystem} */ (compiler.inputFileSystem), + context, + /** @type {string} */ (module.context) + ) + ); + break; + } + + parser.hooks.evaluateIdentifier + .for("__dirname") + .tap(PLUGIN_NAME, expr => { + if (!parser.state.module) return; + return evaluateToString( + /** @type {string} */ (parser.state.module.context) + )(expr); + }); + } + parser.hooks.expression + .for("require.extensions") + .tap( + PLUGIN_NAME, + expressionIsUnsupported( + parser, + "require.extensions is not supported by webpack. Use a loader instead." + ) + ); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + } + ); + } +} + +module.exports = NodeStuffPlugin; diff --git a/webpack-lib/lib/NormalModule.js b/webpack-lib/lib/NormalModule.js new file mode 100644 index 00000000000..2bf3606a805 --- /dev/null +++ b/webpack-lib/lib/NormalModule.js @@ -0,0 +1,1654 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const parseJson = require("json-parse-even-better-errors"); +const { getContext, runLoaders } = require("loader-runner"); +const querystring = require("querystring"); +const { HookMap, SyncHook, AsyncSeriesBailHook } = require("tapable"); +const { + CachedSource, + OriginalSource, + RawSource, + SourceMapSource +} = require("webpack-sources"); +const Compilation = require("./Compilation"); +const HookWebpackError = require("./HookWebpackError"); +const Module = require("./Module"); +const ModuleBuildError = require("./ModuleBuildError"); +const ModuleError = require("./ModuleError"); +const ModuleGraphConnection = require("./ModuleGraphConnection"); +const ModuleParseError = require("./ModuleParseError"); +const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants"); +const ModuleWarning = require("./ModuleWarning"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const UnhandledSchemeError = require("./UnhandledSchemeError"); +const WebpackError = require("./WebpackError"); +const formatLocation = require("./formatLocation"); +const LazySet = require("./util/LazySet"); +const { isSubset } = require("./util/SetHelpers"); +const { getScheme } = require("./util/URLAbsoluteSpecifier"); +const { + compareLocations, + concatComparators, + compareSelect, + keepOriginalOrder +} = require("./util/comparators"); +const createHash = require("./util/createHash"); +const { createFakeHook } = require("./util/deprecation"); +const { join } = require("./util/fs"); +const { + contextify, + absolutify, + makePathsRelative +} = require("./util/identifier"); +const makeSerializable = require("./util/makeSerializable"); +const memoize = require("./util/memoize"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").Mode} Mode */ +/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Generator")} Generator */ +/** @typedef {import("./Module").BuildInfo} BuildInfo */ +/** @typedef {import("./Module").BuildMeta} BuildMeta */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("./Module").KnownBuildInfo} KnownBuildInfo */ +/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module").UnsafeCacheData} UnsafeCacheData */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ +/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("./ModuleTypeConstants").JavaScriptModuleTypes} JavaScriptModuleTypes */ +/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ +/** @typedef {import("./NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ +/** @typedef {import("./Parser")} Parser */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolveContext} ResolveContext */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./logging/Logger").Logger} WebpackLogger */ +/** @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/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./util/createHash").Algorithm} Algorithm */ +/** + * @template T + * @typedef {import("./util/deprecation").FakeHook} FakeHook + */ + +/** @typedef {{[k: string]: any}} ParserOptions */ +/** @typedef {{[k: string]: any}} GeneratorOptions */ + +/** @typedef {UnsafeCacheData & { parser: undefined | Parser, parserOptions: undefined | ParserOptions, generator: undefined | Generator, generatorOptions: undefined | GeneratorOptions }} NormalModuleUnsafeCacheData */ + +/** + * @template T + * @typedef {import("../declarations/LoaderContext").LoaderContext} LoaderContext + */ + +/** + * @template T + * @typedef {import("../declarations/LoaderContext").NormalModuleLoaderContext} NormalModuleLoaderContext + */ + +/** + * @typedef {object} SourceMap + * @property {number} version + * @property {string[]} sources + * @property {string} mappings + * @property {string=} file + * @property {string=} sourceRoot + * @property {string[]=} sourcesContent + * @property {string[]=} names + * @property {string=} debugId + */ + +const getInvalidDependenciesModuleWarning = memoize(() => + require("./InvalidDependenciesModuleWarning") +); +const getValidate = memoize(() => require("schema-utils").validate); + +const ABSOLUTE_PATH_REGEX = /^([a-zA-Z]:\\|\\\\|\/)/; + +/** + * @typedef {object} LoaderItem + * @property {string} loader + * @property {any} options + * @property {string?} ident + * @property {string?} type + */ + +/** + * @param {string} context absolute context path + * @param {string} source a source path + * @param {object=} 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 + )}`; +}; + +/** + * @param {string} context absolute context path + * @param {SourceMap} sourceMap a source map + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {SourceMap} new source map + */ +const contextifySourceMap = (context, sourceMap, associatedObjectForCache) => { + if (!Array.isArray(sourceMap.sources)) return sourceMap; + const { sourceRoot } = sourceMap; + /** @type {function(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("utf-8"); + } + return input; +}; + +/** + * @param {string | Buffer} input the input + * @returns {Buffer} the converted buffer + */ +const asBuffer = input => { + if (!Buffer.isBuffer(input)) { + return Buffer.from(input, "utf-8"); + } + return input; +}; + +class NonErrorEmittedError extends WebpackError { + /** + * @param {any} error value which is not an instance of Error + */ + constructor(error) { + super(); + + this.name = "NonErrorEmittedError"; + this.message = `(Emitted value instead of an instance of Error) ${error}`; + } +} + +makeSerializable( + NonErrorEmittedError, + "webpack/lib/NormalModule", + "NonErrorEmittedError" +); + +/** + * @typedef {object} NormalModuleCompilationHooks + * @property {SyncHook<[LoaderContext, NormalModule]>} loader + * @property {SyncHook<[LoaderItem[], NormalModule, LoaderContext]>} beforeLoaders + * @property {SyncHook<[NormalModule]>} beforeParse + * @property {SyncHook<[NormalModule]>} beforeSnapshot + * @property {HookMap>>} readResourceForScheme + * @property {HookMap], string | Buffer | null>>} readResource + * @property {AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>} needBuild + */ + +/** + * @typedef {object} NormalModuleCreateData + * @property {string=} layer an optional layer in which the module is + * @property {JavaScriptModuleTypes | ""} 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 {Record=} resourceResolveData resource resolve data + * @property {string} context context directory for resolving + * @property {string=} matchResource path + query of the matched resource (virtual) + * @property {Parser} parser the parser used + * @property {ParserOptions=} parserOptions the options of the parser used + * @property {Generator} generator the generator used + * @property {GeneratorOptions=} generatorOptions the options of the generator used + * @property {ResolveOptions=} resolveOptions options used for resolving requests from this module + */ + +/** @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"]) + ), + 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, + generatorOptions, + resolveOptions + }) { + super(type, context || getContext(resource), layer); + + // Info from Factory + /** @type {string} */ + this.request = request; + /** @type {string} */ + this.userRequest = userRequest; + /** @type {string} */ + this.rawRequest = rawRequest; + /** @type {boolean} */ + this.binary = /^(asset|webassembly)\b/.test(type); + /** @type {undefined | Parser} */ + this.parser = parser; + /** @type {undefined | ParserOptions} */ + this.parserOptions = parserOptions; + /** @type {undefined | Generator} */ + this.generator = generator; + /** @type {undefined | GeneratorOptions} */ + this.generatorOptions = generatorOptions; + /** @type {string} */ + this.resource = resource; + this.resourceResolveData = resourceResolveData; + /** @type {string | undefined} */ + this.matchResource = matchResource; + /** @type {LoaderItem[]} */ + this.loaders = loaders; + if (resolveOptions !== undefined) { + // already declared in super class + this.resolveOptions = resolveOptions; + } + + // Info from Build + /** @type {WebpackError | null} */ + this.error = null; + /** + * @private + * @type {Source | null} + */ + this._source = null; + /** + * @private + * @type {Map | undefined} + */ + this._sourceSizes = undefined; + /** + * @private + * @type {undefined | SourceTypes} + */ + this._sourceTypes = undefined; + + // Cache + this._lastSuccessfulBuildMeta = {}; + this._forceBuild = true; + this._isEvaluatingSideEffects = false; + /** @type {WeakSet | undefined} */ + this._addedSideEffectsBailout = undefined; + /** @type {Map} */ + this._codeGeneratorData = new Map(); + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + 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}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return /** @type {string} */ (requestShortener.shorten(this.userRequest)); + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + let ident = contextify( + options.context, + this.userRequest, + options.associatedObjectForCache + ); + if (this.layer) ident = `(${this.layer})/${ident}`; + return ident; + } + + /** + * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) + */ + nameForCondition() { + const resource = this.matchResource || this.resource; + const idx = resource.indexOf("?"); + 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) { + 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; + } + + /** + * 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; + } + + /** + * 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 = + /** @type {NormalModuleUnsafeCacheData} */ + (super.getUnsafeCacheData()); + data.parserOptions = this.parserOptions; + data.generatorOptions = this.generatorOptions; + return data; + } + + /** + * restore unsafe cache data + * @param {NormalModuleUnsafeCacheData} unsafeCacheData data from getUnsafeCacheData + * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching + */ + restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) { + this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory); + } + + /** + * restore unsafe cache data + * @param {object} 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 | SourceMap)=} sourceMap an optional source map + * @param {object=} 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 {SourceMap} */ (sourceMap), + associatedObjectForCache + ) + ); + } + } + + return new RawSource(content); + } + + /** + * @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").NormalModuleLoaderContext} loader context + */ + _createLoaderContext(resolver, options, compilation, fs, hooks) { + const { requestShortener } = compilation.runtimeTemplate; + const getCurrentLoaderName = () => { + const currentLoader = this.getCurrentLoader(loaderContext); + if (!currentLoader) return "(not in loader scope)"; + return requestShortener.shorten(currentLoader.loader); + }; + /** + * @returns {ResolveContext} resolve context + */ + const getResolveContext = () => ({ + fileDependencies: { + add: d => /** @type {TODO} */ (loaderContext).addDependency(d) + }, + contextDependencies: { + add: d => /** @type {TODO} */ (loaderContext).addContextDependency(d) + }, + missingDependencies: { + add: d => /** @type {TODO} */ (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 {(string | typeof import("./util/Hash"))=} type type + * @returns {Hash} hash + */ + createHash: type => + createHash( + type || + /** @type {Algorithm} */ + (compilation.outputOptions.hashFunction) + ) + }; + /** @type {import("../declarations/LoaderContext").NormalModuleLoaderContext} */ + const loaderContext = { + version: 2, + getOptions: schema => { + const loader = this.getCurrentLoader(loaderContext); + + let { options } = /** @type {LoaderItem} */ (loader); + + if (typeof options === "string") { + if (options.startsWith("{") && options.endsWith("}")) { + try { + options = parseJson(options); + } catch (err) { + throw new Error( + `Cannot parse string options: ${/** @type {Error} */ (err).message}` + ); + } + } else { + options = querystring.parse(options, "&", "=", { + maxKeys: 0 + }); + } + } + + if (options === null || options === undefined) { + options = {}; + } + + if (schema) { + let name = "Loader"; + let baseDataPath = "options"; + let match; + if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) { + [, name, baseDataPath] = match; + } + getValidate()(schema, options, { + name, + baseDataPath + }); + } + + return options; + }, + emitWarning: warning => { + if (!(warning instanceof Error)) { + warning = new NonErrorEmittedError(warning); + } + this.addWarning( + new ModuleWarning(warning, { + from: getCurrentLoaderName() + }) + ); + }, + emitError: error => { + if (!(error instanceof Error)) { + error = new NonErrorEmittedError(error); + } + this.addError( + new ModuleError(error, { + from: getCurrentLoaderName() + }) + ); + }, + getLogger: name => { + const currentLoader = this.getCurrentLoader(loaderContext); + return compilation.getLogger(() => + [currentLoader && currentLoader.loader, name, this.identifier()] + .filter(Boolean) + .join("|") + ); + }, + resolve(context, request, callback) { + resolver.resolve({}, context, request, getResolveContext(), callback); + }, + getResolve(options) { + const child = options ? resolver.withOptions(options) : resolver; + return (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); + } + ); + }); + } + }; + }, + emitFile: (name, content, sourceMap, assetInfo) => { + const buildInfo = /** @type {BuildInfo} */ (this.buildInfo); + + if (!buildInfo.assets) { + buildInfo.assets = Object.create(null); + buildInfo.assetsInfo = new Map(); + } + + const assets = + /** @type {NonNullable} */ + (buildInfo.assets); + const assetsInfo = + /** @type {NonNullable} */ + (buildInfo.assetsInfo); + + assets[name] = this.createSourceForAsset( + /** @type {string} */ (options.context), + name, + content, + sourceMap, + compilation.compiler.root + ); + assetsInfo.set(name, assetInfo); + }, + addBuildDependency: dep => { + const buildInfo = /** @type {BuildInfo} */ (this.buildInfo); + + if (buildInfo.buildDependencies === undefined) { + buildInfo.buildDependencies = new LazySet(); + } + buildInfo.buildDependencies.add(dep); + }, + utils, + rootContext: /** @type {string} */ (options.context), + webpack: true, + sourceMap: Boolean(this.useSourceMap), + mode: options.mode || "production", + hashFunction: /** @type {TODO} */ (options.output.hashFunction), + hashDigest: /** @type {string} */ (options.output.hashDigest), + hashDigestLength: /** @type {number} */ (options.output.hashDigestLength), + hashSalt: /** @type {string} */ (options.output.hashSalt), + _module: this, + _compilation: compilation, + _compiler: compilation.compiler, + fs + }; + + Object.assign(loaderContext, options.loader); + + hooks.loader.call(/** @type {LoaderContext} */ (loaderContext), this); + + return loaderContext; + } + + // TODO remove `loaderContext` in webpack@6 + /** + * @param {TODO} loaderContext loader context + * @param {number} index index + * @returns {LoaderItem | null} loader + */ + getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) { + if ( + this.loaders && + this.loaders.length && + index < this.loaders.length && + index >= 0 && + this.loaders[index] + ) { + return this.loaders[index]; + } + return null; + } + + /** + * @param {string} context the compilation context + * @param {string | Buffer} content the content + * @param {(string | SourceMapSource | null)=} sourceMap an optional source map + * @param {object=} 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(content); + } + + // from here on we assume we have an identifier + const identifier = this.identifier(); + + if (this.useSourceMap && sourceMap) { + return new SourceMapSource( + content, + contextifySourceUrl(context, identifier, associatedObjectForCache), + contextifySourceMap( + context, + /** @type {TODO} */ (sourceMap), + associatedObjectForCache + ) + ); + } + + if (this.useSourceMap || this.useSimpleSourceMap) { + return new OriginalSource( + content, + contextifySourceUrl(context, identifier, associatedObjectForCache) + ); + } + + return new RawSource(content); + } + + /** + * @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 {function((WebpackError | null)=): void} callback callback function + * @returns {void} + */ + _doBuild(options, compilation, resolver, fs, hooks, callback) { + const loaderContext = this._createLoaderContext( + resolver, + options, + compilation, + fs, + hooks + ); + + /** @typedef {[string | Buffer, string | SourceMapSource, Record]} Result */ + + /** + * @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 = /** @type {Result} */ (_result); + 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( + /** @type {string} */ (options.context), + isBinaryModule ? asBuffer(source) : asString(source), + sourceMap, + compilation.compiler.root + ); + if (this._sourceSizes !== undefined) this._sourceSizes.clear(); + this._ast = + typeof extraInfo === "object" && + extraInfo !== null && + extraInfo.webpackAST !== undefined + ? extraInfo.webpackAST + : null; + return callback(); + }; + + const buildInfo = /** @type {BuildInfo} */ (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 {LoaderContext} */ (loaderContext) + ); + } catch (err) { + processResult(/** @type {Error} */ (err)); + return; + } + + if (this.loaders.length > 0) { + /** @type {BuildInfo} */ + (this.buildInfo).buildDependencies = new LazySet(); + } + + runLoaders( + { + resource: this.resource, + loaders: this.loaders, + context: loaderContext, + /** + * @param {LoaderContext} loaderContext the loader context + * @param {string} resourcePath the resource Path + * @param {(err: Error | null, result?: string | Buffer) => void} callback callback + */ + processResource: (loaderContext, resourcePath, callback) => { + const resource = loaderContext.resource; + const scheme = getScheme(resource); + hooks.readResource + .for(scheme) + .callAsync(loaderContext, (err, result) => { + if (err) return callback(err); + if (typeof result !== "string" && !result) { + return callback( + new UnhandledSchemeError( + /** @type {string} */ + (scheme), + resource + ) + ); + } + return callback(null, result); + }); + } + }, + (err, result) => { + // Cleanup loaderContext to avoid leaking memory in ICs + loaderContext._compilation = + loaderContext._compiler = + loaderContext._module = + // eslint-disable-next-line no-warning-comments + // @ts-ignore + loaderContext.fs = + undefined; + + if (!result) { + /** @type {BuildInfo} */ + (this.buildInfo).cacheable = false; + return processResult( + err || new Error("No result from loader-runner processing"), + null + ); + } + + const buildInfo = /** @type {BuildInfo} */ (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); + } + buildInfo.cacheable = buildInfo.cacheable && result.cacheable; + processResult(err, result.result); + } + ); + } + + /** + * @param {WebpackError} error the error + * @returns {void} + */ + markModuleAsErrored(error) { + // Restore build meta from successful build to keep importing state + this.buildMeta = { ...this._lastSuccessfulBuildMeta }; + this.error = error; + this.addError(error); + } + + /** + * @param {TODO} 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.startsWith(rule); + } + + if (typeof rule === "function") { + return rule(content); + } + // we assume rule is a regexp + return rule.test(content); + } + + /** + * @param {TODO} 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. + if (!noParseRule) { + return false; + } + + // we only have one rule to check + if (!Array.isArray(noParseRule)) { + // returns "true" if the module is !not! to be parsed + return this.applyNoParseRule(noParseRule, request); + } + + for (let i = 0; i < noParseRule.length; i++) { + const rule = noParseRule[i]; + // early exit on first truthy match + // this module is !not! to be parsed + if (this.applyNoParseRule(rule, request)) { + return true; + } + } + // no match found, so this module !should! be parsed + return false; + } + + /** + * @param {Compilation} compilation compilation + * @private + */ + _initBuildHash(compilation) { + const hash = createHash( + /** @type {Algorithm} */ + (compilation.outputOptions.hashFunction) + ); + if (this._source) { + hash.update("source"); + this._source.updateHash(hash); + } + hash.update("meta"); + hash.update(JSON.stringify(this.buildMeta)); + /** @type {BuildInfo} */ + (this.buildInfo).hash = /** @type {string} */ (hash.digest("hex")); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this._forceBuild = false; + this._source = null; + if (this._sourceSizes !== undefined) this._sourceSizes.clear(); + this._sourceTypes = undefined; + this._ast = null; + this.error = null; + this.clearWarningsAndErrors(); + this.clearDependenciesAndBlocks(); + this.buildMeta = {}; + this.buildInfo = { + cacheable: false, + parsed: true, + fileDependencies: undefined, + contextDependencies: undefined, + missingDependencies: undefined, + buildDependencies: undefined, + valueDependencies: undefined, + hash: undefined, + assets: undefined, + assetsInfo: undefined + }; + + 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); + this._initBuildHash(compilation); + return callback(); + } + + /** + * @param {Error} e error + * @returns {void} + */ + const handleParseError = e => { + const source = /** @type {Source} */ (this._source).source(); + const loaders = this.loaders.map(item => + contextify( + /** @type {string} */ (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 = () => { + this.dependencies.sort( + concatComparators( + compareSelect(a => a.loc, compareLocations), + keepOriginalOrder(this.dependencies) + ) + ); + this._initBuildHash(compilation); + this._lastSuccessfulBuildMeta = + /** @type {BuildMeta} */ + (this.buildMeta); + return handleBuildDone(); + }; + + const handleBuildDone = () => { + try { + hooks.beforeSnapshot.call(this); + } catch (err) { + this.markModuleAsErrored(/** @type {WebpackError} */ (err)); + return callback(); + } + + const snapshotOptions = compilation.options.snapshot.module; + const { cacheable } = /** @type {BuildInfo} */ (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 {LazySet} deps deps + */ + const checkDependencies = deps => { + for (const dep of deps) { + if (!ABSOLUTE_PATH_REGEX.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_REGEX.test(absolute)) { + (depWithoutGlob !== dep + ? /** @type {NonNullable} */ + ( + /** @type {BuildInfo} */ (this.buildInfo) + .contextDependencies + ) + : deps + ).add(absolute); + } + } catch (_err) { + // ignore + } + } + } + }; + const buildInfo = /** @type {BuildInfo} */ (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) { + this.markModuleAsErrored(err); + return; + } + buildInfo.fileDependencies = undefined; + buildInfo.contextDependencies = undefined; + buildInfo.missingDependencies = undefined; + buildInfo.snapshot = snapshot; + return callback(); + } + ); + }; + + try { + hooks.beforeParse.call(this); + } catch (err) { + this.markModuleAsErrored(/** @type {WebpackError} */ (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 {BuildInfo} */ + (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 + }); + } catch (parseErr) { + handleParseError(/** @type {Error} */ (parseErr)); + return; + } + handleParseResult(); + }); + } + + /** + * @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); + } + + /** + * @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; + } + if (this.buildMeta !== undefined && this.buildMeta.sideEffectFree) { + if (this._isEvaluatingSideEffects) + return ModuleGraphConnection.CIRCULAR_CONNECTION; + this._isEvaluatingSideEffects = true; + /** @type {ConnectionState} */ + let current = false; + for (const dep of this.dependencies) { + const state = dep.getModuleEvaluationSideEffectsState(moduleGraph); + if (state === true) { + if ( + this._addedSideEffectsBailout === undefined + ? ((this._addedSideEffectsBailout = new WeakSet()), true) + : !this._addedSideEffectsBailout.has(moduleGraph) + ) { + this._addedSideEffectsBailout.add(moduleGraph); + moduleGraph + .getOptimizationBailout(this) + .push( + () => + `Dependency (${ + dep.type + }) with side effects at ${formatLocation(dep.loc)}` + ); + } + this._isEvaluatingSideEffects = false; + return true; + } else if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) { + current = ModuleGraphConnection.addConnectionStates(current, state); + } + } + this._isEvaluatingSideEffects = false; + // When caching is implemented here, make sure to not cache when + // at least one circular connection was in the loop above + return current; + } + return true; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + if (this._sourceTypes === undefined) { + this._sourceTypes = /** @type {Generator} */ (this.generator).getTypes( + this + ); + } + return this._sourceTypes; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime, + concatenationScope, + codeGenerationResults, + sourceTypes + }) { + /** @type {Set} */ + const runtimeRequirements = new Set(); + + const { parsed } = /** @type {BuildInfo} */ (this.buildInfo); + + if (!parsed) { + runtimeRequirements.add(RuntimeGlobals.module); + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.thisAsExports); + } + + /** @type {function(): Map} */ + const getData = () => this._codeGeneratorData; + + const sources = new Map(); + for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) { + const source = this.error + ? new RawSource( + `throw new Error(${JSON.stringify(this.error.message)});` + ) + : /** @type {Generator} */ (this.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 + }; + return resultEntry; + } + + /** + * @returns {Source | null} the original source for the module before webpack transformation + */ + originalSource() { + return this._source; + } + + /** + * @returns {void} + */ + invalidateBuild() { + this._forceBuild = true; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} 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 {BuildInfo} */ (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)); + }); + }); + } + + /** + * @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; + } + + /** + * @param {LazySet} fileDependencies set where file dependencies are added to + * @param {LazySet} contextDependencies set where context dependencies are added to + * @param {LazySet} missingDependencies set where missing dependencies are added to + * @param {LazySet} buildDependencies set where build dependencies are added to + */ + addCacheDependencies( + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + ) { + const { snapshot, buildDependencies: buildDeps } = + /** @type {BuildInfo} */ (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 {BuildInfo} */ (this.buildInfo); + if (fileDeps !== undefined) fileDependencies.addAll(fileDeps); + if (contextDeps !== undefined) contextDependencies.addAll(contextDeps); + if (missingDeps !== undefined) missingDependencies.addAll(missingDeps); + } + if (buildDeps !== undefined) { + buildDependencies.addAll(buildDeps); + } + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + hash.update(/** @type {BuildInfo} */ (this.buildInfo).hash); + /** @type {Generator} */ + (this.generator).updateHash(hash, { + module: this, + ...context + }); + super.updateHash(hash, 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); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {TODO} Module + */ + static deserialize(context) { + const obj = new NormalModule({ + // 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) + }); + obj.deserialize(context); + return obj; + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this._source = read(); + this.error = read(); + this._lastSuccessfulBuildMeta = read(); + this._forceBuild = read(); + this._codeGeneratorData = read(); + super.deserialize(context); + } +} + +makeSerializable(NormalModule, "webpack/lib/NormalModule"); + +module.exports = NormalModule; diff --git a/webpack-lib/lib/NormalModuleFactory.js b/webpack-lib/lib/NormalModuleFactory.js new file mode 100644 index 00000000000..546bd593ac4 --- /dev/null +++ b/webpack-lib/lib/NormalModuleFactory.js @@ -0,0 +1,1326 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { getContext } = require("loader-runner"); +const asyncLib = require("neo-async"); +const { + AsyncSeriesBailHook, + SyncWaterfallHook, + SyncBailHook, + SyncHook, + HookMap +} = 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 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 { + parseResource, + parseResourceWithoutFragment +} = require("./util/identifier"); + +/** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("./Generator")} Generator */ +/** @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").ResolveContext} ResolveContext */ +/** @typedef {import("./ResolverFactory").ResolveRequest} ResolveRequest */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +/** @typedef {Pick} ModuleSettings */ +/** @typedef {Partial} CreateData */ + +/** + * @typedef {object} ResolveData + * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo + * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions + * @property {string} context + * @property {string} request + * @property {Record | undefined} assertions + * @property {ModuleDependency[]} dependencies + * @property {string} dependencyType + * @property {CreateData} createData + * @property {LazySet} fileDependencies + * @property {LazySet} missingDependencies + * @property {LazySet} contextDependencies + * @property {Module=} ignoredModule + * @property {boolean} cacheable allow to use the unsafe cache + */ + +/** + * @typedef {object} ResourceData + * @property {string} resource + * @property {string=} path + * @property {string=} query + * @property {string=} fragment + * @property {string=} context + */ + +/** @typedef {ResourceData & { data: Record }} ResourceDataWithData */ + +/** + * @typedef {object} ParsedLoaderRequest + * @property {string} loader loader + * @property {string|undefined} options options + */ + +/** + * @template T + * @callback Callback + * @param {(Error | null)=} err + * @param {T=} stats + * @returns {void} + */ + +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 = /^[^.]/; + +/** + * @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}`; + } + 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}?${JSON.stringify(data.options)}`; +}; + +/** + * @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; +}; + +/** + * @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); + } +}; + +/** + * @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("/"); + 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 +/** + * @param {string} name name + * @param {TODO} hook hook + * @returns {string} result + */ +const deprecationChangedHookMessage = (name, hook) => { + const names = hook.taps + .map( + /** + * @param {TODO} tapped tapped + * @returns {string} name + */ + tapped => tapped.name + ) + .join(", "); + + return ( + `NormalModuleFactory.${name} (${names}) is no longer a waterfall hook, but a bailing hook instead. ` + + "Do not return the passed object, but modify it instead. " + + "Returning false will ignore the request and results in no module created." + ); +}; + +const ruleSetCompiler = new RuleSetCompiler([ + new BasicMatcherRulePlugin("test", "resource"), + new BasicMatcherRulePlugin("scheme"), + new BasicMatcherRulePlugin("mimetype"), + new BasicMatcherRulePlugin("dependency"), + new BasicMatcherRulePlugin("include", "resource"), + new BasicMatcherRulePlugin("exclude", "resource", true), + new BasicMatcherRulePlugin("resource"), + new BasicMatcherRulePlugin("resourceQuery"), + new BasicMatcherRulePlugin("resourceFragment"), + new BasicMatcherRulePlugin("realResource"), + new BasicMatcherRulePlugin("issuer"), + new BasicMatcherRulePlugin("compiler"), + new BasicMatcherRulePlugin("issuerLayer"), + new ObjectMatcherRulePlugin("assert", "assertions", value => { + if (value) { + return /** @type {any} */ (value)._isLegacyAssert !== undefined; + } + + return false; + }), + new ObjectMatcherRulePlugin("with", "assertions", value => { + if (value) { + return !(/** @type {any} */ (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 UseEffectRulePlugin() +]); + +class NormalModuleFactory extends ModuleFactory { + /** + * @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 {object} param.associatedObjectForCache an object to which the cache will be attached + * @param {boolean=} param.layers enable layers + */ + constructor({ + context, + fs, + resolverFactory, + options, + associatedObjectForCache, + layers = false + }) { + super(); + 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<[ResolveData["createData"], ResolveData], Module | void>} */ + createModule: new AsyncSeriesBailHook(["createData", "resolveData"]), + /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData]>} */ + module: new SyncWaterfallHook(["module", "createData", "resolveData"]), + /** @type {HookMap>} */ + createParser: new HookMap(() => new SyncBailHook(["parserOptions"])), + /** @type {HookMap>} */ + parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])), + /** @type {HookMap>} */ + createGenerator: new HookMap( + () => new SyncBailHook(["generatorOptions"]) + ), + /** @type {HookMap>} */ + generator: new HookMap( + () => new SyncHook(["generator", "generatorOptions"]) + ), + /** @type {HookMap>} */ + createModuleClass: new HookMap( + () => new SyncBailHook(["createData", "resolveData"]) + ) + }); + this.resolverFactory = resolverFactory; + this.ruleSet = ruleSetCompiler.compile([ + { + rules: options.defaultRules + }, + { + rules: options.rules + } + ]); + this.context = context || ""; + 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(); + + const cacheParseResource = parseResource.bindCache( + associatedObjectForCache + ); + const cachedParseResourceWithoutFragment = + parseResourceWithoutFragment.bindCache(associatedObjectForCache); + this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment; + + this.hooks.factorize.tapAsync( + { + name: "NormalModuleFactory", + stage: 100 + }, + (resolveData, callback) => { + this.hooks.resolve.callAsync(resolveData, (err, result) => { + if (err) return callback(err); + + // Ignored + if (result === false) return callback(); + + // direct module + if (result instanceof Module) return callback(null, result); + + if (typeof result === "object") + throw new Error( + `${deprecationChangedHookMessage( + "resolve", + this.hooks.resolve + )} Returning a Module object will result in this module used as result.` + ); + + this.hooks.afterResolve.callAsync(resolveData, (err, result) => { + if (err) return callback(err); + + if (typeof result === "object") + throw new Error( + deprecationChangedHookMessage( + "afterResolve", + this.hooks.afterResolve + ) + ); + + // Ignored + if (result === false) return callback(); + + const createData = resolveData.createData; + + 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( + /** @type {ModuleSettings} */ + (createData.settings).type + ) + .call(createData, resolveData); + + if (!createdModule) { + createdModule = /** @type {Module} */ ( + new NormalModule( + /** @type {NormalModuleCreateData} */ + (createData) + ) + ); + } + } + + createdModule = this.hooks.module.call( + createdModule, + createData, + resolveData + ); + + return callback(null, createdModule); + } + ); + }); + }); + } + ); + this.hooks.resolve.tapAsync( + { + name: "NormalModuleFactory", + stage: 100 + }, + (data, callback) => { + const { + contextInfo, + context, + dependencies, + dependencyType, + request, + assertions, + resolveOptions, + fileDependencies, + missingDependencies, + contextDependencies + } = data; + const loaderResolver = this.getResolver("loader"); + + /** @type {ResourceData | undefined} */ + let matchResourceData; + /** @type {string} */ + let unresolvedResource; + /** @type {ParsedLoaderRequest[]} */ + let elements; + let noPreAutoLoaders = false; + let noAutoLoaders = false; + let noPrePostAutoLoaders = false; + + 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]; + if (matchResource.charCodeAt(0) === 46) { + // 46 === ".", 47 === "/" + const secondChar = matchResource.charCodeAt(1); + if ( + secondChar === 47 || + (secondChar === 46 && matchResource.charCodeAt(2) === 47) + ) { + // if matchResources startsWith ../ or ./ + matchResource = join(this.fs, context, matchResource); + } + } + matchResourceData = { + resource: matchResource, + ...cacheParseResource(matchResource) + }; + requestWithoutMatchResource = request.slice( + matchResourceMatch[0].length + ); + } + + 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); + + // translate option idents + try { + for (const item of /** @type {LoaderItem[]} */ (loaders)) { + if (typeof item.options === "string" && item.options[0] === "?") { + 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 (identErr) { + return callback(/** @type {Error} */ (identErr)); + } + + if (!resourceData) { + // ignored + return callback(null, dependencies[0].createIgnoredModule(context)); + } + + const userRequest = + (matchResourceData !== undefined + ? `${matchResourceData.resource}!=!` + : "") + + stringifyLoadersAndResource( + /** @type {LoaderItem[]} */ (loaders), + resourceData.resource + ); + + /** @type {ModuleSettings} */ + const settings = {}; + const useLoadersPost = []; + const useLoaders = []; + const useLoadersPre = []; + + // handle .webpack[] suffix + let resource; + 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, + assertions, + 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 ( + typeof r.value === "object" && + r.value !== null && + typeof settings[ + /** @type {keyof ModuleSettings} */ (r.type) + ] === "object" && + settings[/** @type {keyof ModuleSettings} */ (r.type)] !== null + ) { + settings[r.type] = cachedCleverMerge( + settings[/** @type {keyof ModuleSettings} */ (r.type)], + r.value + ); + } else { + settings[r.type] = r.value; + } + } + } + + /** @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 {string} */ (settings.type); + const resolveOptions = settings.resolve; + const layer = settings.layer; + if (layer !== undefined && !layers) { + return callback( + new Error( + "'Rule.layer' is only allowed when 'experiments.layers' is enabled" + ) + ); + } + 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 + }); + } 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(); + } + ); + + /** + * @param {string} context context + */ + const defaultResolve = context => { + if (/^($|\?)/.test(unresolvedResource)) { + resourceData = { + resource: unresolvedResource, + data: {}, + ...cacheParseResource(unresolvedResource) + }; + 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, + unresolvedResource, + normalResolver, + resolveContext, + (err, _resolvedResource, resolvedResourceResolveData) => { + if (err) return continueCallback(err); + if (_resolvedResource !== false) { + const resolvedResource = + /** @type {string} */ + (_resolvedResource); + resourceData = { + resource: resolvedResource, + data: + /** @type {ResolveRequest} */ + (resolvedResourceResolveData), + ...cacheParseResource(resolvedResource) + }; + } + 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(); + } + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies); + const context = data.context || this.context; + const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS; + const dependency = dependencies[0]; + const request = dependency.request; + const assertions = dependency.assertions; + const contextInfo = data.contextInfo; + const fileDependencies = new LazySet(); + const missingDependencies = new LazySet(); + const contextDependencies = new LazySet(); + const dependencyType = + (dependencies.length > 0 && dependencies[0].category) || ""; + /** @type {ResolveData} */ + const resolveData = { + contextInfo, + resolveOptions, + context, + request, + assertions, + 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 === false) { + /** @type {ModuleFactoryResult} * */ + const factoryResult = { + fileDependencies, + missingDependencies, + contextDependencies, + cacheable: resolveData.cacheable + }; + + if (resolveData.ignoredModule) { + factoryResult.module = resolveData.ignoredModule; + } + + return callback(null, factoryResult); + } + + 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); + }); + }); + } + + /** + * @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 = Array.from( + 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); + } + ); + } + + /** + * @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)); + } + ); + } + + /** + * @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, + (item, callback) => { + resolver.resolve( + contextInfo, + context, + item.loader, + resolveContext, + (err, result, resolveRequest) => { + if ( + err && + /^[^/]*$/.test(item.loader) && + !item.loader.endsWith("-loader") + ) { + return resolver.resolve( + contextInfo, + context, + `${item.loader}-loader`, + resolveContext, + err2 => { + if (!err2) { + err.message = + `${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` + + " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed"; + } + callback(err); + } + ); + } + if (err) return callback(err); + + 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 {ResolveRequest} */ + (resolveRequest).descriptionFileData.type; + const resolved = { + loader: parsedResult.path, + type, + options: + item.options === undefined + ? parsedResult.query + ? parsedResult.query.slice(1) + : undefined + : item.options, + ident: + item.options === undefined + ? undefined + : /** @type {string} */ (item.ident) + }; + + return callback(null, /** @type {LoaderItem} */ (resolved)); + } + ); + }, + /** @type {Callback} */ (callback) + ); + } + + /** + * @param {string} type type + * @param {ParserOptions} parserOptions parser options + * @returns {Parser} parser + */ + getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) { + let cache = this.parserCache.get(type); + + if (cache === undefined) { + cache = new WeakMap(); + this.parserCache.set(type, cache); + } + + let parser = cache.get(parserOptions); + + if (parser === undefined) { + parser = this.createParser(type, parserOptions); + cache.set(parserOptions, parser); + } + + return parser; + } + + /** + * @param {string} type type + * @param {ParserOptions} parserOptions parser options + * @returns {Parser} 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; + } + + /** + * @param {string} type type of generator + * @param {GeneratorOptions} generatorOptions generator options + * @returns {Generator} generator + */ + getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) { + let cache = this.generatorCache.get(type); + + if (cache === undefined) { + cache = new WeakMap(); + this.generatorCache.set(type, cache); + } + + let generator = cache.get(generatorOptions); + + if (generator === undefined) { + generator = this.createGenerator(type, generatorOptions); + cache.set(generatorOptions, generator); + } + + return generator; + } + + /** + * @param {string} type type of generator + * @param {GeneratorOptions} generatorOptions generator options + * @returns {Generator} generator + */ + createGenerator(type, generatorOptions = {}) { + generatorOptions = mergeGlobalOptions( + this._globalGeneratorOptions, + type, + generatorOptions + ); + const generator = this.hooks.createGenerator + .for(type) + .call(generatorOptions); + if (!generator) { + throw new Error(`No generator registered for ${type}`); + } + this.hooks.generator.for(type).call(generator, generatorOptions); + return generator; + } + + /** + * @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); + } +} + +module.exports = NormalModuleFactory; diff --git a/webpack-lib/lib/NormalModuleReplacementPlugin.js b/webpack-lib/lib/NormalModuleReplacementPlugin.js new file mode 100644 index 00000000000..fb44e088db1 --- /dev/null +++ b/webpack-lib/lib/NormalModuleReplacementPlugin.js @@ -0,0 +1,77 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { join, dirname } = require("./util/fs"); + +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +/** @typedef {function(import("./NormalModuleFactory").ResolveData): void} ModuleReplacer */ + +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; + } + + /** + * Apply the plugin + * @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 (resourceRegExp.test(result.request)) { + if (typeof newResource === "function") { + newResource(result); + } else { + result.request = newResource; + } + } + }); + nmf.hooks.afterResolve.tap("NormalModuleReplacementPlugin", 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 { + createData.resource = join( + fs, + dirname(fs, /** @type {string} */ (createData.resource)), + newResource + ); + } + } + } + }); + } + ); + } +} + +module.exports = NormalModuleReplacementPlugin; diff --git a/webpack-lib/lib/NullFactory.js b/webpack-lib/lib/NullFactory.js new file mode 100644 index 00000000000..50f3471be46 --- /dev/null +++ b/webpack-lib/lib/NullFactory.js @@ -0,0 +1,23 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleFactory = require("./ModuleFactory"); + +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ + +class NullFactory extends ModuleFactory { + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + return callback(); + } +} +module.exports = NullFactory; diff --git a/webpack-lib/lib/OptimizationStages.js b/webpack-lib/lib/OptimizationStages.js new file mode 100644 index 00000000000..102d613c5aa --- /dev/null +++ b/webpack-lib/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_BASIC = -10; +module.exports.STAGE_DEFAULT = 0; +module.exports.STAGE_ADVANCED = 10; diff --git a/webpack-lib/lib/OptionsApply.js b/webpack-lib/lib/OptionsApply.js new file mode 100644 index 00000000000..b7a3941543b --- /dev/null +++ b/webpack-lib/lib/OptionsApply.js @@ -0,0 +1,22 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./Compiler")} Compiler */ + +class OptionsApply { + /** + * @param {WebpackOptions} options options object + * @param {Compiler} compiler compiler object + * @returns {WebpackOptions} options object + */ + process(options, compiler) { + return options; + } +} + +module.exports = OptionsApply; diff --git a/webpack-lib/lib/Parser.js b/webpack-lib/lib/Parser.js new file mode 100644 index 00000000000..892c5fcd329 --- /dev/null +++ b/webpack-lib/lib/Parser.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./NormalModule")} NormalModule */ + +/** @typedef {Record} PreparsedAst */ + +/** + * @typedef {object} ParserStateBase + * @property {string | Buffer} source + * @property {NormalModule} current + * @property {NormalModule} module + * @property {Compilation} compilation + * @property {{[k: string]: any}} options + */ + +/** @typedef {Record & ParserStateBase} ParserState */ + +class Parser { + /* istanbul ignore next */ + /** + * @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("./AbstractMethodError"); + throw new AbstractMethodError(); + } +} + +module.exports = Parser; diff --git a/webpack-lib/lib/PlatformPlugin.js b/webpack-lib/lib/PlatformPlugin.js new file mode 100644 index 00000000000..ae601ae8b45 --- /dev/null +++ b/webpack-lib/lib/PlatformPlugin.js @@ -0,0 +1,39 @@ +/* + 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 */ + +/** + * Should be used only for "target === false" or + * when you want to overwrite platform target properties + */ +class PlatformPlugin { + /** + * @param {Partial} platform target properties + */ + constructor(platform) { + /** @type {Partial} */ + this.platform = platform; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.environment.tap("PlatformPlugin", () => { + compiler.platform = { + ...compiler.platform, + ...this.platform + }; + }); + } +} + +module.exports = PlatformPlugin; diff --git a/webpack-lib/lib/PrefetchPlugin.js b/webpack-lib/lib/PrefetchPlugin.js new file mode 100644 index 00000000000..4f09fc0c3dc --- /dev/null +++ b/webpack-lib/lib/PrefetchPlugin.js @@ -0,0 +1,54 @@ +/* + 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 */ + +class PrefetchPlugin { + /** + * @param {string} context context or request if context is not set + * @param {string} [request] request + */ + constructor(context, request) { + if (request) { + this.context = context; + this.request = request; + } else { + this.context = null; + this.request = context; + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "PrefetchPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + PrefetchDependency, + normalModuleFactory + ); + } + ); + compiler.hooks.make.tapAsync("PrefetchPlugin", (compilation, callback) => { + compilation.addModuleChain( + this.context || compiler.context, + new PrefetchDependency(this.request), + err => { + callback(err); + } + ); + }); + } +} + +module.exports = PrefetchPlugin; diff --git a/webpack-lib/lib/ProgressPlugin.js b/webpack-lib/lib/ProgressPlugin.js new file mode 100644 index 00000000000..b8be13916cc --- /dev/null +++ b/webpack-lib/lib/ProgressPlugin.js @@ -0,0 +1,709 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Compiler = require("./Compiler"); +const MultiCompiler = require("./MultiCompiler"); +const NormalModule = require("./NormalModule"); +const createSchemaValidation = require("./util/create-schema-validation"); +const { contextify } = require("./util/identifier"); + +/** @typedef {import("tapable").Tap} Tap */ +/** @typedef {import("../declarations/plugins/ProgressPlugin").HandlerFunction} HandlerFunction */ +/** @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 */ + +/** + * @template T, K, R + * @typedef {import("./util/AsyncQueue")} AsyncQueue + */ + +/** + * @typedef {object} CountsData + * @property {number} modulesCount modules count + * @property {number} dependenciesCount dependencies count + */ + +const validate = createSchemaValidation( + require("../schemas/plugins/ProgressPlugin.check.js"), + () => require("../schemas/plugins/ProgressPlugin.json"), + { + name: "Progress Plugin", + baseDataPath: "options" + } +); + +/** + * @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); + +/** + * @param {boolean | null | undefined} profile need profile + * @param {Logger} logger logger + * @returns {defaultHandler} default handler + */ +const createDefaultHandler = (profile, logger) => { + /** @type {{ value: string | undefined, time: number }[]} */ + const lastStateInfo = []; + + /** + * @param {number} percentage percentage + * @param {string} msg message + * @param {...string} args additional arguments + */ + const defaultHandler = (percentage, msg, ...args) => { + if (profile) { + if (percentage === 0) { + lastStateInfo.length = 0; + } + const fullState = [msg, ...args]; + const state = fullState.map(s => s.replace(/\d+\/\d+ /g, "")); + const now = Date.now(); + const len = Math.max(state.length, lastStateInfo.length); + for (let i = len; i >= 0; i--) { + const stateItem = i < state.length ? state[i] : undefined; + const lastStateItem = + i < lastStateInfo.length ? lastStateInfo[i] : undefined; + if (lastStateItem) { + if (stateItem !== lastStateItem.value) { + const diff = now - lastStateItem.time; + if (lastStateItem.value) { + let reportState = lastStateItem.value; + if (i > 0) { + reportState = `${lastStateInfo[i - 1].value} > ${reportState}`; + } + const stateMsg = `${" | ".repeat(i)}${diff} ms ${reportState}`; + const d = diff; + // This depends on timing so we ignore it for coverage + /* eslint-disable no-lone-blocks */ + /* istanbul ignore next */ + { + if (d > 10000) { + logger.error(stateMsg); + } else if (d > 1000) { + logger.warn(stateMsg); + } else if (d > 10) { + logger.info(stateMsg); + } else if (d > 5) { + logger.log(stateMsg); + } else { + logger.debug(stateMsg); + } + } + /* eslint-enable no-lone-blocks */ + } + if (stateItem === undefined) { + lastStateInfo.length = i; + } else { + lastStateItem.value = stateItem; + lastStateItem.time = now; + lastStateInfo.length = i + 1; + } + } + } else { + lastStateInfo[i] = { + value: stateItem, + time: now + }; + } + } + } + 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"]; + +/** + * @callback ReportProgress + * @param {number} p percentage + * @param {...string} args additional arguments + * @returns {void} + */ + +/** @type {WeakMap} */ +const progressReporters = new WeakMap(); + +class ProgressPlugin { + /** + * @param {Compiler} compiler the current compiler + * @returns {ReportProgress | undefined} a progress reporter, if any + */ + static getReporter(compiler) { + return progressReporters.get(compiler); + } + + /** + * @param {ProgressPluginArgument} options options + */ + constructor(options = {}) { + if (typeof options === "function") { + options = { + handler: options + }; + } + + validate(options); + options = { ...ProgressPlugin.defaultOptions, ...options }; + + this.profile = options.profile; + this.handler = options.handler; + this.modulesCount = options.modulesCount; + this.dependenciesCount = options.dependenciesCount; + this.showEntries = options.entries; + this.showModules = options.modules; + this.showDependencies = options.dependencies; + this.showActiveModules = options.activeModules; + this.percentBy = options.percentBy; + } + + /** + * @param {Compiler | MultiCompiler} compiler webpack compiler + * @returns {void} + */ + apply(compiler) { + const handler = + this.handler || + createDefaultHandler( + this.profile, + compiler.getInfrastructureLogger("webpack.Progress") + ); + if (compiler instanceof MultiCompiler) { + this._applyOnMultiCompiler(compiler, handler); + } else if (compiler instanceof Compiler) { + this._applyOnCompiler(compiler, handler); + } + } + + /** + * @param {MultiCompiler} compiler webpack multi-compiler + * @param {HandlerFunction} 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); + } + } + + /** + * @param {Compiler} compiler webpack compiler + * @param {HandlerFunction} handler function that executes for every progress step + * @returns {void} + */ + _applyOnCompiler(compiler, handler) { + 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; + 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); + 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 + ); + } + + const percentage = 0.1 + percentageFactor * 0.55; + + if (currentLoader) { + items.push( + `import loader ${contextify( + compiler.context, + currentLoader, + compiler.root + )}` + ); + } else { + 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(); + }; + + /** + * @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(); + }; + + /** + * @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 + /** + * @param {Module} module the module + */ + const moduleBuild = module => { + const ident = module.identifier(); + if (ident) { + activeModules.add(ident); + lastActiveModule = ident; + update(); + } + }; + + /** + * @param {Dependency} entry entry dependency + * @param {EntryOptions} options options object + */ + const entryAdd = (entry, options) => { + entriesCount++; + if (entriesCount < 5 || entriesCount % 10 === 0) updateThrottled(); + }; + + /** + * @param {Module} module the module + */ + const moduleDone = module => { + doneModules++; + if (showActiveModules) { + const ident = module.identifier(); + if (ident) { + activeModules.delete(ident); + if (lastActiveModule === ident) { + lastActiveModule = ""; + for (const m of activeModules) { + lastActiveModule = m; + } + update(); + return; + } + } + } + if (doneModules < 50 || doneModules % 100 === 0) updateThrottled(); + }; + + /** + * @param {Dependency} entry entry dependency + * @param {EntryOptions} options options object + */ + const entryDone = (entry, options) => { + doneEntries++; + update(); + }; + + const cache = compiler + .getCache("ProgressPlugin") + .getItemCache("counts", null); + + /** @type {Promise | undefined} */ + let cacheGetPromise; + + compiler.hooks.beforeCompile.tap("ProgressPlugin", () => { + if (!cacheGetPromise) { + cacheGetPromise = cache.getPromise().then( + data => { + if (data) { + lastModulesCount = lastModulesCount || data.modulesCount; + lastDependenciesCount = + lastDependenciesCount || data.dependenciesCount; + } + return data; + }, + err => { + // Ignore error + } + ); + } + }); + + compiler.hooks.afterCompile.tapPromise("ProgressPlugin", 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("ProgressPlugin", 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("ProgressPlugin", item => + factorizeAdd(compilation.factorizeQueue, item) + ); + compilation.factorizeQueue.hooks.result.tap( + "ProgressPlugin", + factorizeDone + ); + + compilation.addModuleQueue.hooks.added.tap("ProgressPlugin", item => + moduleAdd(compilation.addModuleQueue, item) + ); + compilation.processDependenciesQueue.hooks.result.tap( + "ProgressPlugin", + moduleDone + ); + + if (showActiveModules) { + compilation.hooks.buildModule.tap("ProgressPlugin", moduleBuild); + } + + compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd); + compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone); + compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone); + + // avoid dynamic require if bundled with webpack + // @ts-expect-error + if (typeof __webpack_require__ !== "function") { + const requiredLoaders = new Set(); + NormalModule.getCompilationHooks(compilation).beforeLoaders.tap( + "ProgressPlugin", + 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); + } + } + 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: "ProgressPlugin", + 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.make.intercept({ + name: "ProgressPlugin", + call() { + handler(0.1, "building"); + }, + done() { + handler(0.65, "building"); + } + }); + /** + * @param {TODO} 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: "ProgressPlugin", + call() { + handler(progress, category, name); + }, + done() { + progressReporters.set(compiler, undefined); + handler(progress, category, name); + }, + result() { + handler(progress, category, name); + }, + error() { + handler(progress, category, name); + }, + /** + * @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.cache.hooks.endIdle.intercept({ + name: "ProgressPlugin", + call() { + handler(0, ""); + } + }); + interceptHook(compiler.cache.hooks.endIdle, 0.01, "cache", "end idle"); + compiler.hooks.beforeRun.intercept({ + name: "ProgressPlugin", + 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: "ProgressPlugin", + 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: "ProgressPlugin", + done() { + handler(1, ""); + } + }); + compiler.cache.hooks.shutdown.intercept({ + name: "ProgressPlugin", + done() { + handler(1, ""); + } + }); + } +} + +ProgressPlugin.defaultOptions = { + profile: false, + modulesCount: 5000, + dependenciesCount: 10000, + modules: true, + dependencies: true, + activeModules: false, + entries: true +}; + +ProgressPlugin.createDefaultHandler = createDefaultHandler; + +module.exports = ProgressPlugin; diff --git a/webpack-lib/lib/ProvidePlugin.js b/webpack-lib/lib/ProvidePlugin.js new file mode 100644 index 00000000000..28c3ce5d590 --- /dev/null +++ b/webpack-lib/lib/ProvidePlugin.js @@ -0,0 +1,119 @@ +/* + 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 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 PLUGIN_NAME = "ProvidePlugin"; + +class ProvidePlugin { + /** + * @param {Record} definitions the provided identifiers + */ + constructor(definitions) { + this.definitions = definitions; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const definitions = this.definitions; + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + compilation.dependencyFactories.set( + ProvidedDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + ProvidedDependency, + new ProvidedDependency.Template() + ); + /** + * @param {JavascriptParser} parser the parser + * @param {JavascriptParserOptions} parserOptions options + * @returns {void} + */ + const handler = (parser, parserOptions) => { + for (const name of Object.keys(definitions)) { + const request = + /** @type {string[]} */ + ([]).concat(definitions[name]); + const splittedName = name.split("."); + if (splittedName.length > 0) { + for (const [i, _] of splittedName.slice(1).entries()) { + const name = splittedName.slice(0, i + 1).join("."); + 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_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 = ProvidePlugin; diff --git a/webpack-lib/lib/RawModule.js b/webpack-lib/lib/RawModule.js new file mode 100644 index 00000000000..bd02863c672 --- /dev/null +++ b/webpack-lib/lib/RawModule.js @@ -0,0 +1,164 @@ +/* + 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 { JS_TYPES } = require("./ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("./Generator").SourceTypes} SourceTypes */ +/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("./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 */ + +class RawModule extends Module { + /** + * @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.runtimeRequirements = runtimeRequirements || null; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this.identifierStr; + } + + /** + * @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); + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return /** @type {string} */ ( + requestShortener.shorten(this.readableIdentifierStr) + ); + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + cacheable: true + }; + callback(); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + const sources = new Map(); + if (this.useSourceMap || this.useSimpleSourceMap) { + sources.set( + "javascript", + new OriginalSource(this.sourceStr, this.identifier()) + ); + } else { + sources.set("javascript", new RawSource(this.sourceStr)); + } + return { sources, runtimeRequirements: this.runtimeRequirements }; + } + + /** + * @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, context); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.sourceStr); + write(this.identifierStr); + write(this.readableIdentifierStr); + write(this.runtimeRequirements); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.sourceStr = read(); + this.identifierStr = read(); + this.readableIdentifierStr = read(); + this.runtimeRequirements = read(); + + super.deserialize(context); + } +} + +makeSerializable(RawModule, "webpack/lib/RawModule"); + +module.exports = RawModule; diff --git a/webpack-lib/lib/RecordIdsPlugin.js b/webpack-lib/lib/RecordIdsPlugin.js new file mode 100644 index 00000000000..aaace61c89a --- /dev/null +++ b/webpack-lib/lib/RecordIdsPlugin.js @@ -0,0 +1,238 @@ +/* + 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("./Chunk")} Chunk */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Module")} Module */ + +/** + * @typedef {object} RecordsChunks + * @property {Record=} byName + * @property {Record=} bySource + * @property {number[]=} usedIds + */ + +/** + * @typedef {object} RecordsModules + * @property {Record=} byIdentifier + * @property {Record=} bySource + * @property {number[]=} usedIds + */ + +/** + * @typedef {object} Records + * @property {RecordsChunks=} chunks + * @property {RecordsModules=} modules + */ + +class RecordIdsPlugin { + /** + * @param {object} options Options object + * @param {boolean=} options.portableIds true, when ids need to be portable + */ + constructor(options) { + this.options = options || {}; + } + + /** + * @param {Compiler} compiler the Compiler + * @returns {void} + */ + apply(compiler) { + const portableIds = this.options.portableIds; + + const makePathsRelative = + identifierUtils.makePathsRelative.bindContextCache( + compiler.context, + compiler.root + ); + + /** + * @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("RecordIdsPlugin", compilation => { + compilation.hooks.recordModules.tap( + "RecordIdsPlugin", + /** + * @param {Iterable} modules the modules array + * @param {Records} records the records object + * @returns {void} + */ + (modules, records) => { + const chunkGraph = compilation.chunkGraph; + if (!records.modules) records.modules = {}; + if (!records.modules.byIdentifier) records.modules.byIdentifier = {}; + /** @type {Set} */ + 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 = Array.from(usedIds).sort(compareNumbers); + } + ); + compilation.hooks.reviveModules.tap( + "RecordIdsPlugin", + /** + * @param {Iterable} modules the modules array + * @param {Records} records the records object + * @returns {void} + */ + (modules, records) => { + if (!records.modules) return; + if (records.modules.byIdentifier) { + const chunkGraph = compilation.chunkGraph; + /** @type {Set} */ + const usedIds = new Set(); + for (const module of modules) { + 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); + } + } + if (Array.isArray(records.modules.usedIds)) { + compilation.usedModuleIds = new Set(records.modules.usedIds); + } + } + ); + + /** + * @param {Chunk} chunk the chunk + * @returns {string[]} sources of the chunk + */ + const getChunkSources = chunk => { + /** @type {string[]} */ + const sources = []; + for (const chunkGroup of chunk.groupsIterable) { + const index = chunkGroup.chunks.indexOf(chunk); + 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)}` + ); + } + } + } + } + } + return sources; + }; + + compilation.hooks.recordChunks.tap( + "RecordIdsPlugin", + /** + * @param {Iterable} 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(); + 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 = Array.from(usedIds).sort(compareNumbers); + } + ); + compilation.hooks.reviveChunks.tap( + "RecordIdsPlugin", + /** + * @param {Iterable} 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 (id === undefined) continue; + if (usedIds.has(id)) continue; + usedIds.add(id); + chunk.id = id; + chunk.ids = [id]; + } + } + 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 (Array.isArray(records.chunks.usedIds)) { + compilation.usedChunkIds = new Set(records.chunks.usedIds); + } + } + ); + }); + } +} +module.exports = RecordIdsPlugin; diff --git a/webpack-lib/lib/RequestShortener.js b/webpack-lib/lib/RequestShortener.js new file mode 100644 index 00000000000..9ef80190fed --- /dev/null +++ b/webpack-lib/lib/RequestShortener.js @@ -0,0 +1,34 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { contextify } = require("./util/identifier"); + +class RequestShortener { + /** + * @param {string} dir the directory + * @param {object=} associatedObjectForCache an object to which the cache will be attached + */ + constructor(dir, associatedObjectForCache) { + this.contextify = contextify.bindContextCache( + dir, + associatedObjectForCache + ); + } + + /** + * @param {string | undefined | null} request the request to shorten + * @returns {string | undefined | null} the shortened request + */ + shorten(request) { + if (!request) { + return request; + } + return this.contextify(request); + } +} + +module.exports = RequestShortener; diff --git a/webpack-lib/lib/RequireJsStuffPlugin.js b/webpack-lib/lib/RequireJsStuffPlugin.js new file mode 100644 index 00000000000..c9acc6643dd --- /dev/null +++ b/webpack-lib/lib/RequireJsStuffPlugin.js @@ -0,0 +1,84 @@ +/* + 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 +} = require("./ModuleTypeConstants"); +const RuntimeGlobals = require("./RuntimeGlobals"); +const ConstDependency = require("./dependencies/ConstDependency"); +const { + toConstantDependency +} = require("./javascript/JavascriptParserHelpers"); + +/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ + +const PLUGIN_NAME = "RequireJsStuffPlugin"; + +module.exports = class RequireJsStuffPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + ConstDependency, + new ConstDependency.Template() + ); + /** + * @param {JavascriptParser} parser the parser + * @param {JavascriptParserOptions} parserOptions options + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if ( + parserOptions.requireJs === undefined || + !parserOptions.requireJs + ) { + return; + } + + parser.hooks.call + .for("require.config") + .tap(PLUGIN_NAME, toConstantDependency(parser, "undefined")); + parser.hooks.call + .for("requirejs.config") + .tap(PLUGIN_NAME, toConstantDependency(parser, "undefined")); + + parser.hooks.expression + .for("require.version") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("0.0.0")) + ); + parser.hooks.expression + .for("requirejs.onError") + .tap( + PLUGIN_NAME, + toConstantDependency( + parser, + RuntimeGlobals.uncaughtErrorHandler, + [RuntimeGlobals.uncaughtErrorHandler] + ) + ); + }; + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + } + ); + } +}; diff --git a/webpack-lib/lib/ResolverFactory.js b/webpack-lib/lib/ResolverFactory.js new file mode 100644 index 00000000000..9651c6a73e8 --- /dev/null +++ b/webpack-lib/lib/ResolverFactory.js @@ -0,0 +1,155 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Factory = require("enhanced-resolve").ResolverFactory; +const { HookMap, SyncHook, SyncWaterfallHook } = require("tapable"); +const { + cachedCleverMerge, + removeOperations, + resolveByProperty +} = require("./util/cleverMerge"); + +/** @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("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */ +/** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */ + +/** @typedef {WebpackResolveOptions & {dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */ +/** + * @typedef {object} WithOptions + * @property {function(Partial): ResolverWithOptions} withOptions create a resolver with additional/different options + */ + +/** @typedef {Resolver & WithOptions} ResolverWithOptions */ + +// need to be hoisted on module level for caching identity +const EMPTY_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 removeOperations( + resolveByProperty(options, "byDependency", dependencyType), + // Keep the `unsafeCache` because it can be a `Proxy` + ["unsafeCache"] + ); +}; + +/** + * @typedef {object} ResolverCache + * @property {WeakMap} direct + * @property {Map} stringified + */ + +module.exports = class ResolverFactory { + constructor() { + this.hooks = Object.freeze({ + /** @type {HookMap>} */ + resolveOptions: new HookMap( + () => new SyncWaterfallHook(["resolveOptions"]) + ), + /** @type {HookMap>} */ + resolver: new HookMap( + () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"]) + ) + }); + /** @type {Map} */ + this.cache = new Map(); + } + + /** + * @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); + typedCaches.direct.set(resolveOptions, newResolver); + typedCaches.stringified.set(ident, newResolver); + return newResolver; + } + + /** + * @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"); + } + /** @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/webpack-lib/lib/RuntimeGlobals.js b/webpack-lib/lib/RuntimeGlobals.js new file mode 100644 index 00000000000..7d201f6267a --- /dev/null +++ b/webpack-lib/lib/RuntimeGlobals.js @@ -0,0 +1,387 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * the internal require function + */ +module.exports.require = "__webpack_require__"; + +/** + * access to properties of the internal require function/object + */ +module.exports.requireScope = "__webpack_require__.*"; + +/** + * the internal exports object + */ +module.exports.exports = "__webpack_exports__"; + +/** + * top-level this need to be the exports object + */ +module.exports.thisAsExports = "top-level-this-exports"; + +/** + * runtime need to return the exports of the last entry module + */ +module.exports.returnExportsFromRuntime = "return-exports-from-runtime"; + +/** + * the internal module object + */ +module.exports.module = "module"; + +/** + * the internal module object + */ +module.exports.moduleId = "module.id"; + +/** + * the internal module object + */ +module.exports.moduleLoaded = "module.loaded"; + +/** + * the bundle public path + */ +module.exports.publicPath = "__webpack_require__.p"; + +/** + * the module id of the entry point + */ +module.exports.entryModuleId = "__webpack_require__.s"; + +/** + * 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 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 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 exported property define getters function + */ +module.exports.definePropertyGetters = "__webpack_require__.d"; + +/** + * define compatibility on export + */ +module.exports.makeNamespaceObject = "__webpack_require__.r"; + +/** + * create a fake namespace object + */ +module.exports.createFakeNamespaceObject = "__webpack_require__.t"; + +/** + * compatibility get default export + */ +module.exports.compatGetDefaultExport = "__webpack_require__.n"; + +/** + * harmony module decorator + */ +module.exports.harmonyModuleDecorator = "__webpack_require__.hmd"; + +/** + * node.js module decorator + */ +module.exports.nodeModuleDecorator = "__webpack_require__.nmd"; + +/** + * the webpack hash + */ +module.exports.getFullHash = "__webpack_require__.h"; + +/** + * an object containing all installed WebAssembly.Instance export objects keyed by module id + */ +module.exports.wasmInstances = "__webpack_require__.w"; + +/** + * instantiate a wasm instance from module exports object, id, hash and importsObject + */ +module.exports.instantiateWasm = "__webpack_require__.v"; + +/** + * the uncaught error handler for the webpack runtime + */ +module.exports.uncaughtErrorHandler = "__webpack_require__.oe"; + +/** + * the script nonce + */ +module.exports.scriptNonce = "__webpack_require__.nc"; + +/** + * 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"; + +/** + * 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"; + +/** + * function to return webpack's Trusted Types policy + * Arguments: () => TrustedTypePolicy + */ +module.exports.getTrustedTypesPolicy = "__webpack_require__.tt"; + +/** + * a flag when a chunk has a fetch priority + */ +module.exports.hasFetchPriority = "has fetch priority"; + +/** + * the chunk name of the chunk with the runtime + */ +module.exports.chunkName = "__webpack_require__.cn"; + +/** + * the runtime id of the current runtime + */ +module.exports.runtimeId = "__webpack_require__.j"; + +/** + * the filename of the script part of the chunk + */ +module.exports.getChunkScriptFilename = "__webpack_require__.u"; + +/** + * the filename of the css part of the chunk + */ +module.exports.getChunkCssFilename = "__webpack_require__.k"; + +/** + * a flag when a module/chunk/tree has css modules + */ +module.exports.hasCssModules = "has css modules"; + +/** + * the filename of the script part of the hot update chunk + */ +module.exports.getChunkUpdateScriptFilename = "__webpack_require__.hu"; + +/** + * the filename of the css part of the hot update chunk + */ +module.exports.getChunkUpdateCssFilename = "__webpack_require__.hk"; + +/** + * startup signal from runtime + * This will be called when the runtime chunk has been loaded. + */ +module.exports.startup = "__webpack_require__.x"; + +/** + * @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)"; + +/** + * global callback functions for installing chunks + */ +module.exports.chunkCallback = "webpackChunk"; + +/** + * 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"; + +/** + * 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"; + +/** + * method to install a chunk that was loaded somehow + * Signature: ({ id, ids, modules, runtime }) => void + */ +module.exports.externalInstallChunk = "__webpack_require__.C"; + +/** + * interceptor for module executions + */ +module.exports.interceptModuleExecution = "__webpack_require__.i"; + +/** + * the global object + */ +module.exports.global = "__webpack_require__.g"; + +/** + * an object with all share scopes + */ +module.exports.shareScopeMap = "__webpack_require__.S"; + +/** + * 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"; + +/** + * The current scope when getting a module from a remote + */ +module.exports.currentRemoteGetScope = "__webpack_require__.R"; + +/** + * the filename of the HMR manifest + */ +module.exports.getUpdateManifestFilename = "__webpack_require__.hmrF"; + +/** + * 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"; + +/** + * object with all hmr module data for all modules + */ +module.exports.hmrModuleData = "__webpack_require__.hmrD"; + +/** + * array with handler functions when a module should be invalidated + */ +module.exports.hmrInvalidateModuleHandlers = "__webpack_require__.hmrI"; + +/** + * the prefix for storing state of runtime modules when hmr is enabled + */ +module.exports.hmrRuntimeStatePrefix = "__webpack_require__.hmrS"; + +/** + * the AMD define function + */ +module.exports.amdDefine = "__webpack_require__.amdD"; + +/** + * the AMD options + */ +module.exports.amdOptions = "__webpack_require__.amdO"; + +/** + * the System polyfill object + */ +module.exports.system = "__webpack_require__.System"; + +/** + * the shorthand for Object.prototype.hasOwnProperty + * using of it decreases the compiled bundle size + */ +module.exports.hasOwnProperty = "__webpack_require__.o"; + +/** + * the System.register context object + */ +module.exports.systemContext = "__webpack_require__.y"; + +/** + * the baseURI of current document + */ +module.exports.baseURI = "__webpack_require__.b"; + +/** + * a RelativeURL class when relative URLs are used + */ +module.exports.relativeUrl = "__webpack_require__.U"; + +/** + * 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"; diff --git a/webpack-lib/lib/RuntimeModule.js b/webpack-lib/lib/RuntimeModule.js new file mode 100644 index 00000000000..f4fff959ca4 --- /dev/null +++ b/webpack-lib/lib/RuntimeModule.js @@ -0,0 +1,214 @@ +/* + 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 { RUNTIME_TYPES } = require("./ModuleSourceTypesConstants"); +const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} 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").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("./RequestShortener")} RequestShortener */ +/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./util/Hash")} Hash */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ + +class RuntimeModule extends Module { + /** + * @param {string} name a readable name + * @param {number=} stage an optional stage + */ + constructor(name, stage = 0) { + super(WEBPACK_MODULE_TYPE_RUNTIME); + this.name = name; + this.stage = stage; + this.buildMeta = {}; + this.buildInfo = {}; + /** @type {Compilation | undefined} */ + this.compilation = undefined; + /** @type {Chunk | undefined} */ + this.chunk = undefined; + /** @type {ChunkGraph | undefined} */ + this.chunkGraph = undefined; + this.fullHash = false; + this.dependentHash = false; + /** @type {string | undefined | null} */ + this._cachedGeneratedCode = undefined; + } + + /** + * @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 {string} a unique identifier of the module + */ + identifier() { + return `webpack/runtime/${this.name}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `webpack/runtime/${this.name}`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, false); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} 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(); + } + + /** + * @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 { + if (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. + hash.update(/** @type {string} */ (this.generate())); + } else { + hash.update(/** @type {string} */ (this.getGeneratedCode())); + } + } catch (err) { + hash.update(/** @type {Error} */ (err).message); + } + super.updateHash(hash, context); + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return RUNTIME_TYPES; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + 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 + }; + } + + /** + * @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 */ + /** + * @abstract + * @returns {string | null} runtime code + */ + generate() { + const AbstractMethodError = require("./AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @returns {string | null} runtime code + */ + getGeneratedCode() { + if (this._cachedGeneratedCode) { + return this._cachedGeneratedCode; + } + return (this._cachedGeneratedCode = this.generate()); + } + + /** + * @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/webpack-lib/lib/RuntimePlugin.js b/webpack-lib/lib/RuntimePlugin.js new file mode 100644 index 00000000000..cabdffeaa60 --- /dev/null +++ b/webpack-lib/lib/RuntimePlugin.js @@ -0,0 +1,496 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("./RuntimeGlobals"); +const { getChunkFilenameTemplate } = require("./css/CssModulesPlugin"); +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 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 SystemContextRuntimeModule = require("./runtime/SystemContextRuntimeModule"); +const ShareRuntimeModule = require("./sharing/ShareRuntimeModule"); +const StringXor = require("./util/StringXor"); +const memoize = require("./util/memoize"); + +/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ + +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.systemContext, + RuntimeGlobals.onChunksLoaded +]; + +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.initializeSharing]: [RuntimeGlobals.shareScopeMap], + [RuntimeGlobals.shareScopeMap]: [RuntimeGlobals.hasOwnProperty] +}; + +class RuntimePlugin { + /** + * @param {Compiler} compiler the Compiler + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("RuntimePlugin", compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @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("RuntimePlugin", (module, set) => { + set.add(RuntimeGlobals.requireScope); + }); + compilation.hooks.runtimeRequirementInTree + .for(req) + .tap("RuntimePlugin", (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("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { + for (const dep of deps) set.add(dep); + }); + } + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.definePropertyGetters) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule( + chunk, + new DefinePropertyGettersRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.makeNamespaceObject) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule( + chunk, + new MakeNamespaceObjectRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.createFakeNamespaceObject) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule( + chunk, + new CreateFakeNamespaceObjectRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hasOwnProperty) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule( + chunk, + new HasOwnPropertyRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.compatGetDefaultExport) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule( + chunk, + new CompatGetDefaultExportRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.runtimeId) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule(chunk, new RuntimeIdRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.publicPath) + .tap("RuntimePlugin", (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") set.add(RuntimeGlobals.global); + compilation.addRuntimeModule(chunk, module); + } else { + const module = new PublicPathRuntimeModule(publicPath); + + if ( + typeof publicPath !== "string" || + /\[(full)?hash\]/.test(publicPath) + ) { + module.fullHash = true; + } + + compilation.addRuntimeModule(chunk, module); + } + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.global) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule(chunk, new GlobalRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.asyncModule) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule(chunk, new AsyncModuleRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.systemContext) + .tap("RuntimePlugin", 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("RuntimePlugin", (chunk, set, { chunkGraph }) => { + if ( + typeof compilation.outputOptions.chunkFilename === "string" && + /\[(full)?hash(:\d+)?\]/.test( + compilation.outputOptions.chunkFilename + ) + ) { + set.add(RuntimeGlobals.getFullHash); + } + compilation.addRuntimeModule( + chunk, + new GetChunkFilenameRuntimeModule( + "javascript", + "javascript", + RuntimeGlobals.getChunkScriptFilename, + chunk => + getJavascriptModulesPlugin().chunkHasJs(chunk, chunkGraph) && + /** @type {TemplatePath} */ ( + chunk.filenameTemplate || + (chunk.canBeInitial() + ? compilation.outputOptions.filename + : compilation.outputOptions.chunkFilename) + ), + false + ) + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.getChunkCssFilename) + .tap("RuntimePlugin", (chunk, set, { chunkGraph }) => { + if ( + typeof compilation.outputOptions.cssChunkFilename === "string" && + /\[(full)?hash(:\d+)?\]/.test( + compilation.outputOptions.cssChunkFilename + ) + ) { + set.add(RuntimeGlobals.getFullHash); + } + compilation.addRuntimeModule( + chunk, + new GetChunkFilenameRuntimeModule( + "css", + "css", + RuntimeGlobals.getChunkCssFilename, + chunk => + getCssModulesPlugin().chunkHasCss(chunk, chunkGraph) && + getChunkFilenameTemplate(chunk, compilation.outputOptions), + set.has(RuntimeGlobals.hmrDownloadUpdateHandlers) + ) + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.getChunkUpdateScriptFilename) + .tap("RuntimePlugin", (chunk, set) => { + if ( + /\[(full)?hash(:\d+)?\]/.test( + /** @type {NonNullable} */ + (compilation.outputOptions.hotUpdateChunkFilename) + ) + ) + set.add(RuntimeGlobals.getFullHash); + compilation.addRuntimeModule( + chunk, + new GetChunkFilenameRuntimeModule( + "javascript", + "javascript update", + RuntimeGlobals.getChunkUpdateScriptFilename, + c => + /** @type {NonNullable} */ + (compilation.outputOptions.hotUpdateChunkFilename), + true + ) + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.getUpdateManifestFilename) + .tap("RuntimePlugin", (chunk, set) => { + if ( + /\[(full)?hash(:\d+)?\]/.test( + /** @type {NonNullable} */ + (compilation.outputOptions.hotUpdateMainFilename) + ) + ) { + set.add(RuntimeGlobals.getFullHash); + } + compilation.addRuntimeModule( + chunk, + new GetMainFilenameRuntimeModule( + "update manifest", + RuntimeGlobals.getUpdateManifestFilename, + /** @type {NonNullable} */ + (compilation.outputOptions.hotUpdateMainFilename) + ) + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunk) + .tap("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { + set.add(RuntimeGlobals.ensureChunkHandlers); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.shareScopeMap) + .tap("RuntimePlugin", (chunk, set) => { + compilation.addRuntimeModule(chunk, new ShareRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.loadScript) + .tap("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { + if (compilation.outputOptions.trustedTypes) { + set.add(RuntimeGlobals.getTrustedTypesPolicy); + } + compilation.addRuntimeModule(chunk, new CreateScriptRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.createScriptUrl) + .tap("RuntimePlugin", (chunk, set) => { + if (compilation.outputOptions.trustedTypes) { + set.add(RuntimeGlobals.getTrustedTypesPolicy); + } + compilation.addRuntimeModule( + chunk, + new CreateScriptUrlRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.getTrustedTypesPolicy) + .tap("RuntimePlugin", (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new GetTrustedTypesPolicyRuntimeModule(set) + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.relativeUrl) + .tap("RuntimePlugin", (chunk, set) => { + compilation.addRuntimeModule(chunk, new RelativeUrlRuntimeModule()); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.onChunksLoaded) + .tap("RuntimePlugin", (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new OnChunksLoadedRuntimeModule() + ); + return true; + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.baseURI) + .tap("RuntimePlugin", chunk => { + if (isChunkLoadingDisabledForChunk(chunk)) { + compilation.addRuntimeModule(chunk, new BaseUriRuntimeModule()); + return true; + } + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.scriptNonce) + .tap("RuntimePlugin", chunk => { + compilation.addRuntimeModule(chunk, new NonceRuntimeModule()); + return true; + }); + // TODO webpack 6: remove CompatRuntimeModule + compilation.hooks.additionalTreeRuntimeRequirements.tap( + "RuntimePlugin", + (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( + "RuntimePlugin", + (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/webpack-lib/lib/RuntimeTemplate.js b/webpack-lib/lib/RuntimeTemplate.js new file mode 100644 index 00000000000..4f79d7137a6 --- /dev/null +++ b/webpack-lib/lib/RuntimeTemplate.js @@ -0,0 +1,1109 @@ +/* + 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 { equals } = require("./util/ArrayHelpers"); +const compileBooleanMatcher = require("./util/compileBooleanMatcher"); +const propertyAccess = require("./util/propertyAccess"); +const { forEachRuntime, subtractRuntime } = require("./util/runtime"); + +/** @typedef {import("../declarations/WebpackOptions").Environment} Environment */ +/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("./CodeGenerationResults").CodeGenerationResult} CodeGenerationResult */ +/** @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 */ + +/** + * @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 && + Array.from(connection.explanations).join(", ")) || + "" + }` +).join("")}`; + +/** + * @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 { + /** + * @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( + /** @type {NonNullable} */ + (outputOptions.hashDigestLength) + ); + } + + isIIFE() { + return this.outputOptions.iife; + } + + isModule() { + return this.outputOptions.module; + } + + isNeutralPlatform() { + return ( + !this.outputOptions.environment.document && + !this.compilation.compiler.platform.node + ); + } + + supportsConst() { + return this.outputOptions.environment.const; + } + + supportsArrowFunction() { + return this.outputOptions.environment.arrowFunction; + } + + supportsAsyncFunction() { + return this.outputOptions.environment.asyncFunction; + } + + supportsOptionalChaining() { + return this.outputOptions.environment.optionalChaining; + } + + 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; + } + + /** + * @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}; }`; + } + + /** + * @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}`; + } + + /** + * @param {Array} 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("")}\``; + } + + /** + * @param {Array} 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; + } + + /** + * @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 {string} empty function code + */ + emptyFunction() { + return this.supportsArrowFunction() ? "x => {}" : "function() {}"; + } + + /** + * @param {string[]} items items + * @param {string} value value + * @returns {string} destructure array code + */ + destructureArray(items, value) { + return this.supportsDestructuring() + ? `var [${items.join(", ")}] = ${value};` + : Template.asString( + items.map((item, i) => `var ${item} = ${value}[${i}];`) + ); + } + + /** + * @param {string[]} items items + * @param {string} value value + * @returns {string} destructure object code + */ + destructureObject(items, value) { + return this.supportsDestructuring() + ? `var {${items.join(", ")}} = ${value};` + : Template.asString( + items.map(item => `var ${item} = ${value}${propertyAccess([item])};`) + ); + } + + /** + * @param {string} args arguments + * @param {string} body body + * @returns {string} IIFE code + */ + iife(args, body) { + return `(${this.basicFunction(args, body)})()`; + } + + /** + * @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});`; + } + + /** + * Add a comment + * @param {object} options Information content of the comment + * @param {string=} options.request request string used originally + * @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 }) { + let content; + if (this.outputOptions.pathinfo) { + content = [message, request, chunkName, chunkReason] + .filter(Boolean) + .map(item => this.requestShortener.shorten(item)) + .join(" | "); + } else { + content = [message, chunkName, chunkReason] + .filter(Boolean) + .map(item => this.requestShortener.shorten(item)) + .join(" | "); + } + if (!content) return ""; + if (this.outputOptions.pathinfo) { + return `${Template.toComment(content)} `; + } + return `${Template.toNormalComment(content)} `; + } + + /** + * @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 `var e = new Error(${JSON.stringify( + err + )}); e.code = 'MODULE_NOT_FOUND'; throw e;`; + } + + /** + * @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 } + )} }`; + } + + /** + * @param {object} options generation options + * @param {string=} options.request request string used originally + * @returns {string} generated error IIFE + */ + missingModule({ request }) { + return `Object(${this.throwMissingModuleErrorFunction({ request })}())`; + } + + /** + * @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`; + } + + /** + * @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 + })})`; + } + + /** + * @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 = `var 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); + } + } + + /** + * @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 + }); + } + const moduleId = chunkGraph.getModuleId(module); + if (moduleId === null) { + if (weak) { + return "null /* weak dependency, without id */"; + } + throw new Error( + `RuntimeTemplate.moduleId(): ${noModuleIdErrorMessage( + module, + chunkGraph + )}` + ); + } + return `${this.comment({ request })}${JSON.stringify(moduleId)}`; + } + + /** + * @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 + }); + } + 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 + })})`; + } + + /** + * @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, + chunkGraph, + request, + weak, + runtimeRequirements + }); + } + + /** + * @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, + chunkGraph, + request, + weak + }); + 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)`; + } + } + + /** + * @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 {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements + * @returns {string} the promise expression + */ + moduleNamespacePromise({ + chunkGraph, + block, + module, + request, + message, + strict, + weak, + runtimeRequirements + }) { + if (!module) { + return this.missingModulePromise({ + 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: "promise" + }); + } + throw new Error( + `RuntimeTemplate.moduleNamespacePromise(): ${noModuleIdErrorMessage( + module, + chunkGraph + )}` + ); + } + const promise = this.blockPromise({ + chunkGraph, + block, + message, + runtimeRequirements + }); + + let appending; + let idExpr = JSON.stringify(chunkGraph.getModuleId(module)); + const comment = this.comment({ + request + }); + let header = ""; + if (weak) { + if (idExpr.length > 8) { + // 'var x="nnnnnn";x,"+x+",x' vs '"nnnnnn",nnnnnn,"nnnnnn"' + header += `var id = ${idExpr}; `; + idExpr = "id"; + } + runtimeRequirements.add(RuntimeGlobals.moduleFactories); + header += `if(!${ + RuntimeGlobals.moduleFactories + }[${idExpr}]) { ${this.weakError({ + module, + chunkGraph, + request, + idExpr, + type: "statements" + })} } `; + } + const moduleIdExpr = this.moduleId({ + module, + chunkGraph, + request, + weak + }); + const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict); + 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 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()"}${appending}`; + } + + /** + * @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( + Array.from(positiveRuntimeIds), + Array.from(negativeRuntimeIds) + )(RuntimeGlobals.runtimeId); + } + + /** + * @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 {ChunkGraph} options.chunkGraph the chunk graph + * @param {string} options.request the request that should be printed as comment + * @param {string} options.importVar name of the import variable + * @param {Module} options.originModule module in which the statement is emitted + * @param {boolean=} options.weak true, if this is a weak dependency + * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements + * @returns {[string, string]} the import statement and the compat statement + */ + importStatement({ + update, + module, + chunkGraph, + request, + importVar, + originModule, + weak, + runtimeRequirements + }) { + if (!module) { + 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, + chunkGraph, + request, + weak + }); + const optDeclaration = update ? "" : "var "; + + const exportsType = module.getExportsType( + chunkGraph.moduleGraph, + /** @type {BuildMeta} */ + (originModule.buildMeta).strictHarmonyModule + ); + runtimeRequirements.add(RuntimeGlobals.require); + const 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 [importContent, ""]; + } + + /** + * @param {object} options options + * @param {ModuleGraph} options.moduleGraph the module 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} 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 + * @returns {string} expression + */ + exportFromImport({ + moduleGraph, + module, + request, + exportName, + originModule, + asiSafe, + isCall, + callContext, + defaultInterop, + importVar, + initFragments, + runtime, + runtimeRequirements + }) { + if (!module) { + return this.missingModule({ + request + }); + } + if (!Array.isArray(exportName)) { + exportName = exportName ? [exportName] : []; + } + const exportsType = module.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ + (originModule.buildMeta).strictHarmonyModule + ); + + if (defaultInterop) { + if (exportName.length > 0 && exportName[0] === "default") { + 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) { + if (exportsType === "default-only") { + return `/* non-default import from non-esm module */undefined${propertyAccess( + exportName, + 1 + )}`; + } else if ( + exportsType !== "namespace" && + exportName[0] === "__esModule" + ) { + return "/* __esModule */true"; + } + } else if ( + exportsType === "default-only" || + exportsType === "default-with-named" + ) { + runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); + initFragments.push( + new InitFragment( + `var ${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 (exportName.length > 0) { + const exportsInfo = moduleGraph.getExportsInfo(module); + const used = exportsInfo.getUsedName(exportName, runtime); + if (!used) { + const comment = Template.toNormalComment( + `unused export ${propertyAccess(exportName)}` + ); + return `${comment} undefined`; + } + const comment = equals(used, exportName) + ? "" + : `${Template.toNormalComment(propertyAccess(exportName))} `; + const access = `${importVar}${comment}${propertyAccess(used)}`; + if (isCall && callContext === false) { + return asiSafe + ? `(0,${access})` + : asiSafe === false + ? `;(0,${access})` + : `/*#__PURE__*/Object(${access})`; + } + return access; + } + return importVar; + } + + /** + * @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 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 + }); + if (chunks.length === 1) { + const chunkId = JSON.stringify(chunks[0].id); + 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) { + runtimeRequirements.add(RuntimeGlobals.ensureChunk); + + const fetchPriority = chunkGroup.options.fetchPriority; + + if (fetchPriority) { + runtimeRequirements.add(RuntimeGlobals.hasFetchPriority); + } + + /** + * @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(", ")}])`; + } + return `Promise.resolve(${comment.trim()})`; + } + + /** + * @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)})` + ); + } + + /** + * @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); + } + + /** + * @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/webpack-lib/lib/SelfModuleFactory.js b/webpack-lib/lib/SelfModuleFactory.js new file mode 100644 index 00000000000..3a10333e20c --- /dev/null +++ b/webpack-lib/lib/SelfModuleFactory.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./ModuleGraph")} ModuleGraph */ + +class SelfModuleFactory { + /** + * @param {ModuleGraph} moduleGraph module graph + */ + constructor(moduleGraph) { + this.moduleGraph = moduleGraph; + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const module = this.moduleGraph.getParentModule(data.dependencies[0]); + callback(null, { + module + }); + } +} + +module.exports = SelfModuleFactory; diff --git a/webpack-lib/lib/SingleEntryPlugin.js b/webpack-lib/lib/SingleEntryPlugin.js new file mode 100644 index 00000000000..65791735c79 --- /dev/null +++ b/webpack-lib/lib/SingleEntryPlugin.js @@ -0,0 +1,8 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +module.exports = require("./EntryPlugin"); diff --git a/webpack-lib/lib/SizeFormatHelpers.js b/webpack-lib/lib/SizeFormatHelpers.js new file mode 100644 index 00000000000..4c6a748f0eb --- /dev/null +++ b/webpack-lib/lib/SizeFormatHelpers.js @@ -0,0 +1,25 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +/** + * @param {number} size the size in bytes + * @returns {string} the formatted size + */ +module.exports.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 `${Number((size / 1024 ** index).toPrecision(3))} ${abbreviations[index]}`; +}; diff --git a/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js b/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js new file mode 100644 index 00000000000..e7d722e12a8 --- /dev/null +++ b/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js @@ -0,0 +1,61 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); + +/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ +/** @typedef {import("./Compilation")} Compilation */ + +class SourceMapDevToolModuleOptionsPlugin { + /** + * @param {SourceMapDevToolPluginOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * @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; + } + ); + compilation.hooks.runtimeModule.tap( + "SourceMapDevToolModuleOptionsPlugin", + module => { + module.useSourceMap = true; + } + ); + } else { + compilation.hooks.buildModule.tap( + "SourceMapDevToolModuleOptionsPlugin", + module => { + module.useSimpleSourceMap = true; + } + ); + compilation.hooks.runtimeModule.tap( + "SourceMapDevToolModuleOptionsPlugin", + module => { + module.useSimpleSourceMap = true; + } + ); + } + JavascriptModulesPlugin.getCompilationHooks(compilation).useSourceMap.tap( + "SourceMapDevToolModuleOptionsPlugin", + () => true + ); + } +} + +module.exports = SourceMapDevToolModuleOptionsPlugin; diff --git a/webpack-lib/lib/SourceMapDevToolPlugin.js b/webpack-lib/lib/SourceMapDevToolPlugin.js new file mode 100644 index 00000000000..ca16afd0f8b --- /dev/null +++ b/webpack-lib/lib/SourceMapDevToolPlugin.js @@ -0,0 +1,618 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +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 createSchemaValidation = require("./util/create-schema-validation"); +const createHash = require("./util/createHash"); +const { relative, dirname } = 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/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ +/** @typedef {import("./Cache").Etag} Etag */ +/** @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").SourceMap} SourceMap */ +/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("./util/Hash")} Hash */ +/** @typedef {import("./util/createHash").Algorithm} Algorithm */ +/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */ + +const validate = createSchemaValidation( + require("../schemas/plugins/SourceMapDevToolPlugin.check.js"), + () => require("../schemas/plugins/SourceMapDevToolPlugin.json"), + { + name: "SourceMap DevTool Plugin", + baseDataPath: "options" + } +); +/** + * @typedef {object} SourceMapTask + * @property {Source} asset + * @property {AssetInfo} assetInfo + * @property {(string | Module)[]} modules + * @property {string} source + * @property {string} file + * @property {SourceMap} sourceMap + * @property {ItemCacheFacade} cacheItem cache item + */ + +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; +}; + +/** + * Escapes regular expression metacharacters + * @param {string} str String to quote + * @returns {string} Escaped string + */ +const quoteMeta = str => str.replace(METACHARACTERS_REGEXP, "\\$&"); + +/** + * 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 + * @returns {SourceMapTask | undefined} created task instance or `undefined` + */ +const getTaskForFile = ( + file, + asset, + assetInfo, + options, + compilation, + cacheItem +) => { + let source; + /** @type {SourceMap} */ + let sourceMap; + /** + * Check if asset can build source map + */ + if (asset.sourceAndMap) { + const sourceAndMap = asset.sourceAndMap(options); + sourceMap = /** @type {SourceMap} */ (sourceAndMap.map); + source = sourceAndMap.source; + } else { + sourceMap = /** @type {SourceMap} */ (asset.map(options)); + source = asset.source(); + } + if (!sourceMap || typeof source !== "string") return; + const context = /** @type {string} */ (compilation.options.context); + const root = compilation.compiler.root; + const cachedAbsolutify = makePathsAbsolute.bindContextCache(context, root); + const modules = sourceMap.sources.map(source => { + if (!source.startsWith("webpack://")) return source; + source = cachedAbsolutify(source.slice(10)); + const module = compilation.findModule(source); + return module || source; + }); + + return { + file, + asset, + source, + assetInfo, + sourceMap, + modules, + cacheItem + }; +}; + +class SourceMapDevToolPlugin { + /** + * @param {SourceMapDevToolPluginOptions} [options] options object + * @throws {Error} throws error, if got more than 1 arguments + */ + constructor(options = {}) { + validate(options); + + this.sourceMapFilename = /** @type {string | false} */ (options.filename); + /** @type {false | TemplatePath}} */ + this.sourceMappingURLComment = + options.append === false + ? false + : // eslint-disable-next-line no-useless-concat + options.append || "\n//# source" + "MappingURL=[url]"; + /** @type {string | Function} */ + this.moduleFilenameTemplate = + options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]"; + /** @type {string | Function} */ + this.fallbackModuleFilenameTemplate = + options.fallbackModuleFilenameTemplate || + "webpack://[namespace]/[resourcePath]?[hash]"; + /** @type {string} */ + this.namespace = options.namespace || ""; + /** @type {SourceMapDevToolPluginOptions} */ + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler compiler instance + * @returns {void} + */ + apply(compiler) { + const outputFs = /** @type {OutputFileSystem} */ ( + compiler.outputFileSystem + ); + const sourceMapFilename = this.sourceMapFilename; + const sourceMappingURLComment = this.sourceMappingURLComment; + const moduleFilenameTemplate = this.moduleFilenameTemplate; + const namespace = this.namespace; + const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate; + const requestShortener = compiler.requestShortener; + const options = this.options; + options.test = options.test || CSS_AND_JS_MODULE_EXTENSIONS_REGEXP; + + const matchObject = ModuleFilenameHelpers.matchObject.bind( + undefined, + options + ); + + compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => { + new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation); + + compilation.hooks.processAssets.tapAsync( + { + name: "SourceMapDevToolPlugin", + stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, + additionalAssets: true + }, + (assets, callback) => { + const chunkGraph = compilation.chunkGraph; + const cache = compilation.getCache("SourceMapDevToolPlugin"); + /** @type {Map} */ + const moduleToSourceNameMapping = new Map(); + /** + * @type {Function} + * @returns {void} + */ + const reportProgress = + ProgressPlugin.getReporter(compilation.compiler) || (() => {}); + + /** @type {Map} */ + const fileToChunk = new Map(); + for (const chunk of compilation.chunks) { + for (const file of chunk.files) { + 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); + /** @type {SourceMapTask[]} */ + const tasks = []; + let fileIndex = 0; + + asyncLib.each( + files, + (file, callback) => { + const asset = + /** @type {Readonly} */ + (compilation.getAsset(file)); + if (asset.info.related && asset.info.related.sourceMap) { + fileIndex++; + return callback(); + } + + const chunk = fileToChunk.get(file); + const sourceMapNamespace = compilation.getPath(this.namespace, { + chunk + }); + + const cacheItem = cache.getItemCache( + file, + 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) { + 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(); + } + + reportProgress( + (0.5 * fileIndex) / files.length, + file, + "generate SourceMap" + ); + + /** @type {SourceMapTask | undefined} */ + const task = getTaskForFile( + file, + asset.source, + asset.info, + { + module: options.module, + columns: options.columns + }, + compilation, + cacheItem + ); + + if (task) { + const modules = task.modules; + + 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(); + }); + }, + 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 {Array} + */ + 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; + }); + + // 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); + } + + let taskIndex = 0; + + asyncLib.each( + tasks, + (task, callback) => { + const assets = Object.create(null); + 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 = modules.map(m => + moduleToSourceNameMapping.get(m) + ); + sourceMap.sources = /** @type {string[]} */ (moduleFilenames); + if (options.noSources) { + sourceMap.sourcesContent = undefined; + } + sourceMap.sourceRoot = options.sourceRoot || ""; + sourceMap.file = file; + const usesContentHash = + sourceMapFilename && + CONTENT_HASH_DETECT_REGEXP.test(sourceMapFilename); + + resetRegexpState(CONTENT_HASH_DETECT_REGEXP); + + // 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); + sourceMap.file = sourceMap.file.replace( + new RegExp(pattern, "g"), + m => "x".repeat(m.length) + ); + } + + /** @type {false | TemplatePath} */ + 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*/" + ); + } + + if (options.debugIds) { + const debugId = generateDebugId(source, sourceMap.file); + sourceMap.debugId = debugId; + currentSourceMappingURLComment = `\n//# debugId=${debugId}${currentSourceMappingURLComment}`; + } + + const sourceMapString = JSON.stringify(sourceMap); + if (sourceMapFilename) { + const filename = file; + const sourceMapContentHash = + /** @type {string} */ + ( + usesContentHash && + createHash( + /** @type {Algorithm} */ + (compilation.outputOptions.hashFunction) + ) + .update(sourceMapString) + .digest("hex") + ); + 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 + }) + ); + } + const assetInfo = { + related: { sourceMap: sourceMapFile } + }; + 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(sourceMapString); + 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( + "SourceMapDevToolPlugin: append can't be false when no filename is provided" + ); + } + if (typeof currentSourceMappingURLComment === "function") { + throw new Error( + "SourceMapDevToolPlugin: append can't be a function when no filename is provided" + ); + } + /** + * 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,${Buffer.from( + sourceMapString, + "utf-8" + ).toString("base64")}` + ) + ); + 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); + } + ); + } + ); + } + ); + }); + } +} + +module.exports = SourceMapDevToolPlugin; diff --git a/webpack-lib/lib/Stats.js b/webpack-lib/lib/Stats.js new file mode 100644 index 00000000000..22a36632a97 --- /dev/null +++ b/webpack-lib/lib/Stats.js @@ -0,0 +1,89 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ +/** @typedef {import("./Compilation")} Compilation */ +/** @typedef {import("./Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ + +class Stats { + /** + * @param {Compilation} compilation webpack compilation + */ + constructor(compilation) { + this.compilation = compilation; + } + + get hash() { + return this.compilation.hash; + } + + get startTime() { + return this.compilation.startTime; + } + + get endTime() { + return this.compilation.endTime; + } + + /** + * @returns {boolean} true if the compilation had a warning + */ + hasWarnings() { + return ( + this.compilation.getWarnings().length > 0 || + this.compilation.children.some(child => child.getStats().hasWarnings()) + ); + } + + /** + * @returns {boolean} true if the compilation encountered an error + */ + hasErrors() { + return ( + this.compilation.errors.length > 0 || + this.compilation.children.some(child => child.getStats().hasErrors()) + ); + } + + /** + * @param {(string | boolean | StatsOptions)=} options stats options + * @returns {StatsCompilation} json output + */ + toJson(options) { + const normalizedOptions = this.compilation.createStatsOptions(options, { + forToString: false + }); + + const statsFactory = this.compilation.createStatsFactory(normalizedOptions); + + return statsFactory.create("compilation", this.compilation, { + compilation: this.compilation + }); + } + + /** + * @param {(string | boolean | StatsOptions)=} options stats options + * @returns {string} string output + */ + toString(options) { + const normalizedOptions = this.compilation.createStatsOptions(options, { + forToString: true + }); + + const statsFactory = this.compilation.createStatsFactory(normalizedOptions); + const statsPrinter = this.compilation.createStatsPrinter(normalizedOptions); + + const data = statsFactory.create("compilation", this.compilation, { + compilation: this.compilation + }); + const result = statsPrinter.print("compilation", data); + return result === undefined ? "" : result; + } +} + +module.exports = Stats; diff --git a/webpack-lib/lib/Template.js b/webpack-lib/lib/Template.js new file mode 100644 index 00000000000..3b95cfc35b5 --- /dev/null +++ b/webpack-lib/lib/Template.js @@ -0,0 +1,415 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"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("../declarations/WebpackOptions").Output} 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("./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; +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 COMMENT_END_REGEX = /\*\//g; +const PATH_NAME_NORMALIZE_REPLACE_REGEX = /[^a-zA-Z0-9_!§$()=\-^°]+/g; +const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g; + +/** + * @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 */ + +/** + * @typedef {object} RenderManifestEntryTemplated + * @property {function(): Source} render + * @property {TemplatePath} filenameTemplate + * @property {PathData=} pathOptions + * @property {AssetInfo=} info + * @property {string} identifier + * @property {string=} hash + * @property {boolean=} auxiliary + */ + +/** + * @typedef {object} RenderManifestEntryStatic + * @property {function(): Source} render + * @property {string} filename + * @property {AssetInfo} info + * @property {string} identifier + * @property {string=} hash + * @property {boolean=} auxiliary + */ + +/** + * @typedef {object} HasId + * @property {number | string} id + */ + +/** + * @typedef {function(Module, number): boolean} ModuleFilterPredicate + */ + +class Template { + /** + * @param {Function} fn a runtime function (.runtime.js) "template" + * @returns {string} the updated and normalized function string + */ + static getFunctionContent(fn) { + return fn + .toString() + .replace(FUNCTION_CONTENT_REGEX, "") + .replace(INDENT_MULTILINE_REGEX, "") + .replace(LINE_SEPARATOR_REGEX, "\n"); + } + + /** + * @param {string} str the string converted to identifier + * @returns {string} created identifier + */ + static toIdentifier(str) { + if (typeof str !== "string") return ""; + return str + .replace(IDENTIFIER_NAME_REPLACE_REGEX, "_$1") + .replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_"); + } + + /** + * @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, "* /")} */`; + } + + /** + * @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, "* /")} */`; + } + + /** + * @param {string} str string path to be normalized + * @returns {string} normalized bundle-safe path + */ + static toPath(str) { + if (typeof str !== "string") return ""; + return str + .replace(PATH_NAME_NORMALIZE_REPLACE_REGEX, "-") + .replace(MATCH_PADDED_HYPHENS_REPLACE_REGEX, ""); + } + + // map number to a single character a-z, A-Z or multiple characters if number is too big + /** + * @param {number} n number to convert to ident + * @returns {string} returns single character ident + */ + 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) { + return String.fromCharCode(START_UPPERCASE_ALPHABET_CODE + n); + } + + if (n === DELTA_A_TO_Z) return "_"; + return "$"; + } + + /** + * @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) + ) + ); + } + + // 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[]} s string to convert to identity + * @returns {string} converted identity + */ + 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[]} s string to create prefix for + * @param {string} prefix prefix to compose + * @returns {string} returns new prefix string + */ + 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`); + } + + /** + * @param {string|string[]} str string or string collection + * @returns {string} returns a single string from array + */ + static asString(str) { + if (Array.isArray(str)) { + return str.join("\n"); + } + return str; + } + + /** + * @typedef {object} WithId + * @property {string|number} id + */ + + /** + * @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) { + let maxId = -Infinity; + let minId = Infinity; + for (const module of modules) { + const moduleId = module.id; + if (typeof moduleId !== "number") return false; + if (maxId < moduleId) maxId = moduleId; + if (minId > moduleId) minId = moduleId; + } + if (minId < 16 + String(minId).length) { + // add minId x ',' instead of 'Array(minId).concat(…)' + minId = 0; + } + // 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 {ChunkRenderContext} renderContext render context + * @param {Module[]} modules modules to render (should be ordered by identifier) + * @param {function(Module): Source | null} renderModule function to render a module + * @param {string=} prefix applying prefix strings + * @returns {Source | null} rendered chunk modules in a Source object or null if no modules + */ + static renderChunkModules(renderContext, modules, renderModule, prefix = "") { + const { chunkGraph } = renderContext; + const source = new ConcatSource(); + if (modules.length === 0) { + return null; + } + /** @type {{id: string|number, source: Source|string}[]} */ + const allModules = modules.map(module => ({ + id: /** @type {ModuleId} */ (chunkGraph.getModuleId(module)), + source: renderModule(module) || "false" + })); + const bounds = Template.getModulesArrayBounds(allModules); + if (bounds) { + // Render a spare array + 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 (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(")"); + } + } else { + // Render an object + source.add("{\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)}:\n`); + source.add(module.source); + } + source.add(`\n\n${prefix}}`); + } + return source; + } + + /** + * @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; + 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, + 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; + } + + /** + * @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_START_CHARS = + NUMBER_OF_IDENTIFIER_START_CHARS; +module.exports.NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS = + NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS; diff --git a/webpack-lib/lib/TemplatedPathPlugin.js b/webpack-lib/lib/TemplatedPathPlugin.js new file mode 100644 index 00000000000..e7cc5b9442a --- /dev/null +++ b/webpack-lib/lib/TemplatedPathPlugin.js @@ -0,0 +1,395 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Jason Anderson @diurnalist +*/ + +"use strict"; + +const mime = require("mime-types"); +const { basename, extname } = require("path"); +const util = require("util"); +const Chunk = require("./Chunk"); +const Module = require("./Module"); +const { parseResource } = require("./util/identifier"); + +/** @typedef {import("./ChunkGraph")} ChunkGraph */ +/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Compilation").PathData} PathData */ +/** @typedef {import("./Compiler")} Compiler */ + +const REGEXP = /\[\\*([\w:]+)\\*\]/gi; + +/** + * @param {string | number} id id + * @returns {string | number} result + */ +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-zA-Z0-9_-])+/g, "_"); +}; + +/** + * @callback ReplacerFunction + * @param {string} match + * @param {string | undefined} arg + * @param {string} input + */ + +/** + * @param {ReplacerFunction} replacer replacer + * @param {((arg0: number) => string) | undefined} handler handler + * @param {AssetInfo | undefined} assetInfo asset info + * @param {string} hashName hash name + * @returns {ReplacerFunction} hash replacer function + */ +const hashLength = (replacer, handler, assetInfo, hashName) => { + /** @type {ReplacerFunction} */ + const fn = (match, arg, input) => { + 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; +}; + +/** @typedef {(match: string, arg?: string, input?: string) => string} 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 ""; + } + + return `${value}`; + }; + + return fn; +}; + +const deprecationCache = new Map(); +const deprecatedFunction = (() => () => {})(); +/** + * @param {Function} fn function + * @param {string} message message + * @param {string} code code + * @returns {function(...any[]): void} 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 (...args) => { + d(); + return fn(...args); + }; +}; + +/** @typedef {string | function(PathData, AssetInfo=): string} TemplatePath */ + +/** + * @param {TemplatePath} path the raw path + * @param {PathData} data context data + * @param {AssetInfo | undefined} assetInfo extra info about the asset (will be written to) + * @returns {string} the interpolated path + */ +const replacePathVariables = (path, data, assetInfo) => { + 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 (typeof data.filename === "string") { + // check that filename is data uri + const match = data.filename.match(/^data:([^;,]+)/); + if (match) { + const ext = mime.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; + + replacements.set("file", emptyReplacer); + replacements.set("query", emptyReplacer); + replacements.set("fragment", emptyReplacer); + replacements.set("path", emptyReplacer); + replacements.set("base", baseReplacer); + replacements.set("name", baseReplacer); + replacements.set("ext", replacer(ext ? `.${ext}` : "", true)); + // Legacy + 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); + + replacements.set("file", replacer(file)); + replacements.set("query", replacer(query, true)); + replacements.set("fragment", replacer(fragment, true)); + replacements.set("path", replacer(path, true)); + replacements.set("base", replacer(base)); + replacements.set("name", replacer(name)); + replacements.set("ext", replacer(ext, true)); + // Legacy + 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) { + const hashReplacer = hashLength( + replacer(data.hash), + data.hashWithLength, + assetInfo, + "fullhash" + ); + + replacements.set("fullhash", hashReplacer); + + // Legacy + 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" + ) + ); + } + + // 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; + + const contentHashType = data.contentHashType; + + const idReplacer = replacer(chunk.id); + const nameReplacer = replacer(chunk.name || chunk.id); + const chunkhashReplacer = hashLength( + replacer(chunk instanceof Chunk ? chunk.renderedHash : chunk.hash), + "hashWithLength" in chunk ? chunk.hashWithLength : undefined, + assetInfo, + "chunkhash" + ); + const contenthashReplacer = 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" + ); + + replacements.set("id", idReplacer); + replacements.set("name", nameReplacer); + replacements.set("chunkhash", chunkhashReplacer); + replacements.set("contenthash", contenthashReplacer); + } + + // 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 idReplacer = replacer(() => + prepareId( + module instanceof Module + ? /** @type {ModuleId} */ + (/** @type {ChunkGraph} */ (chunkGraph).getModuleId(module)) + : module.id + ) + ); + const moduleHashReplacer = hashLength( + replacer(() => + module instanceof Module + ? /** @type {ChunkGraph} */ + (chunkGraph).getRenderedModuleHash(module, data.runtime) + : module.hash + ), + "hashWithLength" in module ? module.hashWithLength : undefined, + assetInfo, + "modulehash" + ); + const contentHashReplacer = hashLength( + replacer(/** @type {string} */ (data.contentHash)), + undefined, + assetInfo, + "contenthash" + ); + + replacements.set("id", idReplacer); + replacements.set("modulehash", moduleHashReplacer); + replacements.set("contenthash", contentHashReplacer); + replacements.set( + "hash", + data.contentHash ? contentHashReplacer : moduleHashReplacer + ); + // Legacy + replacements.set( + "moduleid", + deprecated( + idReplacer, + "[moduleid] is now [id]", + "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_ID" + ) + ); + } + + // Other things + if (data.url) { + replacements.set("url", replacer(data.url)); + } + if (typeof data.runtime === "string") { + replacements.set( + "runtime", + replacer(() => prepareId(/** @type {string} */ (data.runtime))) + ); + } else { + replacements.set("runtime", replacer("_")); + } + + if (typeof path === "function") { + path = path(data, assetInfo); + } + + 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, path); + } + } else if (match.startsWith("[\\") && match.endsWith("\\]")) { + return `[${match.slice(2, -2)}]`; + } + return match; + }); + + return path; +}; + +const plugin = "TemplatedPathPlugin"; + +class TemplatedPathPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap(plugin, compilation => { + compilation.hooks.assetPath.tap(plugin, replacePathVariables); + }); + } +} + +module.exports = TemplatedPathPlugin; diff --git a/webpack-lib/lib/UnhandledSchemeError.js b/webpack-lib/lib/UnhandledSchemeError.js new file mode 100644 index 00000000000..80fa07af188 --- /dev/null +++ b/webpack-lib/lib/UnhandledSchemeError.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +class UnhandledSchemeError extends WebpackError { + /** + * @param {string} scheme scheme + * @param {string} resource resource + */ + constructor(scheme, resource) { + super( + `Reading from "${resource}" is not handled by plugins (Unhandled scheme).` + + '\nWebpack supports "data:" and "file:" URIs by default.' + + `\nYou may need an additional plugin to handle "${scheme}:" URIs.` + ); + this.file = resource; + this.name = "UnhandledSchemeError"; + } +} + +makeSerializable( + UnhandledSchemeError, + "webpack/lib/UnhandledSchemeError", + "UnhandledSchemeError" +); + +module.exports = UnhandledSchemeError; diff --git a/webpack-lib/lib/UnsupportedFeatureWarning.js b/webpack-lib/lib/UnsupportedFeatureWarning.js new file mode 100644 index 00000000000..2c59f4a80a8 --- /dev/null +++ b/webpack-lib/lib/UnsupportedFeatureWarning.js @@ -0,0 +1,32 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("./WebpackError"); +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ + +class UnsupportedFeatureWarning extends WebpackError { + /** + * @param {string} message description of warning + * @param {DependencyLocation} loc location start and end positions of the module + */ + constructor(message, loc) { + super(message); + + this.name = "UnsupportedFeatureWarning"; + this.loc = loc; + this.hideStack = true; + } +} + +makeSerializable( + UnsupportedFeatureWarning, + "webpack/lib/UnsupportedFeatureWarning" +); + +module.exports = UnsupportedFeatureWarning; diff --git a/webpack-lib/lib/UseStrictPlugin.js b/webpack-lib/lib/UseStrictPlugin.js new file mode 100644 index 00000000000..3db3daa8f62 --- /dev/null +++ b/webpack-lib/lib/UseStrictPlugin.js @@ -0,0 +1,81 @@ +/* + 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("../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 { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + /** + * @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 && + firstNode.type === "ExpressionStatement" && + firstNode.expression.type === "Literal" && + firstNode.expression.value === "use strict" + ) { + // 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( + "", + /** @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_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 = UseStrictPlugin; diff --git a/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js b/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js new file mode 100644 index 00000000000..11af42a590f --- /dev/null +++ b/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js @@ -0,0 +1,65 @@ +/* + 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("./NormalModule")} NormalModule */ + +class WarnCaseSensitiveModulesPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "WarnCaseSensitiveModulesPlugin", + compilation => { + compilation.hooks.seal.tap("WarnCaseSensitiveModulesPlugin", () => { + /** @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; + } + + 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 + ) + ); + } + } + }); + } + ); + } +} + +module.exports = WarnCaseSensitiveModulesPlugin; diff --git a/webpack-lib/lib/WarnDeprecatedOptionPlugin.js b/webpack-lib/lib/WarnDeprecatedOptionPlugin.js new file mode 100644 index 00000000000..8cad0869908 --- /dev/null +++ b/webpack-lib/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("./WebpackError"); + +/** @typedef {import("./Compiler")} Compiler */ + +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; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "WarnDeprecatedOptionPlugin", + 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(); + + this.name = "DeprecatedOptionWarning"; + this.message = + "configuration\n" + + `The value '${value}' for option '${option}' is deprecated. ` + + `Use '${suggestion}' instead.`; + } +} + +module.exports = WarnDeprecatedOptionPlugin; diff --git a/webpack-lib/lib/WarnNoModeSetPlugin.js b/webpack-lib/lib/WarnNoModeSetPlugin.js new file mode 100644 index 00000000000..b8685f03990 --- /dev/null +++ b/webpack-lib/lib/WarnNoModeSetPlugin.js @@ -0,0 +1,25 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NoModeWarning = require("./NoModeWarning"); + +/** @typedef {import("./Compiler")} Compiler */ + +class WarnNoModeSetPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap("WarnNoModeSetPlugin", compilation => { + compilation.warnings.push(new NoModeWarning()); + }); + } +} + +module.exports = WarnNoModeSetPlugin; diff --git a/webpack-lib/lib/WatchIgnorePlugin.js b/webpack-lib/lib/WatchIgnorePlugin.js new file mode 100644 index 00000000000..a7471e9c347 --- /dev/null +++ b/webpack-lib/lib/WatchIgnorePlugin.js @@ -0,0 +1,153 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { groupBy } = require("./util/ArrayHelpers"); +const createSchemaValidation = require("./util/create-schema-validation"); + +/** @typedef {import("../declarations/plugins/WatchIgnorePlugin").WatchIgnorePluginOptions} WatchIgnorePluginOptions */ +/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */ +/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */ +/** @typedef {import("./util/fs").WatchMethod} WatchMethod */ +/** @typedef {import("./util/fs").Watcher} Watcher */ +const validate = createSchemaValidation( + require("../schemas/plugins/WatchIgnorePlugin.check.js"), + () => require("../schemas/plugins/WatchIgnorePlugin.json"), + { + name: "Watch Ignore Plugin", + baseDataPath: "options" + } +); + +const IGNORE_TIME_ENTRY = "ignore"; + +class 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) { + files = Array.from(files); + dirs = Array.from(dirs); + /** + * @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 [ignoredFiles, notIgnoredFiles] = groupBy( + /** @type {Array} */ + (files), + ignored + ); + const [ignoredDirs, notIgnoredDirs] = groupBy( + /** @type {Array} */ + (dirs), + ignored + ); + + const watcher = this.wfs.watch( + notIgnoredFiles, + notIgnoredDirs, + missing, + startTime, + options, + (err, fileTimestamps, dirTimestamps, changedFiles, removedFiles) => { + if (err) return callback(err); + for (const path of ignoredFiles) { + /** @type {TimeInfoEntries} */ + (fileTimestamps).set(path, IGNORE_TIME_ENTRY); + } + + for (const path of ignoredDirs) { + /** @type {TimeInfoEntries} */ + (dirTimestamps).set(path, IGNORE_TIME_ENTRY); + } + + callback( + null, + fileTimestamps, + dirTimestamps, + changedFiles, + removedFiles + ); + }, + callbackUndelayed + ); + + return { + close: () => watcher.close(), + pause: () => watcher.pause(), + getContextTimeInfoEntries: () => { + const dirTimestamps = watcher.getContextTimeInfoEntries(); + for (const path of ignoredDirs) { + dirTimestamps.set(path, IGNORE_TIME_ENTRY); + } + return dirTimestamps; + }, + getFileTimeInfoEntries: () => { + const fileTimestamps = watcher.getFileTimeInfoEntries(); + for (const path of ignoredFiles) { + 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; + }) + }; + } +} + +class WatchIgnorePlugin { + /** + * @param {WatchIgnorePluginOptions} options options + */ + constructor(options) { + validate(options); + this.paths = options.paths; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.afterEnvironment.tap("WatchIgnorePlugin", () => { + compiler.watchFileSystem = new IgnoringWatchFileSystem( + /** @type {WatchFileSystem} */ + (compiler.watchFileSystem), + this.paths + ); + }); + } +} + +module.exports = WatchIgnorePlugin; diff --git a/webpack-lib/lib/Watching.js b/webpack-lib/lib/Watching.js new file mode 100644 index 00000000000..a047f257b20 --- /dev/null +++ b/webpack-lib/lib/Watching.js @@ -0,0 +1,528 @@ +/* + 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("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ +/** @typedef {import("./WebpackError")} WebpackError */ +/** @typedef {import("./logging/Logger").Logger} Logger */ +/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */ +/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */ +/** @typedef {import("./util/fs").Watcher} Watcher */ + +/** + * @template T + * @callback Callback + * @param {Error | null} err + * @param {T=} result + */ + +class Watching { + /** + * @param {Compiler} compiler the compiler + * @param {WatchOptions} watchOptions options + * @param {Callback} handler completion handler + */ + constructor(compiler, watchOptions, handler) { + this.startTime = null; + this.invalid = false; + this.handler = handler; + /** @type {Callback[]} */ + this.callbacks = []; + /** @type {Callback[] | 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") { + /** @type {WatchOptions} */ + this.watchOptions = { ...watchOptions }; + } else { + /** @type {WatchOptions} */ + this.watchOptions = {}; + } + if (typeof this.watchOptions.aggregateTimeout !== "number") { + this.watchOptions.aggregateTimeout = 20; + } + this.compiler = compiler; + this.running = false; + this._initial = true; + this._invalidReported = true; + this._needRecords = true; + this.watcher = undefined; + this.pausedWatcher = undefined; + /** @type {Set | undefined} */ + this._collectedChangedFiles = undefined; + /** @type {Set | undefined} */ + this._collectedRemovedFiles = undefined; + this._done = this._done.bind(this); + process.nextTick(() => { + if (this._initial) this._invalidate(); + }); + } + + /** + * @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 {Set} */ + (this._collectedRemovedFiles).delete(file); + } + for (const file of /** @type {ReadonlySet} */ (removedFiles)) { + this._collectedChangedFiles.delete(file); + /** @type {Set} */ + (this._collectedRemovedFiles).add(file); + } + } + } + + /** + * @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; + 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); + /** + * @param {Error | null} err error + * @param {Compilation=} _compilation compilation + * @returns {void} + */ + const onCompiled = (err, _compilation) => { + if (err) return this._done(err, _compilation); + + const compilation = /** @type {Compilation} */ (_compilation); + + if (this.invalid) return this._done(null, compilation); + + 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); + + 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; + + 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, compilation); + this.compiler.compile(onCompiled); + }); + }); + return; + } + return this._done(null, compilation); + }); + }); + }); + }; + this.compiler.compile(onCompiled); + }); + }; + + run(); + } + + /** + * @param {Compilation} compilation the compilation + * @returns {Stats} the compilation stats + */ + _getStats(compilation) { + const stats = new Stats(compilation); + return stats; + } + + /** + * @param {(Error | null)=} err an optional error + * @param {Compilation=} compilation the compilation + * @returns {void} + */ + _done(err, compilation) { + this.running = false; + + const logger = + /** @type {Logger} */ + (compilation && compilation.getLogger("webpack.Watching")); + + /** @type {Stats | undefined} */ + let stats; + + /** + * @param {Error} err error + * @param {Callback[]=} cbs callbacks + */ + const handleError = (err, cbs) => { + this.compiler.hooks.failed.call(err); + 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; + } + + 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); + 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)); + } + ); + }); + } + + /** + * @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 = + /** @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(); + } + ); + } + + /** + * @param {Callback=} callback signals when the build has completed again + * @returns {void} + */ + invalidate(callback) { + if (callback) { + this.callbacks.push(callback); + } + if (!this._invalidReported) { + this._invalidReported = true; + this.compiler.hooks.invalid.call(null, Date.now()); + } + this._onChange(); + this._invalidate(); + } + + /** + * @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; + } else { + this._go( + fileTimeInfoEntries, + contextTimeInfoEntries, + changedFiles, + removedFiles + ); + } + } + + suspend() { + this.suspended = true; + } + + resume() { + if (this.suspended) { + this.suspended = false; + this._invalidate(); + } + } + + /** + * @param {Callback} callback signals when the watcher is closed + * @returns {void} + */ + close(callback) { + if (this._closeCallbacks) { + if (callback) { + this._closeCallbacks.push(callback); + } + return; + } + /** + * @param {WebpackError | null} err error if any + * @param {Compilation=} compilation compilation if any + */ + const finalCallback = (err, compilation) => { + this.running = false; + this.compiler.running = false; + 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; + /** + * @param {WebpackError | null} err error if any + */ + const shutdown = err => { + this.compiler.hooks.watchClose.call(); + const closeCallbacks = + /** @type {Callback[]} */ + (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; + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + if (this.pausedWatcher) { + 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(null); + } + } +} + +module.exports = Watching; diff --git a/webpack-lib/lib/WebpackError.js b/webpack-lib/lib/WebpackError.js new file mode 100644 index 00000000000..d20d816b246 --- /dev/null +++ b/webpack-lib/lib/WebpackError.js @@ -0,0 +1,70 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Jarid Margolin @jaridmargolin +*/ + +"use strict"; + +const inspect = require("util").inspect.custom; +const makeSerializable = require("./util/makeSerializable"); + +/** @typedef {import("./Chunk")} Chunk */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class WebpackError extends Error { + /** + * Creates an instance of WebpackError. + * @param {string=} message error message + */ + constructor(message) { + super(message); + + /** @type {string=} */ + this.details = undefined; + /** @type {(Module | null)=} */ + this.module = undefined; + /** @type {DependencyLocation=} */ + this.loc = undefined; + /** @type {boolean=} */ + this.hideStack = undefined; + /** @type {Chunk=} */ + this.chunk = undefined; + /** @type {string=} */ + this.file = undefined; + } + + [inspect]() { + return this.stack + (this.details ? `\n${this.details}` : ""); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize({ write }) { + write(this.name); + write(this.message); + write(this.stack); + write(this.details); + write(this.loc); + write(this.hideStack); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize({ read }) { + this.name = read(); + this.message = read(); + this.stack = read(); + this.details = read(); + this.loc = read(); + this.hideStack = read(); + } +} + +makeSerializable(WebpackError, "webpack/lib/WebpackError"); + +module.exports = WebpackError; diff --git a/webpack-lib/lib/WebpackIsIncludedPlugin.js b/webpack-lib/lib/WebpackIsIncludedPlugin.js new file mode 100644 index 00000000000..981cf8f6dff --- /dev/null +++ b/webpack-lib/lib/WebpackIsIncludedPlugin.js @@ -0,0 +1,94 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const IgnoreErrorModuleFactory = require("./IgnoreErrorModuleFactory"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("./ModuleTypeConstants"); +const WebpackIsIncludedDependency = require("./dependencies/WebpackIsIncludedDependency"); +const { + toConstantDependency +} = require("./javascript/JavascriptParserHelpers"); + +/** @typedef {import("enhanced-resolve").Resolver} Resolver */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./javascript/JavascriptParser").Range} Range */ + +const PLUGIN_NAME = "WebpackIsIncludedPlugin"; + +class WebpackIsIncludedPlugin { + /** + * @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() + ); + + /** + * @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/webpack-lib/lib/WebpackOptionsApply.js b/webpack-lib/lib/WebpackOptionsApply.js new file mode 100644 index 00000000000..3928c043832 --- /dev/null +++ b/webpack-lib/lib/WebpackOptionsApply.js @@ -0,0 +1,783 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const OptionsApply = require("./OptionsApply"); + +const AssetModulesPlugin = require("./asset/AssetModulesPlugin"); +const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); +const JsonModulesPlugin = require("./json/JsonModulesPlugin"); + +const ChunkPrefetchPreloadPlugin = require("./prefetch/ChunkPrefetchPreloadPlugin"); + +const EntryOptionPlugin = require("./EntryOptionPlugin"); +const RecordIdsPlugin = require("./RecordIdsPlugin"); + +const RuntimePlugin = require("./RuntimePlugin"); + +const APIPlugin = require("./APIPlugin"); +const CompatibilityPlugin = require("./CompatibilityPlugin"); +const ConstPlugin = require("./ConstPlugin"); +const ExportsInfoApiPlugin = require("./ExportsInfoApiPlugin"); +const WebpackIsIncludedPlugin = require("./WebpackIsIncludedPlugin"); + +const TemplatedPathPlugin = require("./TemplatedPathPlugin"); +const UseStrictPlugin = require("./UseStrictPlugin"); +const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin"); + +const DataUriPlugin = require("./schemes/DataUriPlugin"); +const FileUriPlugin = require("./schemes/FileUriPlugin"); + +const ResolverCachePlugin = require("./cache/ResolverCachePlugin"); + +const CommonJsPlugin = require("./dependencies/CommonJsPlugin"); +const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin"); +const ImportMetaContextPlugin = require("./dependencies/ImportMetaContextPlugin"); +const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin"); +const ImportPlugin = require("./dependencies/ImportPlugin"); +const LoaderPlugin = require("./dependencies/LoaderPlugin"); +const RequireContextPlugin = require("./dependencies/RequireContextPlugin"); +const RequireEnsurePlugin = require("./dependencies/RequireEnsurePlugin"); +const RequireIncludePlugin = require("./dependencies/RequireIncludePlugin"); +const SystemPlugin = require("./dependencies/SystemPlugin"); +const URLPlugin = require("./dependencies/URLPlugin"); +const WorkerPlugin = require("./dependencies/WorkerPlugin"); + +const InferAsyncModulesPlugin = require("./async-modules/InferAsyncModulesPlugin"); + +const JavascriptMetaInfoPlugin = require("./JavascriptMetaInfoPlugin"); +const DefaultStatsFactoryPlugin = require("./stats/DefaultStatsFactoryPlugin"); +const DefaultStatsPresetPlugin = require("./stats/DefaultStatsPresetPlugin"); +const DefaultStatsPrinterPlugin = require("./stats/DefaultStatsPrinterPlugin"); + +const { cleverMerge } = require("./util/cleverMerge"); + +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ +/** @typedef {import("./Compiler")} Compiler */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */ + +class WebpackOptionsApply extends OptionsApply { + constructor() { + super(); + } + + /** + * @param {WebpackOptions} options options object + * @param {Compiler} compiler compiler object + * @returns {WebpackOptions} options object + */ + process(options, compiler) { + compiler.outputPath = /** @type {string} */ (options.output.path); + compiler.recordsInputPath = options.recordsInputPath || null; + compiler.recordsOutputPath = options.recordsOutputPath || null; + compiler.name = options.name; + + 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"); + new NodeTargetPlugin().apply(compiler); + } + if (options.externalsPresets.electronMain) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); + new ElectronTargetPlugin("main").apply(compiler); + } + if (options.externalsPresets.electronPreload) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); + new ElectronTargetPlugin("preload").apply(compiler); + } + if (options.externalsPresets.electronRenderer) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); + new ElectronTargetPlugin("renderer").apply(compiler); + } + if ( + options.externalsPresets.electron && + !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().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); + } + if (options.externalsPresets.webAsync) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ExternalsPlugin = require("./ExternalsPlugin"); + new ExternalsPlugin("import", ({ request, dependencyType }, callback) => { + if (dependencyType === "url") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `asset ${request}`); + } else if (options.experiments.css && dependencyType === "css-import") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `css-import ${request}`); + } else if ( + options.experiments.css && + /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) + ) { + if (/^\.css(\?|$)/.test(/** @type {string} */ (request))) + return callback(null, `css-import ${request}`); + return callback(null, `import ${request}`); + } + callback(); + }).apply(compiler); + } else if (options.externalsPresets.web) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ExternalsPlugin = require("./ExternalsPlugin"); + new ExternalsPlugin("module", ({ request, dependencyType }, callback) => { + if (dependencyType === "url") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `asset ${request}`); + } else if (options.experiments.css && dependencyType === "css-import") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `css-import ${request}`); + } else if ( + /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) + ) { + if ( + options.experiments.css && + /^\.css((\?)|$)/.test(/** @type {string} */ (request)) + ) + return callback(null, `css-import ${request}`); + return callback(null, `module ${request}`); + } + callback(); + }).apply(compiler); + } else if (options.externalsPresets.node && options.experiments.css) { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const ExternalsPlugin = require("./ExternalsPlugin"); + new ExternalsPlugin("module", ({ request, dependencyType }, callback) => { + if (dependencyType === "url") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `asset ${request}`); + } else if (dependencyType === "css-import") { + if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) + return callback(null, `css-import ${request}`); + } else if ( + /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) + ) { + if (/^\.css(\?|$)/.test(/** @type {string} */ (request))) + return callback(null, `css-import ${request}`); + return callback(null, `module ${request}`); + } + callback(); + }).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 "commonjs": { + const CommonJsChunkFormatPlugin = require("./javascript/CommonJsChunkFormatPlugin"); + new CommonJsChunkFormatPlugin().apply(compiler); + break; + } + case "module": { + const ModuleChunkFormatPlugin = require("./esm/ModuleChunkFormatPlugin"); + new ModuleChunkFormatPlugin().apply(compiler); + break; + } + default: + 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) { + for (const type of enabledLibraryTypes) { + const EnableLibraryPlugin = require("./library/EnableLibraryPlugin"); + new EnableLibraryPlugin(type).apply(compiler); + } + } + + 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.devtool) { + if (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"); + const noSources = options.devtool.includes("nosources"); + const debugIds = options.devtool.includes("debugids"); + const Plugin = evalWrapped + ? require("./EvalSourceMapDevToolPlugin") + : require("./SourceMapDevToolPlugin"); + new Plugin({ + 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 (options.devtool.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 AssetModulesPlugin().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.lazyCompilation) { + const LazyCompilationPlugin = require("./hmr/LazyCompilationPlugin"); + const lazyOptions = + typeof options.experiments.lazyCompilation === "object" + ? options.experiments.lazyCompilation + : {}; + new LazyCompilationPlugin({ + backend: + typeof lazyOptions.backend === "function" + ? lazyOptions.backend + : require("./hmr/lazyCompilationBackend")({ + ...lazyOptions.backend, + client: + (lazyOptions.backend && lazyOptions.backend.client) || + require.resolve( + `../hot/lazy-compilation-${ + options.externalsPresets.node ? "node" : "web" + }.js` + ) + }), + entries: !lazyOptions || lazyOptions.entries !== false, + imports: !lazyOptions || lazyOptions.imports !== false, + test: (lazyOptions && lazyOptions.test) || undefined + }).apply(compiler); + } + + if (options.experiments.buildHttp) { + const HttpUriPlugin = require("./schemes/HttpUriPlugin"); + const httpOptions = options.experiments.buildHttp; + new HttpUriPlugin(httpOptions).apply(compiler); + } + + new EntryOptionPlugin().apply(compiler); + compiler.hooks.entryOption.call( + /** @type {string} */ + (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({ + topLevelAwait: options.experiments.topLevelAwait + }).apply(compiler); + if (options.amd !== false) { + const AMDPlugin = require("./dependencies/AMDPlugin"); + const RequireJsStuffPlugin = require("./RequireJsStuffPlugin"); + new AMDPlugin(options.amd || {}).apply(compiler); + new RequireJsStuffPlugin().apply(compiler); + } + new CommonJsPlugin().apply(compiler); + new LoaderPlugin({}).apply(compiler); + if (options.node !== false) { + const NodeStuffPlugin = require("./NodeStuffPlugin"); + new NodeStuffPlugin(options.node).apply(compiler); + } + new APIPlugin({ + module: options.output.module + }).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().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 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.sideEffects) { + const SideEffectsFlagPlugin = require("./optimize/SideEffectsFlagPlugin"); + new SideEffectsFlagPlugin( + options.optimization.sideEffects === true + ).apply(compiler); + } + if (options.optimization.providedExports) { + const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin"); + new FlagDependencyExportsPlugin().apply(compiler); + } + if (options.optimization.usedExports) { + 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.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( + /** @type {{ name?: (entrypoint: { name: string }) => string }} */ + (options.optimization.runtimeChunk) + ).apply(compiler); + } + 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); + } + 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` + ); + } + } + 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"); + new DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify(options.optimization.nodeEnv) + }).apply(compiler); + } + if (options.optimization.minimize) { + for (const minimizer of /** @type {(WebpackPluginInstance | WebpackPluginFunction | "...")[]} */ ( + options.optimization.minimizer + )) { + 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); + } + + new TemplatedPathPlugin().apply(compiler); + + new RecordIdsPlugin({ + portableIds: options.optimization.portableRecords + }).apply(compiler); + + new WarnCaseSensitiveModulesPlugin().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"); + // eslint-disable-next-line guard-for-in + 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: /** @type {string} */ (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); + if (!compiler.inputFileSystem) { + throw new Error("No input filesystem provided"); + } + compiler.resolverFactory.hooks.resolveOptions + .for("normal") + .tap("WebpackOptionsApply", resolveOptions => { + resolveOptions = cleverMerge(options.resolve, resolveOptions); + resolveOptions.fileSystem = + /** @type {InputFileSystem} */ + (compiler.inputFileSystem); + return resolveOptions; + }); + compiler.resolverFactory.hooks.resolveOptions + .for("context") + .tap("WebpackOptionsApply", 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 => { + resolveOptions = cleverMerge(options.resolveLoader, resolveOptions); + resolveOptions.fileSystem = + /** @type {InputFileSystem} */ + (compiler.inputFileSystem); + return resolveOptions; + }); + compiler.hooks.afterResolvers.call(compiler); + return options; + } +} + +module.exports = WebpackOptionsApply; diff --git a/webpack-lib/lib/WebpackOptionsDefaulter.js b/webpack-lib/lib/WebpackOptionsDefaulter.js new file mode 100644 index 00000000000..12fbe698d93 --- /dev/null +++ b/webpack-lib/lib/WebpackOptionsDefaulter.js @@ -0,0 +1,26 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { applyWebpackOptionsDefaults } = require("./config/defaults"); +const { getNormalizedWebpackOptions } = require("./config/normalization"); + +/** @typedef {import("./config/normalization").WebpackOptions} WebpackOptions */ +/** @typedef {import("./config/normalization").WebpackOptionsNormalized} WebpackOptionsNormalized */ + +class WebpackOptionsDefaulter { + /** + * @param {WebpackOptions} options webpack options + * @returns {WebpackOptionsNormalized} normalized webpack options + */ + process(options) { + const normalizedOptions = getNormalizedWebpackOptions(options); + applyWebpackOptionsDefaults(normalizedOptions); + return normalizedOptions; + } +} + +module.exports = WebpackOptionsDefaulter; diff --git a/webpack-lib/lib/asset/AssetGenerator.js b/webpack-lib/lib/asset/AssetGenerator.js new file mode 100644 index 00000000000..4661d6cafdc --- /dev/null +++ b/webpack-lib/lib/asset/AssetGenerator.js @@ -0,0 +1,739 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const mimeTypes = require("mime-types"); +const path = require("path"); +const { RawSource } = require("webpack-sources"); +const ConcatenationScope = require("../ConcatenationScope"); +const Generator = require("../Generator"); +const { + NO_TYPES, + ASSET_TYPES, + ASSET_AND_JS_TYPES, + ASSET_AND_JS_AND_CSS_URL_TYPES, + ASSET_AND_CSS_URL_TYPES, + JS_TYPES, + JS_AND_CSS_URL_TYPES, + CSS_URL_TYPES +} = require("../ModuleSourceTypesConstants"); +const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const createHash = require("../util/createHash"); +const { makePathsRelative } = require("../util/identifier"); +const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); + +/** @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").RawPublicPath} RawPublicPath */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("../Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/createHash").Algorithm} Algorithm */ + +/** + * @template T + * @template U + * @param {Array | Set} a a + * @param {Array | Set} b b + * @returns {Array & Array} array + */ +const mergeMaybeArrays = (a, b) => { + 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 Array.from(set); +}; + +/** + * @template {object} T + * @template {object} U + * @param {TODO} a a + * @param {TODO} b b + * @returns {T & U} object + */ +const mergeAssetInfo = (a, b) => { + 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(a[key], b[key]); + break; + default: + throw new Error(`Can't handle conflicting asset info for ${key}`); + } + } + } + return result; +}; + +/** + * @template {object} T + * @template {object} U + * @param {TODO} a a + * @param {TODO} b b + * @returns {T & U} 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; +}; + +/** + * @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("utf-8"); + } + + 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; +}; + +/** + * @param {string} 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 { + /** + * @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(); + this.dataUrlOptions = dataUrlOptions; + this.filename = filename; + this.publicPath = publicPath; + this.outputPath = outputPath; + this.emit = emit; + this._moduleGraph = moduleGraph; + } + + /** + * @param {NormalModule} module module + * @param {RuntimeTemplate} runtimeTemplate runtime template + * @returns {string} source file name + */ + getSourceFileName(module, runtimeTemplate) { + return makePathsRelative( + runtimeTemplate.compilation.compiler.context, + module.matchResource || module.resource, + runtimeTemplate.compilation.compiler.root + ).replace(/^\.\//, ""); + } + + /** + * @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; + } + + /** + * @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 | boolean | undefined} */ + let mimeType = + /** @type {AssetGeneratorDataUrlOptions} */ + (this.dataUrlOptions).mimetype; + if (mimeType === undefined) { + const ext = path.extname( + /** @type {string} */ + (module.nameForCondition()) + ); + if ( + module.resourceResolveData && + module.resourceResolveData.mimetype !== undefined + ) { + mimeType = + module.resourceResolveData.mimetype + + module.resourceResolveData.parameters; + } else if (ext) { + mimeType = mimeTypes.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); + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @returns {string} DataURI + */ + generateDataUri(module) { + const source = /** @type {Source} */ (module.originalSource()); + + let encodedSource; + + if (typeof this.dataUrlOptions === "function") { + encodedSource = this.dataUrlOptions.call(null, source.source(), { + filename: module.matchResource || module.resource, + module + }); + } else { + /** @type {"base64" | false | undefined} */ + 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); + + let encodedContent; + + if ( + module.resourceResolveData && + module.resourceResolveData.encoding === encoding && + decodeDataUriContent( + module.resourceResolveData.encoding, + module.resourceResolveData.encodedContent + ).equals(source.buffer()) + ) { + encodedContent = module.resourceResolveData.encodedContent; + } else { + encodedContent = encodeDataUri(encoding, source); + } + + encodedSource = `data:${mimeType}${ + encoding ? `;${encoding}` : "" + },${encodedContent}`; + } + + return encodedSource; + } + + /** + * @private + * @param {NormalModule} module module for which the code should be generated + * @param {GenerateContext} generateContext context for generate + * @param {string} contentHash the content hash + * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info + */ + _getFilenameWithInfo( + module, + { runtime, runtimeTemplate, chunkGraph }, + contentHash + ) { + const assetModuleFilename = + this.filename || + /** @type {AssetModuleFilename} */ + (runtimeTemplate.outputOptions.assetModuleFilename); + + const sourceFilename = this.getSourceFileName(module, runtimeTemplate); + let { path: filename, info: assetInfo } = + runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, { + module, + runtime, + filename: sourceFilename, + chunkGraph, + contentHash + }); + + const originalFilename = filename; + + if (this.outputPath) { + const { path: outputPath, info } = + runtimeTemplate.compilation.getAssetPathWithInfo(this.outputPath, { + module, + runtime, + filename: sourceFilename, + chunkGraph, + contentHash + }); + filename = path.posix.join(outputPath, filename); + assetInfo = mergeAssetInfo(assetInfo, info); + } + + return { originalFilename, filename, assetInfo }; + } + + /** + * @private + * @param {NormalModule} module module for which the code should be generated + * @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 + */ + _getAssetPathWithInfo( + module, + { runtimeTemplate, runtime, chunkGraph, type, runtimeRequirements }, + filename, + assetInfo, + contentHash + ) { + const sourceFilename = this.getSourceFileName(module, runtimeTemplate); + + let assetPath; + + if (this.publicPath !== undefined && type === "javascript") { + const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo( + this.publicPath, + { + module, + runtime, + filename: sourceFilename, + chunkGraph, + contentHash + } + ); + assetInfo = mergeAssetInfo(assetInfo, info); + assetPath = JSON.stringify(path + filename); + } else if (this.publicPath !== undefined && type === "css-url") { + const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo( + this.publicPath, + { + module, + runtime, + filename: sourceFilename, + chunkGraph, + contentHash + } + ); + assetInfo = mergeAssetInfo(assetInfo, info); + assetPath = path + filename; + } else if (type === "javascript") { + // add __webpack_require__.p + runtimeRequirements.add(RuntimeGlobals.publicPath); + assetPath = runtimeTemplate.concatenation( + { expr: RuntimeGlobals.publicPath }, + filename + ); + } else if (type === "css-url") { + const compilation = runtimeTemplate.compilation; + const path = + compilation.outputOptions.publicPath === "auto" + ? CssUrlDependency.PUBLIC_PATH_AUTO + : compilation.getAssetPath( + /** @type {TemplatePath} */ + (compilation.outputOptions.publicPath), + { + hash: compilation.hash + } + ); + + assetPath = path + filename; + } + + return { + // eslint-disable-next-line object-shorthand + assetPath: /** @type {string} */ (assetPath), + assetInfo: { sourceFilename, ...assetInfo } + }; + } + + /** + * @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; + + let content; + + const needContent = type === "javascript" || type === "css-url"; + + const data = getData ? getData() : undefined; + + if ( + /** @type {BuildInfo} */ + (module.buildInfo).dataUrl && + needContent + ) { + const encodedSource = this.generateDataUri(module); + content = + type === "javascript" ? JSON.stringify(encodedSource) : encodedSource; + + if (data) { + data.set("url", { [type]: content, ...data.get("url") }); + } + } else { + const hash = createHash( + /** @type {Algorithm} */ + (runtimeTemplate.outputOptions.hashFunction) + ); + + if (runtimeTemplate.outputOptions.hashSalt) { + hash.update(runtimeTemplate.outputOptions.hashSalt); + } + + hash.update(/** @type {Source} */ (module.originalSource()).buffer()); + + const fullHash = + /** @type {string} */ + (hash.digest(runtimeTemplate.outputOptions.hashDigest)); + + if (data) { + data.set("fullContentHash", fullHash); + } + + /** @type {BuildInfo} */ + (module.buildInfo).fullContentHash = fullHash; + + /** @type {string} */ + const contentHash = nonNumericOnlyHash( + fullHash, + /** @type {number} */ + (generateContext.runtimeTemplate.outputOptions.hashDigestLength) + ); + + if (data) { + data.set("contentHash", contentHash); + } + + const { originalFilename, filename, assetInfo } = + this._getFilenameWithInfo(module, generateContext, contentHash); + + if (data) { + data.set("filename", filename); + } + + let { assetPath, assetInfo: newAssetInfo } = this._getAssetPathWithInfo( + module, + generateContext, + originalFilename, + assetInfo, + contentHash + ); + + if (data && (type === "javascript" || type === "css-url")) { + data.set("url", { [type]: assetPath, ...data.get("url") }); + } + + if (data && data.get("assetInfo")) { + newAssetInfo = mergeAssetInfo(data.get("assetInfo"), 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 {BuildInfo} */ + (module.buildInfo).filename = filename; + + /** @type {BuildInfo} */ + (module.buildInfo).assetInfo = newAssetInfo; + + content = assetPath; + } + + if (type === "javascript") { + if (concatenationScope) { + concatenationScope.registerNamespaceExport( + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + ); + + return new RawSource( + `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + } = ${content};` + ); + } + + runtimeRequirements.add(RuntimeGlobals.module); + + return new RawSource(`${RuntimeGlobals.module}.exports = ${content};`); + } else if (type === "css-url") { + return null; + } + + return /** @type {Source} */ (module.originalSource()); + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + 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 && module.buildInfo.dataUrl) || this.emit === false) { + if (sourceTypes) { + if (sourceTypes.has("javascript") && sourceTypes.has("css")) { + return JS_AND_CSS_URL_TYPES; + } else if (sourceTypes.has("javascript")) { + return JS_TYPES; + } else if (sourceTypes.has("css")) { + return CSS_URL_TYPES; + } + } + + return NO_TYPES; + } + + if (sourceTypes) { + if (sourceTypes.has("javascript") && sourceTypes.has("css")) { + return ASSET_AND_JS_AND_CSS_URL_TYPES; + } else if (sourceTypes.has("javascript")) { + return ASSET_AND_JS_TYPES; + } else if (sourceTypes.has("css")) { + return ASSET_AND_CSS_URL_TYPES; + } + } + + return ASSET_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} 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 && 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; + } + } + + /** + * @param {Hash} hash hash that will be modified + * @param {UpdateHashContext} updateHashContext context for updating hash + */ + updateHash(hash, updateHashContext) { + const { module } = updateHashContext; + if ( + /** @type {BuildInfo} */ + (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: this.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 || + /** @type {AssetModuleFilename} */ + (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/webpack-lib/lib/asset/AssetModulesPlugin.js b/webpack-lib/lib/asset/AssetModulesPlugin.js new file mode 100644 index 00000000000..93817f3d064 --- /dev/null +++ b/webpack-lib/lib/asset/AssetModulesPlugin.js @@ -0,0 +1,250 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Yuta Hiroto @hiroppy +*/ + +"use strict"; + +const { + ASSET_MODULE_TYPE_RESOURCE, + ASSET_MODULE_TYPE_INLINE, + ASSET_MODULE_TYPE, + ASSET_MODULE_TYPE_SOURCE +} = require("../ModuleTypeConstants"); +const { cleverMerge } = require("../util/cleverMerge"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const memoize = require("../util/memoize"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ + +/** + * @param {string} name name of definitions + * @returns {TODO} definition + */ +const getSchema = name => { + const { definitions } = require("../../schemas/WebpackOptions.json"); + return { + definitions, + oneOf: [{ $ref: `#/definitions/${name}` }] + }; +}; + +const generatorValidationOptions = { + name: "Asset Modules Plugin", + baseDataPath: "generator" +}; +const validateGeneratorOptions = { + asset: createSchemaValidation( + require("../../schemas/plugins/asset/AssetGeneratorOptions.check.js"), + () => getSchema("AssetGeneratorOptions"), + generatorValidationOptions + ), + "asset/resource": createSchemaValidation( + require("../../schemas/plugins/asset/AssetResourceGeneratorOptions.check.js"), + () => getSchema("AssetResourceGeneratorOptions"), + generatorValidationOptions + ), + "asset/inline": createSchemaValidation( + require("../../schemas/plugins/asset/AssetInlineGeneratorOptions.check.js"), + () => getSchema("AssetInlineGeneratorOptions"), + generatorValidationOptions + ) +}; + +const validateParserOptions = createSchemaValidation( + require("../../schemas/plugins/asset/AssetParserOptions.check.js"), + () => getSchema("AssetParserOptions"), + { + name: "Asset Modules Plugin", + baseDataPath: "parser" + } +); + +const getAssetGenerator = memoize(() => require("./AssetGenerator")); +const getAssetParser = memoize(() => require("./AssetParser")); +const getAssetSourceParser = memoize(() => require("./AssetSourceParser")); +const getAssetSourceGenerator = memoize(() => + require("./AssetSourceGenerator") +); + +const type = ASSET_MODULE_TYPE; +const plugin = "AssetModulesPlugin"; + +class AssetModulesPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + plugin, + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.createParser + .for(ASSET_MODULE_TYPE) + .tap(plugin, parserOptions => { + validateParserOptions(parserOptions); + parserOptions = cleverMerge( + /** @type {AssetParserOptions} */ + (compiler.options.module.parser.asset), + parserOptions + ); + + 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, _parserOptions => { + const AssetParser = getAssetParser(); + + return new AssetParser(true); + }); + normalModuleFactory.hooks.createParser + .for(ASSET_MODULE_TYPE_RESOURCE) + .tap(plugin, _parserOptions => { + const AssetParser = getAssetParser(); + + return new AssetParser(false); + }); + normalModuleFactory.hooks.createParser + .for(ASSET_MODULE_TYPE_SOURCE) + .tap(plugin, _parserOptions => { + const AssetSourceParser = getAssetSourceParser(); + + return new AssetSourceParser(); + }); + + for (const type of [ + ASSET_MODULE_TYPE, + ASSET_MODULE_TYPE_INLINE, + ASSET_MODULE_TYPE_RESOURCE + ]) { + normalModuleFactory.hooks.createGenerator + .for(type) + .tap(plugin, generatorOptions => { + validateGeneratorOptions[type](generatorOptions); + + let dataUrl; + if (type !== ASSET_MODULE_TYPE_RESOURCE) { + dataUrl = generatorOptions.dataUrl; + if (!dataUrl || typeof dataUrl === "object") { + dataUrl = { + encoding: undefined, + mimetype: undefined, + ...dataUrl + }; + } + } + + let filename; + let publicPath; + 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, () => { + const AssetSourceGenerator = getAssetSourceGenerator(); + + return new AssetSourceGenerator(compilation.moduleGraph); + }); + + compilation.hooks.renderManifest.tap(plugin, (result, options) => { + const { chunkGraph } = compilation; + const { chunk, codeGenerationResults } = options; + + const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + ASSET_MODULE_TYPE, + compareModulesByIdentifier + ); + if (modules) { + for (const module of modules) { + try { + const codeGenResult = codeGenerationResults.get( + module, + chunk.runtime + ); + const buildInfo = /** @type {BuildInfo} */ (module.buildInfo); + const data = + /** @type {NonNullable} */ + (codeGenResult.data); + const errored = module.getNumberOfErrors() > 0; + result.push({ + render: () => + /** @type {Source} */ (codeGenResult.sources.get(type)), + filename: errored + ? module.nameForCondition() + : buildInfo.filename || data.get("filename"), + info: buildInfo.assetInfo || data.get("assetInfo"), + auxiliary: true, + identifier: `assetModule${chunkGraph.getModuleId(module)}`, + hash: errored + ? chunkGraph.getModuleHash(module, chunk.runtime) + : buildInfo.fullContentHash || data.get("fullContentHash") + }); + } catch (err) { + /** @type {Error} */ (err).message += + `\nduring rendering of asset ${module.identifier()}`; + throw err; + } + } + } + + return result; + }); + + compilation.hooks.prepareModuleExecution.tap( + "AssetModulesPlugin", + (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(data.get("filename"), { + source, + info: data.get("assetInfo") + }); + } + ); + } + ); + } +} + +module.exports = AssetModulesPlugin; diff --git a/webpack-lib/lib/asset/AssetParser.js b/webpack-lib/lib/asset/AssetParser.js new file mode 100644 index 00000000000..b4f1d534948 --- /dev/null +++ b/webpack-lib/lib/asset/AssetParser.js @@ -0,0 +1,65 @@ +/* + 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").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ + +class AssetParser extends Parser { + /** + * @param {AssetParserOptions["dataUrlCondition"] | boolean} dataUrlCondition condition for inlining as DataUrl + */ + constructor(dataUrlCondition) { + super(); + this.dataUrlCondition = dataUrlCondition; + } + + /** + * @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 {BuildInfo} */ (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: state.module.matchResource || state.module.resource, + 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/webpack-lib/lib/asset/AssetSourceGenerator.js b/webpack-lib/lib/asset/AssetSourceGenerator.js new file mode 100644 index 00000000000..c6f2633d0b9 --- /dev/null +++ b/webpack-lib/lib/asset/AssetSourceGenerator.js @@ -0,0 +1,146 @@ +/* + 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 { + NO_TYPES, + CSS_URL_TYPES, + JS_TYPES, + JS_AND_CSS_URL_TYPES +} = require("../ModuleSourceTypesConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../NormalModule")} NormalModule */ + +class AssetSourceGenerator extends Generator { + /** + * @param {ModuleGraph} moduleGraph the module graph + */ + constructor(moduleGraph) { + super(); + + this._moduleGraph = moduleGraph; + } + + /** + * @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": { + if (!originalSource) { + return new RawSource(""); + } + + const content = originalSource.source(); + const encodedSource = + typeof content === "string" ? content : content.toString("utf-8"); + + let sourceContent; + if (concatenationScope) { + concatenationScope.registerNamespaceExport( + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + ); + sourceContent = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + } = ${JSON.stringify(encodedSource)};`; + } else { + runtimeRequirements.add(RuntimeGlobals.module); + sourceContent = `${RuntimeGlobals.module}.exports = ${JSON.stringify( + encodedSource + )};`; + } + return new RawSource(sourceContent); + } + case "css-url": { + if (!originalSource) { + return null; + } + + const content = originalSource.source(); + const encodedSource = + typeof content === "string" ? content : content.toString("utf-8"); + + if (data) { + data.set("url", { [type]: encodedSource }); + } + return null; + } + default: + return null; + } + } + + /** + * @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; + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + 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.has("javascript") && sourceTypes.has("css")) { + return JS_AND_CSS_URL_TYPES; + } else if (sourceTypes.has("javascript")) { + return JS_TYPES; + } else if (sourceTypes.has("css")) { + return CSS_URL_TYPES; + } + + return NO_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} 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/webpack-lib/lib/asset/AssetSourceParser.js b/webpack-lib/lib/asset/AssetSourceParser.js new file mode 100644 index 00000000000..c122f0ea4e4 --- /dev/null +++ b/webpack-lib/lib/asset/AssetSourceParser.js @@ -0,0 +1,37 @@ +/* + 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 { + /** + * @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/webpack-lib/lib/asset/RawDataUrlModule.js b/webpack-lib/lib/asset/RawDataUrlModule.js new file mode 100644 index 00000000000..509efa51604 --- /dev/null +++ b/webpack-lib/lib/asset/RawDataUrlModule.js @@ -0,0 +1,163 @@ +/* + 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 { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const { ASSET_MODULE_TYPE_RAW_DATA_URL } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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 */ + +class RawDataUrlModule extends Module { + /** + * @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); + this.url = url; + this.urlBuffer = url ? Buffer.from(url) : undefined; + this.identifierStr = identifier || this.url; + this.readableIdentifierStr = readableIdentifier || this.identifierStr; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this.identifierStr; + } + + /** + * @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); + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return /** @type {string} */ ( + requestShortener.shorten(this.readableIdentifierStr) + ); + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + cacheable: true + }; + callback(); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration(context) { + if (this.url === undefined) + this.url = /** @type {Buffer} */ (this.urlBuffer).toString(); + const sources = new Map(); + sources.set( + "javascript", + new RawSource(`module.exports = ${JSON.stringify(this.url)};`) + ); + const data = new Map(); + data.set("url", { + javascript: this.url + }); + const runtimeRequirements = new Set(); + runtimeRequirements.add(RuntimeGlobals.module); + return { sources, runtimeRequirements, data }; + } + + /** + * @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); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.urlBuffer); + write(this.identifierStr); + write(this.readableIdentifierStr); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.urlBuffer = read(); + this.identifierStr = read(); + this.readableIdentifierStr = read(); + + super.deserialize(context); + } +} + +makeSerializable(RawDataUrlModule, "webpack/lib/asset/RawDataUrlModule"); + +module.exports = RawDataUrlModule; diff --git a/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js b/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js new file mode 100644 index 00000000000..84abf28107d --- /dev/null +++ b/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js @@ -0,0 +1,72 @@ +/* + 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 */ + +/** + * @extends {InitFragment} + */ +class AwaitDependenciesInitFragment extends InitFragment { + /** + * @param {Set} promises the promises that should be awaited + */ + constructor(promises) { + super( + undefined, + InitFragment.STAGE_ASYNC_DEPENDENCIES, + 0, + "await-dependencies" + ); + this.promises = promises; + } + + /** + * @param {AwaitDependenciesInitFragment} other other AwaitDependenciesInitFragment + * @returns {AwaitDependenciesInitFragment} AwaitDependenciesInitFragment + */ + merge(other) { + const promises = new Set(other.promises); + for (const p of this.promises) { + promises.add(p); + } + return new AwaitDependenciesInitFragment(promises); + } + + /** + * @param {GenerateContext} context context + * @returns {string | Source | undefined} the source code that will be included as initialization code + */ + getContent({ runtimeRequirements }) { + runtimeRequirements.add(RuntimeGlobals.module); + const promises = this.promises; + if (promises.size === 0) { + return ""; + } + if (promises.size === 1) { + const [p] = promises; + return Template.asString([ + `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${p}]);`, + `${p} = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];`, + "" + ]); + } + const sepPromises = Array.from(promises).join(", "); + // TODO check if destructuring is supported + return Template.asString([ + `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${sepPromises}]);`, + `([${sepPromises}] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);`, + "" + ]); + } +} + +module.exports = AwaitDependenciesInitFragment; diff --git a/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js b/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js new file mode 100644 index 00000000000..d395e30c474 --- /dev/null +++ b/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js @@ -0,0 +1,55 @@ +/* + 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 */ + +class InferAsyncModulesPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("InferAsyncModulesPlugin", compilation => { + const { moduleGraph } = compilation; + compilation.hooks.finishModules.tap( + "InferAsyncModulesPlugin", + 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/webpack-lib/lib/buildChunkGraph.js b/webpack-lib/lib/buildChunkGraph.js new file mode 100644 index 00000000000..ce2dafebb05 --- /dev/null +++ b/webpack-lib/lib/buildChunkGraph.js @@ -0,0 +1,1344 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError"); +const { connectChunkGroupParentAndChild } = require("./GraphHelpers"); +const ModuleGraphConnection = require("./ModuleGraphConnection"); +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")} Dependency */ +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Entrypoint")} Entrypoint */ +/** @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 */ + +/** + * @typedef {object} QueueItem + * @property {number} action + * @property {DependenciesBlock} block + * @property {Module} module + * @property {Chunk} chunk + * @property {ChunkGroup} chunkGroup + * @property {ChunkGroupInfo} chunkGroupInfo + */ + +/** + * @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 + */ + +/** + * @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); + +/** + * @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; + +/** + * @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; +}; + +/** + * @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; + 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; + } +}; + +/** + * @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; + + const blockModulesRuntimeMap = new Map(); + + /** @type {BlockModulesMap | undefined} */ + let blockModulesMap; + + /** @type {Map} */ + const ordinalByModule = new Map(); + + /** + * @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); + } + + /** + * @param {DependenciesBlock} block block + * @param {RuntimeSpec} runtime runtime + * @returns {BlockModulesInFlattenTuples} block modules in flatten tuples + */ + const getBlockModules = (block, runtime) => { + blockModulesMap = blockModulesRuntimeMap.get(runtime); + if (blockModulesMap === undefined) { + 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) { + const map = memCache.provide( + "bundleChunkGraph.blockModules", + runtime, + () => { + logger.time("visitModules: prepare"); + const map = new Map(); + extractBlockModules(module, moduleGraph, runtime, map); + logger.timeAggregate("visitModules: prepare"); + return map; + } + ); + for (const [block, blockModules] of map) + blockModulesMap.set(block, blockModules); + return map.get(block); + } + logger.time("visitModules: prepare"); + extractBlockModules(module, moduleGraph, runtime, 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(); + + /** @type {Map} */ + const namedChunkGroups = new Map(); + + /** @type {Map} */ + const namedAsyncEntrypoints = 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 = []; + + /** @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 = { + 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 + /** + * @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; + 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) { + entrypoint = compilation.addAsyncEntrypoint( + entryOptions, + module, + /** @type {DependencyLocation} */ (b.loc), + /** @type {string} */ (b.request) + ); + maskByChunk.set(entrypoint.chunks[0], ZERO_BIGINT); + entrypoint.index = nextChunkGroupIndex++; + cgi = { + chunkGroup: entrypoint, + initialized: false, + runtime: entrypoint.options.runtime || 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, cgi); + + chunkGraph.connectBlockAndChunkGroup(b, entrypoint); + if (chunkName) { + namedAsyncEntrypoints.set(chunkName, cgi); + } + } else { + entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup); + // TODO merge entryOptions + 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: 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 = { + 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) { + 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.chunkGroup.addAsyncEntrypoint(entrypoint); + } + }; + + /** + * @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 refOrdinal = /** @type {number} */ getModuleOrdinal(refModule); + 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 (isOrdinalSetInMask(minAvailableModules, refOrdinal)) { + // 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); + } + }; + + /** + * @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; + } + } + } + }; + + /** + * @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) { + 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; + /** + * @param {DependenciesBlock} current current + * @param {BlocksWithNestedBlocks} visited visited dependencies blocks + */ + const process = (current, visited) => { + const blockModules = 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)` + ); +}; + +/** + * @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 ( + // TODO is this needed? + !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 + connectChunkGroupParentAndChild( + originChunkGroupInfo.chunkGroup, + 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/webpack-lib/lib/cli.js b/webpack-lib/lib/cli.js new file mode 100644 index 00000000000..c7ff52bc311 --- /dev/null +++ b/webpack-lib/lib/cli.js @@ -0,0 +1,699 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const path = require("path"); +const webpackSchema = require("../schemas/WebpackOptions.json"); + +/** @typedef {TODO & { absolutePath: boolean, instanceof: string, cli: { helper?: boolean, exclude?: boolean } }} Schema */ + +// TODO add originPath to PathItem for better errors +/** + * @typedef {object} PathItem + * @property {any} schema the part of the schema + * @property {string} path the path in the config + */ + +/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */ + +/** + * @typedef {object} Problem + * @property {ProblemType} type + * @property {string} path + * @property {string} argument + * @property {any=} value + * @property {number=} index + * @property {string=} expected + */ + +/** + * @typedef {object} LocalProblem + * @property {ProblemType} type + * @property {string} path + * @property {string=} expected + */ + +/** + * @typedef {object} ArgumentConfig + * @property {string | undefined} description + * @property {string} [negatedDescription] + * @property {string} path + * @property {boolean} multiple + * @property {"enum"|"string"|"path"|"number"|"boolean"|"RegExp"|"reset"} type + * @property {any[]=} values + */ + +/** @typedef {"string" | "number" | "boolean"} SimpleType */ + +/** + * @typedef {object} Argument + * @property {string | undefined} description + * @property {SimpleType} simpleType + * @property {boolean} multiple + * @property {ArgumentConfig[]} configs + */ + +/** @typedef {string | number | boolean | RegExp | (string | number | boolean | RegExp)} Value */ + +/** @typedef {Record} Flags */ + +/** + * @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 = {}; + + /** + * @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(); + + /** + * @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[newPath[i]]; + + if (!inner) { + break; + } + + schemaPart = inner; + } + + return schemaPart; + }; + + /** + * @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; + } + }; + + /** + * @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; + } + } + }; + + /** + * @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; + } + } + }; + + /** + * @param {Schema} schemaPart schema + * @returns {Pick | undefined} partial argument config + */ + const schemaToArgumentConfig = schemaPart => { + if (schemaPart.enum) { + return { + type: "enum", + values: schemaPart.enum + }; + } + 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; + }; + + /** + * @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)) + }; + }; + + /** + * @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} with ${argConfig.type} type (array type must be before single item type)` + ); + } + return 0; + } + + flags[name].configs.push(argConfig); + + return 1; + }; + + // TODO support `not` and `if/then/else` + // TODO support `const`, but we don't use it on our schema + /** + * @param {Schema} schemaPart the current schema + * @param {string} schemaPath the current path in the schema + * @param {{schema: object, path: string}[]} path all previous visited schemaParts + * @param {string | null} inArray if inside of an array, the path to the array + * @returns {number} added arguments + */ + const traverse = (schemaPart, schemaPath = "", path = [], inArray = null) => { + while (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; + + const fullPath = [{ schema: schemaPart, path: schemaPath }, ...path]; + + let addedArguments = 0; + + addedArguments += addFlag(fullPath, Boolean(inArray)); + + 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 + ); + } + } + + 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 + ); + } + + return addedArguments; + } + + addedArguments += traverse( + /** @type {Schema} */ + (schemaPart.items), + `${schemaPath}[]`, + fullPath, + schemaPath + ); + + if (addedArguments > 0) { + addResetFlag(fullPath); + addedArguments++; + } + + return addedArguments; + } + + const maybeOf = schemaPart.oneOf || schemaPart.anyOf || schemaPart.allOf; + + if (maybeOf) { + const items = maybeOf; + + for (let i = 0; i < items.length; i++) { + addedArguments += traverse( + /** @type {Schema} */ + (items[i]), + schemaPath, + fullPath, + inArray + ); + } + + 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; +}; + +const cliAddedItems = new WeakMap(); + +/** @typedef {string | number} Property */ + +/** + * @param {Configuration} 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?: any, property?: Property, value?: 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; + 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++; + } + 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 }; +}; + +/** + * @param {Configuration} config configuration + * @param {string} schemaPath path in the config + * @param {any} 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; + object[/** @type {Property} */ (property)] = value; + return null; +}; + +/** + * @param {ArgumentConfig} argConfig processing instructions + * @param {Configuration} 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; +}; + +/** + * @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; + } +}; + +/** + * @param {ArgumentConfig} argConfig processing instructions + * @param {Value} value the value + * @returns {any | 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" && /^[+-]?\d*(\.\d*)[eE]\d+$/) { + 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 {NonNullable} */ + (argConfig.values); + if (values.includes(value)) return value; + for (const item of values) { + if (`${item}` === value) return item; + } + break; + } + case "reset": + if (value === true) return []; + break; + } +}; + +/** @typedef {any} Configuration */ + +/** + * @param {Flags} args object of arguments + * @param {Configuration} config configuration + * @param {Record} 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; + } + /** + * @param {Value} value value + * @param {number | undefined} i index + */ + const processValue = (value, i) => { + 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; +}; + +module.exports.getArguments = getArguments; +module.exports.processArguments = processArguments; diff --git a/webpack-lib/lib/config/browserslistTargetHandler.js b/webpack-lib/lib/config/browserslistTargetHandler.js new file mode 100644 index 00000000000..51b0896ab0c --- /dev/null +++ b/webpack-lib/lib/config/browserslistTargetHandler.js @@ -0,0 +1,363 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const browserslist = require("browserslist"); +const path = require("path"); + +/** @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; + +/** + * @typedef {object} BrowserslistHandlerConfig + * @property {string=} configPath + * @property {string=} env + * @property {string=} query + */ + +/** + * @param {string | null | undefined} input input string + * @param {string} context the context directory + * @returns {BrowserslistHandlerConfig} config + */ +const parse = (input, context) => { + if (!input) { + return {}; + } + + if (path.isAbsolute(input)) { + const [, configPath, env] = inputRx.exec(input) || []; + return { configPath, env }; + } + + const config = browserslist.findConfig(context); + + if (config && Object.keys(config).includes(input)) { + return { env: input }; + } + + return { query: input }; +}; + +/** + * @param {string | null | undefined} input input string + * @param {string} context the context directory + * @returns {string[] | undefined} selected browsers + */ +const load = (input, context) => { + const { configPath, env, query } = parse(input, context); + + // if a query is specified, then use it, else + // if a path to a config is specified then load it, else + // find a nearest config + const config = + query || + (configPath + ? browserslist.loadConfig({ + config: configPath, + env + }) + : browserslist.loadConfig({ path: context, env })); + + if (!config) return; + return browserslist(config); +}; + +/** + * @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; + // Internet Explorer Mobile, Blackberry browser and Opera Mini are very old browsers, they do not support new features + const es6DynamicImport = rawChecker({ + /* eslint-disable camelcase */ + 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] + /* eslint-enable camelcase */ + }); + + 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] + }), + 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: Unknown support + // baidu: Unknown support + // and_uc: Unknown support + 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: Unknown support + // baidu: Unknown support + // and_uc: Unknown support + 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: es6DynamicImport, + dynamicImportInWorker: es6DynamicImport && !anyNode, + // 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: Unknown support + // baidu: Unknown support + // and_uc: Unknown support + 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: Not supported + // baidu: Not supported + // and_uc: Not supported + kaios: [3, 0], + node: 14 + }), + 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: [13, 1], + baidu: [13, 18], + and_uc: [15, 5], + 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: true, + nodeBuiltins: nodeProperty, + nodePrefixForCoreModules: + nodeProperty && + !browsers.some(b => b.startsWith("node 15")) && + rawChecker({ + node: [14, 18] + }), + require: nodeProperty + }; +}; + +module.exports = { + resolve, + load +}; diff --git a/webpack-lib/lib/config/defaults.js b/webpack-lib/lib/config/defaults.js new file mode 100644 index 00000000000..f264730144a --- /dev/null +++ b/webpack-lib/lib/config/defaults.js @@ -0,0 +1,1673 @@ +/* + 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 { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JSON_MODULE_TYPE, + WEBASSEMBLY_MODULE_TYPE_ASYNC, + WEBASSEMBLY_MODULE_TYPE_SYNC, + ASSET_MODULE_TYPE, + ASSET_MODULE_TYPE_INLINE, + ASSET_MODULE_TYPE_RESOURCE, + CSS_MODULE_TYPE_AUTO, + CSS_MODULE_TYPE, + CSS_MODULE_TYPE_MODULE, + CSS_MODULE_TYPE_GLOBAL +} = require("../ModuleTypeConstants"); +const Template = require("../Template"); +const { cleverMerge } = require("../util/cleverMerge"); +const { + getTargetsProperties, + getTargetProperties, + getDefaultTarget +} = require("./target"); + +/** @typedef {import("../../declarations/WebpackOptions").CacheOptions} CacheOptions */ +/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptionsNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").Context} Context */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorOptions} CssGeneratorOptions */ +/** @typedef {import("../../declarations/WebpackOptions").CssParserOptions} CssParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */ +/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */ +/** @typedef {import("../../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */ +/** @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").Library} Library */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../../declarations/WebpackOptions").Loader} Loader */ +/** @typedef {import("../../declarations/WebpackOptions").Mode} Mode */ +/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ +/** @typedef {import("../../declarations/WebpackOptions").Node} WebpackNode */ +/** @typedef {import("../../declarations/WebpackOptions").Optimization} 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").Target} Target */ +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */ +/** @typedef {import("./target").TargetProperties} TargetProperties */ + +/** + * @typedef {object} ResolvedOptions + * @property {PlatformTargetProperties | false} platform - platform target properties + */ + +const NODE_MODULES_REGEXP = /[\\/]node_modules[\\/]/i; +const DEFAULT_CACHE_NAME = "default"; + +/** + * Sets a constant default value when undefined + * @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; + } +}; + +/** + * Sets a dynamic default value when undefined, by calling the factory function + * @template T + * @template {keyof T} P + * @param {T} obj an object + * @param {P} prop a property of this object + * @param {function(): 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 {function(): 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); + } + } + } +}; + +/** + * @param {WebpackOptionsNormalized} options options to be modified + * @returns {void} + */ +const applyWebpackOptionsBaseDefaults = options => { + F(options, "context", () => process.cwd()); + applyInfrastructureLoggingDefaults(options.infrastructureLogging); +}; + +/** + * @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", () => (development ? "eval" : 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, "cache", () => + development ? { type: /** @type {"memory"} */ ("memory") } : false + ); + applyCacheDefaults(options.cache, { + name: name || DEFAULT_CACHE_NAME, + mode: mode || "production", + development, + cacheUnaffected: options.experiments.cacheUnaffected, + 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, + syncWebAssembly: + /** @type {NonNullable} */ + (options.experiments.syncWebAssembly), + asyncWebAssembly: + /** @type {NonNullable} */ + (options.experiments.asyncWebAssembly), + css: + /** @type {NonNullable} */ + (options.experiments.css), + futureDefaults, + isNode: targetProperties && targetProperties.node === true, + uniqueName: options.output.uniqueName, + targetProperties, + mode: options.mode + }); + + applyExternalsPresetsDefaults(options.externalsPresets, { + targetProperties, + buildHttp: Boolean(options.experiments.buildHttp) + }); + + 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) + }), + 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, + nwjs: targetProperties.nwjs, + electron: targetProperties.electron + } + }; +}; + +/** + * @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); + D(experiments, "syncWebAssembly", false); + D(experiments, "asyncWebAssembly", experiments.futureDefaults); + D(experiments, "outputModule", false); + D(experiments, "layers", false); + D(experiments, "lazyCompilation", undefined); + D(experiments, "buildHttp", undefined); + D(experiments, "cacheUnaffected", experiments.futureDefaults); + F(experiments, "css", () => (experiments.futureDefaults ? true : undefined)); + + // TODO webpack 6: remove this. topLevelAwait should be enabled by default + let shouldEnableTopLevelAwait = true; + if (typeof experiments.topLevelAwait === "boolean") { + shouldEnableTopLevelAwait = experiments.topLevelAwait; + } + D(experiments, "topLevelAwait", shouldEnableTopLevelAwait); + + if (typeof experiments.buildHttp === "object") { + D(experiments.buildHttp, "frozen", production); + D(experiments.buildHttp, "upgrade", false); + } +}; + +/** + * @param {CacheOptionsNormalized} cache options + * @param {object} options options + * @param {string} options.name name + * @param {Mode} options.mode mode + * @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 } +) => { + 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", "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; + } +}; + +/** + * @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, "resolve", () => + production ? { timestamp: true, hash: true } : { timestamp: true } + ); +}; + +/** + * @param {JavascriptParserOptions} parserOptions parser options + * @param {object} options options + * @param {boolean} options.futureDefaults is future defaults enabled + * @param {boolean} options.isNode is node target platform + * @returns {void} + */ +const applyJavascriptParserOptionsDefaults = ( + parserOptions, + { futureDefaults, isNode } +) => { + 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", true); + D(parserOptions, "dynamicImportMode", "lazy"); + D(parserOptions, "dynamicImportPrefetch", false); + D(parserOptions, "dynamicImportPreload", false); + D(parserOptions, "dynamicImportFetchPriority", false); + D(parserOptions, "createRequire", isNode); + if (futureDefaults) D(parserOptions, "exportsPresence", "error"); +}; + +/** + * @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); +}; + +/** + * @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.css is css 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 {TargetProperties | false} options.targetProperties target properties + * @param {Mode} options.mode mode + * @returns {void} + */ +const applyModuleDefaults = ( + module, + { + cache, + syncWebAssembly, + asyncWebAssembly, + css, + futureDefaults, + isNode, + uniqueName, + targetProperties, + mode + } +) => { + if (cache) { + D( + module, + "unsafeCache", + /** + * @param {Module} module module + * @returns {boolean | null | string} true, if we want to cache the module + */ + module => { + const name = module.nameForCondition(); + return name && 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( + module.parser[JSON_MODULE_TYPE], + "exportsDepth", + mode === "development" ? 1 : Infinity + ); + + applyJavascriptParserOptionsDefaults( + /** @type {NonNullable} */ + (module.parser.javascript), + { + futureDefaults, + isNode + } + ); + + if (css) { + F(module.parser, CSS_MODULE_TYPE, () => ({})); + + D(module.parser[CSS_MODULE_TYPE], "import", true); + D(module.parser[CSS_MODULE_TYPE], "url", true); + D(module.parser[CSS_MODULE_TYPE], "namedExports", true); + + F(module.generator, CSS_MODULE_TYPE, () => ({})); + + applyCssGeneratorOptionsDefaults( + /** @type {NonNullable} */ + (module.generator[CSS_MODULE_TYPE]), + { targetProperties } + ); + + const localIdentName = + uniqueName.length > 0 ? "[uniqueName]-[id]-[local]" : "[id]-[local]"; + + F(module.generator, CSS_MODULE_TYPE_AUTO, () => ({})); + D(module.generator[CSS_MODULE_TYPE_AUTO], "localIdentName", localIdentName); + D(module.generator[CSS_MODULE_TYPE_AUTO], "exportsConvention", "as-is"); + + F(module.generator, CSS_MODULE_TYPE_MODULE, () => ({})); + D( + module.generator[CSS_MODULE_TYPE_MODULE], + "localIdentName", + localIdentName + ); + D(module.generator[CSS_MODULE_TYPE_MODULE], "exportsConvention", "as-is"); + + F(module.generator, CSS_MODULE_TYPE_GLOBAL, () => ({})); + D( + module.generator[CSS_MODULE_TYPE_GLOBAL], + "localIdentName", + localIdentName + ); + D(module.generator[CSS_MODULE_TYPE_GLOBAL], "exportsConvention", "as-is"); + } + + A(module, "defaultRules", () => { + const esm = { + type: JAVASCRIPT_MODULE_TYPE_ESM, + resolve: { + byDependency: { + esm: { + fullySpecified: true + } + } + } + }; + const commonjs = { + type: JAVASCRIPT_MODULE_TYPE_DYNAMIC + }; + /** @type {RuleSetRules} */ + const rules = [ + { + mimetype: "application/node", + type: JAVASCRIPT_MODULE_TYPE_AUTO + }, + { + test: /\.json$/i, + type: JSON_MODULE_TYPE + }, + { + mimetype: "application/json", + type: JSON_MODULE_TYPE + }, + { + test: /\.mjs$/i, + ...esm + }, + { + test: /\.js$/i, + descriptionData: { + type: "module" + }, + ...esm + }, + { + test: /\.cjs$/i, + ...commonjs + }, + { + test: /\.js$/i, + descriptionData: { + type: "commonjs" + }, + ...commonjs + }, + { + mimetype: { + or: ["text/javascript", "application/javascript"] + }, + ...esm + } + ]; + if (asyncWebAssembly) { + const wasm = { + type: WEBASSEMBLY_MODULE_TYPE_ASYNC, + rules: [ + { + descriptionData: { + type: "module" + }, + resolve: { + fullySpecified: true + } + } + ] + }; + rules.push({ + test: /\.wasm$/i, + ...wasm + }); + rules.push({ + mimetype: "application/wasm", + ...wasm + }); + } else if (syncWebAssembly) { + const wasm = { + type: WEBASSEMBLY_MODULE_TYPE_SYNC, + rules: [ + { + descriptionData: { + type: "module" + }, + resolve: { + fullySpecified: true + } + } + ] + }; + rules.push({ + test: /\.wasm$/i, + ...wasm + }); + rules.push({ + mimetype: "application/wasm", + ...wasm + }); + } + if (css) { + const resolve = { + fullySpecified: true, + preferRelative: true + }; + rules.push({ + test: /\.css$/i, + type: CSS_MODULE_TYPE_AUTO, + resolve + }); + rules.push({ + mimetype: "text/css+module", + type: CSS_MODULE_TYPE_MODULE, + resolve + }); + rules.push({ + mimetype: "text/css", + type: CSS_MODULE_TYPE, + resolve + }); + } + rules.push( + { + dependency: "url", + oneOf: [ + { + scheme: /^data$/, + type: ASSET_MODULE_TYPE_INLINE + }, + { + type: ASSET_MODULE_TYPE_RESOURCE + } + ] + }, + { + assert: { type: JSON_MODULE_TYPE }, + type: JSON_MODULE_TYPE + }, + { + with: { type: JSON_MODULE_TYPE }, + type: JSON_MODULE_TYPE + } + ); + return rules; + }); +}; + +/** + * @param {Output} output options + * @param {object} options options + * @param {string} options.context context + * @param {TargetProperties | false} options.targetProperties target properties + * @param {boolean} options.isAffectedByBrowserslist is affected by browserslist + * @param {boolean} options.outputModule is outputModule experiment enabled + * @param {boolean} options.development is development mode + * @param {Entry} options.entry entry option + * @param {boolean} options.futureDefaults is future defaults enabled + * @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled + * @returns {void} + */ +const applyOutputDefaults = ( + output, + { + context, + targetProperties: tp, + isAffectedByBrowserslist, + outputModule, + development, + entry, + futureDefaults, + asyncWebAssembly + } +) => { + /** + * @param {Library=} library the library option + * @returns {string} a readable library name + */ + const getLibraryName = library => { + const libraryName = + typeof library === "object" && + library && + !Array.isArray(library) && + "type" in library + ? library.name + : /** @type {LibraryName} */ (library); + if (Array.isArray(libraryName)) { + return libraryName.join("."); + } else if (typeof libraryName === "object") { + return getLibraryName(libraryName.root); + } else if (typeof libraryName === "string") { + return libraryName; + } + return ""; + }; + + F(output, "uniqueName", () => { + const libraryName = getLibraryName(output.library).replace( + /^\[(\\*[\w:]+\\*)\](\.)|(\.)\[(\\*[\w:]+\\*)\](?=\.|$)|\[(\\*[\w:]+\\*)\]/g, + (m, a, d1, d2, b, c) => { + const content = a || b || c; + return content.startsWith("\\") && content.endsWith("\\") + ? `${d2 || ""}[${content.slice(1, -1)}]${d1 || ""}` + : ""; + } + ); + if (libraryName) return libraryName; + const pkgPath = path.resolve(context, "package.json"); + try { + const packageInfo = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); + return packageInfo.name || ""; + } catch (err) { + if (/** @type {Error & { code: string }} */ (err).code !== "ENOENT") { + /** @type {Error & { code: string }} */ + (err).message += + `\nwhile determining default 'output.uniqueName' from 'name' in ${pkgPath}`; + throw err; + } + return ""; + } + }); + + F(output, "module", () => Boolean(outputModule)); + + const environment = /** @type {Environment} */ (output.environment); + /** + * @param {boolean | undefined} v value + * @returns {boolean} true, when v is truthy or undefined + */ + const optimistic = v => v || v === undefined; + /** + * @param {boolean | undefined} v value + * @param {boolean | undefined} c condition + * @returns {boolean | undefined} true, when v is truthy or undefined, or c is truthy + */ + const conditionallyOptimistic = (v, c) => (v === undefined && c) || v; + + F( + environment, + "globalThis", + () => /** @type {boolean | undefined} */ (tp && tp.globalThis) + ); + F( + environment, + "bigIntLiteral", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.bigIntLiteral)) + ); + F( + environment, + "const", + () => tp && optimistic(/** @type {boolean | undefined} */ (tp.const)) + ); + F( + environment, + "arrowFunction", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.arrowFunction)) + ); + F( + environment, + "asyncFunction", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.asyncFunction)) + ); + F( + environment, + "forOf", + () => tp && optimistic(/** @type {boolean | undefined} */ (tp.forOf)) + ); + F( + environment, + "destructuring", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.destructuring)) + ); + F( + environment, + "optionalChaining", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.optionalChaining)) + ); + F( + environment, + "nodePrefixForCoreModules", + () => + tp && + optimistic( + /** @type {boolean | undefined} */ (tp.nodePrefixForCoreModules) + ) + ); + F( + environment, + "templateLiteral", + () => + tp && optimistic(/** @type {boolean | undefined} */ (tp.templateLiteral)) + ); + F(environment, "dynamicImport", () => + conditionallyOptimistic( + /** @type {boolean | undefined} */ (tp && tp.dynamicImport), + output.module + ) + ); + F(environment, "dynamicImportInWorker", () => + conditionallyOptimistic( + /** @type {boolean | undefined} */ (tp && tp.dynamicImportInWorker), + output.module + ) + ); + F(environment, "module", () => + conditionallyOptimistic( + /** @type {boolean | undefined} */ (tp && tp.module), + output.module + ) + ); + F( + environment, + "document", + () => tp && optimistic(/** @type {boolean | undefined} */ (tp.document)) + ); + + D(output, "filename", output.module ? "[name].mjs" : "[name].js"); + F(output, "iife", () => !output.module); + D(output, "importFunctionName", "import"); + D(output, "importMetaName", "import.meta"); + F(output, "chunkFilename", () => { + const filename = + /** @type {NonNullable} */ + (output.filename); + if (typeof filename !== "function") { + const hasName = filename.includes("[name]"); + const hasId = filename.includes("[id]"); + const hasChunkHash = filename.includes("[chunkhash]"); + const hasContentHash = filename.includes("[contenthash]"); + // Anything changing depending on chunk is fine + if (hasChunkHash || hasContentHash || hasName || hasId) return filename; + // Otherwise prefix "[id]." in front of the basename to make it changing + return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2"); + } + return output.module ? "[id].mjs" : "[id].js"; + }); + F(output, "cssFilename", () => { + const filename = + /** @type {NonNullable} */ + (output.filename); + if (typeof filename !== "function") { + return filename.replace(/\.[mc]?js(\?|$)/, ".css$1"); + } + return "[id].css"; + }); + F(output, "cssChunkFilename", () => { + const chunkFilename = + /** @type {NonNullable} */ + (output.chunkFilename); + if (typeof chunkFilename !== "function") { + return chunkFilename.replace(/\.[mc]?js(\?|$)/, ".css$1"); + } + return "[id].css"; + }); + D(output, "assetModuleFilename", "[hash][ext][query]"); + D(output, "webassemblyModuleFilename", "[hash].module.wasm"); + D(output, "compareBeforeEmit", true); + D(output, "charset", true); + const uniqueNameId = Template.toIdentifier( + /** @type {NonNullable} */ (output.uniqueName) + ); + F(output, "hotUpdateGlobal", () => `webpackHotUpdate${uniqueNameId}`); + F(output, "chunkLoadingGlobal", () => `webpackChunk${uniqueNameId}`); + F(output, "globalObject", () => { + if (tp) { + if (tp.global) return "global"; + if (tp.globalThis) return "globalThis"; + } + return "self"; + }); + F(output, "chunkFormat", () => { + if (tp) { + const helpMessage = isAffectedByBrowserslist + ? "Make sure that your 'browserslist' includes only platforms that support these features or select an appropriate 'target' to allow selecting a chunk format by default. Alternatively specify the 'output.chunkFormat' directly." + : "Select an appropriate 'target' to allow selecting one by default, or specify the 'output.chunkFormat' directly."; + if (output.module) { + if (environment.dynamicImport) return "module"; + if (tp.document) return "array-push"; + throw new Error( + "For the selected environment is no default ESM chunk format available:\n" + + "ESM exports can be chosen when 'import()' is available.\n" + + `JSONP Array push can be chosen when 'document' is available.\n${ + helpMessage + }` + ); + } else { + if (tp.document) return "array-push"; + if (tp.require) return "commonjs"; + if (tp.nodeBuiltins) return "commonjs"; + if (tp.importScripts) return "array-push"; + throw new Error( + "For the selected environment is no default script chunk format available:\n" + + "JSONP Array push can be chosen when 'document' or 'importScripts' is available.\n" + + `CommonJs exports can be chosen when 'require' or node builtins are available.\n${ + helpMessage + }` + ); + } + } + throw new Error( + "Chunk format can't be selected by default when no target is specified" + ); + }); + D(output, "asyncChunks", true); + F(output, "chunkLoading", () => { + if (tp) { + switch (output.chunkFormat) { + case "array-push": + if (tp.document) return "jsonp"; + if (tp.importScripts) return "import-scripts"; + break; + case "commonjs": + if (tp.require) return "require"; + if (tp.nodeBuiltins) return "async-node"; + break; + case "module": + if (environment.dynamicImport) return "import"; + break; + } + if ( + (tp.require === null || + tp.nodeBuiltins === null || + tp.document === null || + tp.importScripts === null) && + output.module && + environment.dynamicImport + ) { + return "universal"; + } + } + return false; + }); + F(output, "workerChunkLoading", () => { + if (tp) { + switch (output.chunkFormat) { + case "array-push": + if (tp.importScriptsInWorker) return "import-scripts"; + break; + case "commonjs": + if (tp.require) return "require"; + if (tp.nodeBuiltins) return "async-node"; + break; + case "module": + if (environment.dynamicImportInWorker) return "import"; + break; + } + if ( + (tp.require === null || + tp.nodeBuiltins === null || + tp.importScriptsInWorker === null) && + output.module && + environment.dynamicImport + ) { + return "universal"; + } + } + return false; + }); + F(output, "wasmLoading", () => { + if (tp) { + if (tp.fetchWasm) return "fetch"; + if (tp.nodeBuiltins) return "async-node"; + if ( + (tp.nodeBuiltins === null || tp.fetchWasm === null) && + asyncWebAssembly && + output.module && + environment.dynamicImport + ) { + return "universal"; + } + } + return false; + }); + F(output, "workerWasmLoading", () => output.wasmLoading); + F(output, "devtoolNamespace", () => output.uniqueName); + if (output.library) { + F(output.library, "type", () => (output.module ? "module" : "var")); + } + F(output, "path", () => path.join(process.cwd(), "dist")); + F(output, "pathinfo", () => development); + D(output, "sourceMapFilename", "[file].map[query]"); + D( + output, + "hotUpdateChunkFilename", + `[id].[fullhash].hot-update.${output.module ? "mjs" : "js"}` + ); + D(output, "hotUpdateMainFilename", "[runtime].[fullhash].hot-update.json"); + D(output, "crossOriginLoading", false); + F(output, "scriptType", () => (output.module ? "module" : false)); + D( + output, + "publicPath", + (tp && (tp.document || tp.importScripts)) || output.scriptType === "module" + ? "auto" + : "" + ); + D(output, "workerPublicPath", ""); + D(output, "chunkLoadTimeout", 120000); + D(output, "hashFunction", futureDefaults ? "xxhash64" : "md4"); + D(output, "hashDigest", "hex"); + D(output, "hashDigestLength", futureDefaults ? 16 : 20); + D(output, "strictModuleErrorHandling", false); + D(output, "strictModuleExceptionHandling", false); + + const { trustedTypes } = output; + if (trustedTypes) { + F( + trustedTypes, + "policyName", + () => + /** @type {NonNullable} */ + (output.uniqueName).replace(/[^a-zA-Z0-9\-#=_/@.%]+/g, "_") || "webpack" + ); + D(trustedTypes, "onPolicyCreationFailure", "stop"); + } + + /** + * @param {function(EntryDescription): void} fn iterator + * @returns {void} + */ + const forEachEntry = fn => { + for (const name of Object.keys(entry)) { + fn(/** @type {{[k: string] : EntryDescription}} */ (entry)[name]); + } + }; + A(output, "enabledLibraryTypes", () => { + /** @type {LibraryType[]} */ + const enabledLibraryTypes = []; + if (output.library) { + enabledLibraryTypes.push(output.library.type); + } + forEachEntry(desc => { + if (desc.library) { + enabledLibraryTypes.push(desc.library.type); + } + }); + return enabledLibraryTypes; + }); + + A(output, "enabledChunkLoadingTypes", () => { + const enabledChunkLoadingTypes = new Set(); + if (output.chunkLoading) { + enabledChunkLoadingTypes.add(output.chunkLoading); + } + if (output.workerChunkLoading) { + enabledChunkLoadingTypes.add(output.workerChunkLoading); + } + forEachEntry(desc => { + if (desc.chunkLoading) { + enabledChunkLoadingTypes.add(desc.chunkLoading); + } + }); + return Array.from(enabledChunkLoadingTypes); + }); + + A(output, "enabledWasmLoadingTypes", () => { + const enabledWasmLoadingTypes = new Set(); + if (output.wasmLoading) { + enabledWasmLoadingTypes.add(output.wasmLoading); + } + if (output.workerWasmLoading) { + enabledWasmLoadingTypes.add(output.workerWasmLoading); + } + forEachEntry(desc => { + if (desc.wasmLoading) { + enabledWasmLoadingTypes.add(desc.wasmLoading); + } + }); + return Array.from(enabledWasmLoadingTypes); + }); +}; + +/** + * @param {ExternalsPresets} externalsPresets options + * @param {object} options options + * @param {TargetProperties | false} options.targetProperties target properties + * @param {boolean} options.buildHttp buildHttp experiment enabled + * @returns {void} + */ +const applyExternalsPresetsDefaults = ( + externalsPresets, + { targetProperties, buildHttp } +) => { + D( + externalsPresets, + "web", + /** @type {boolean | undefined} */ + (!buildHttp && targetProperties && targetProperties.web) + ); + D( + externalsPresets, + "node", + /** @type {boolean | undefined} */ + (targetProperties && targetProperties.node) + ); + D( + externalsPresets, + "nwjs", + /** @type {boolean | undefined} */ + (targetProperties && targetProperties.nwjs) + ); + D( + externalsPresets, + "electron", + /** @type {boolean | undefined} */ + (targetProperties && targetProperties.electron) + ); + D( + externalsPresets, + "electronMain", + /** @type {boolean | undefined} */ + ( + targetProperties && + targetProperties.electron && + targetProperties.electronMain + ) + ); + D( + externalsPresets, + "electronPreload", + /** @type {boolean | undefined} */ + ( + targetProperties && + targetProperties.electron && + targetProperties.electronPreload + ) + ); + D( + externalsPresets, + "electronRenderer", + /** @type {boolean | undefined} */ + ( + targetProperties && + targetProperties.electron && + targetProperties.electronRenderer + ) + ); +}; + +/** + * @param {Loader} loader options + * @param {object} options options + * @param {TargetProperties | false} options.targetProperties target properties + * @param {Environment} options.environment environment + * @returns {void} + */ +const applyLoaderDefaults = (loader, { targetProperties, environment }) => { + F(loader, "target", () => { + if (targetProperties) { + if (targetProperties.electron) { + if (targetProperties.electronMain) return "electron-main"; + if (targetProperties.electronPreload) return "electron-preload"; + if (targetProperties.electronRenderer) return "electron-renderer"; + return "electron"; + } + if (targetProperties.nwjs) return "nwjs"; + if (targetProperties.node) return "node"; + if (targetProperties.web) return "web"; + } + }); + D(loader, "environment", environment); +}; + +/** + * @param {WebpackNode} node options + * @param {object} options options + * @param {TargetProperties | false} options.targetProperties target properties + * @param {boolean} options.futureDefaults is future defaults enabled + * @param {boolean} options.outputModule is output type is module + * @returns {void} + */ +const applyNodeDefaults = ( + node, + { futureDefaults, outputModule, targetProperties } +) => { + if (node === false) return; + + F(node, "global", () => { + if (targetProperties && targetProperties.global) return false; + // TODO webpack 6 should always default to false + return futureDefaults ? "warn" : true; + }); + + const handlerForNames = () => { + if (targetProperties && targetProperties.node) + return outputModule ? "node-module" : "eval-only"; + // TODO webpack 6 should always default to false + return futureDefaults ? "warn-mock" : "mock"; + }; + + F(node, "__filename", handlerForNames); + F(node, "__dirname", handlerForNames); +}; + +/** + * @param {Performance} performance options + * @param {object} options options + * @param {boolean} options.production is production + * @returns {void} + */ +const applyPerformanceDefaults = (performance, { production }) => { + if (performance === false) return; + D(performance, "maxAssetSize", 250000); + D(performance, "maxEntrypointSize", 250000); + F(performance, "hints", () => (production ? "warning" : false)); +}; + +/** + * @param {Optimization} optimization options + * @param {object} options options + * @param {boolean} options.production is production + * @param {boolean} options.development is development + * @param {boolean} options.css is css enabled + * @param {boolean} options.records using records + * @returns {void} + */ +const applyOptimizationDefaults = ( + optimization, + { production, development, css, records } +) => { + D(optimization, "removeAvailableModules", false); + D(optimization, "removeEmptyChunks", true); + D(optimization, "mergeDuplicateChunks", true); + D(optimization, "flagIncludedChunks", production); + F(optimization, "moduleIds", () => { + if (production) return "deterministic"; + if (development) return "named"; + return "natural"; + }); + F(optimization, "chunkIds", () => { + if (production) return "deterministic"; + if (development) return "named"; + return "natural"; + }); + F(optimization, "sideEffects", () => (production ? true : "flag")); + D(optimization, "providedExports", true); + D(optimization, "usedExports", production); + D(optimization, "innerGraph", production); + D(optimization, "mangleExports", production); + D(optimization, "concatenateModules", production); + D(optimization, "avoidEntryIife", production); + D(optimization, "runtimeChunk", false); + D(optimization, "emitOnErrors", !production); + D(optimization, "checkWasmTypes", production); + D(optimization, "mangleWasmImports", false); + D(optimization, "portableRecords", records); + D(optimization, "realContentHash", production); + D(optimization, "minimize", production); + A(optimization, "minimizer", () => [ + { + apply: compiler => { + // Lazy load the Terser plugin + const TerserPlugin = require("terser-webpack-plugin"); + new TerserPlugin({ + terserOptions: { + compress: { + passes: 2 + } + } + }).apply(compiler); + } + } + ]); + F(optimization, "nodeEnv", () => { + if (production) return "production"; + if (development) return "development"; + return false; + }); + const { splitChunks } = optimization; + if (splitChunks) { + A(splitChunks, "defaultSizeTypes", () => + css ? ["javascript", "css", "unknown"] : ["javascript", "unknown"] + ); + D(splitChunks, "hidePathInfo", production); + D(splitChunks, "chunks", "async"); + D(splitChunks, "usedExports", optimization.usedExports === true); + D(splitChunks, "minChunks", 1); + F(splitChunks, "minSize", () => (production ? 20000 : 10000)); + F(splitChunks, "minRemainingSize", () => (development ? 0 : undefined)); + F(splitChunks, "enforceSizeThreshold", () => (production ? 50000 : 30000)); + F(splitChunks, "maxAsyncRequests", () => (production ? 30 : Infinity)); + F(splitChunks, "maxInitialRequests", () => (production ? 30 : Infinity)); + D(splitChunks, "automaticNameDelimiter", "-"); + const cacheGroups = + /** @type {NonNullable} */ + (splitChunks.cacheGroups); + F(cacheGroups, "default", () => ({ + idHint: "", + reuseExistingChunk: true, + minChunks: 2, + priority: -20 + })); + F(cacheGroups, "defaultVendors", () => ({ + idHint: "vendors", + reuseExistingChunk: true, + test: NODE_MODULES_REGEXP, + priority: -10 + })); + } +}; + +/** + * @param {object} options options + * @param {boolean} options.cache is cache enable + * @param {string} options.context build context + * @param {TargetProperties | false} options.targetProperties target properties + * @param {Mode} options.mode mode + * @param {boolean} options.css is css enabled + * @returns {ResolveOptions} resolve options + */ +const getResolveDefaults = ({ + cache, + context, + targetProperties, + mode, + css +}) => { + /** @type {string[]} */ + const conditions = ["webpack"]; + + conditions.push(mode === "development" ? "development" : "production"); + + if (targetProperties) { + if (targetProperties.webworker) conditions.push("worker"); + if (targetProperties.node) conditions.push("node"); + if (targetProperties.web) conditions.push("browser"); + if (targetProperties.electron) conditions.push("electron"); + if (targetProperties.nwjs) conditions.push("nwjs"); + } + + const jsExtensions = [".js", ".json", ".wasm"]; + + const tp = targetProperties; + const browserField = + tp && tp.web && (!tp.node || (tp.electron && tp.electronRenderer)); + + /** @type {function(): ResolveOptions} */ + const cjsDeps = () => ({ + aliasFields: browserField ? ["browser"] : [], + mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."], + conditionNames: ["require", "module", "..."], + extensions: [...jsExtensions] + }); + /** @type {function(): ResolveOptions} */ + const esmDeps = () => ({ + aliasFields: browserField ? ["browser"] : [], + mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."], + conditionNames: ["import", "module", "..."], + extensions: [...jsExtensions] + }); + + /** @type {ResolveOptions} */ + const resolveOptions = { + cache, + modules: ["node_modules"], + conditionNames: conditions, + mainFiles: ["index"], + extensions: [], + aliasFields: [], + exportsFields: ["exports"], + roots: [context], + mainFields: ["main"], + importsFields: ["imports"], + byDependency: { + wasm: esmDeps(), + esm: esmDeps(), + loaderImport: esmDeps(), + url: { + preferRelative: true + }, + worker: { + ...esmDeps(), + preferRelative: true + }, + commonjs: cjsDeps(), + amd: cjsDeps(), + // for backward-compat: loadModule + loader: cjsDeps(), + // for backward-compat: Custom Dependency + unknown: cjsDeps(), + // for backward-compat: getResolve without dependencyType + undefined: cjsDeps() + } + }; + + if (css) { + const styleConditions = []; + + styleConditions.push("webpack"); + styleConditions.push(mode === "development" ? "development" : "production"); + styleConditions.push("style"); + + resolveOptions.byDependency["css-import"] = { + // We avoid using any main files because we have to be consistent with CSS `@import` + // and CSS `@import` does not handle `main` files in directories, + // you should always specify the full URL for styles + mainFiles: [], + mainFields: ["style", "..."], + conditionNames: styleConditions, + extensions: [".css"], + preferRelative: true + }; + } + + return resolveOptions; +}; + +/** + * @param {object} options options + * @param {boolean} options.cache is cache enable + * @returns {ResolveOptions} resolve options + */ +const getResolveLoaderDefaults = ({ cache }) => { + /** @type {ResolveOptions} */ + const resolveOptions = { + cache, + conditionNames: ["loader", "require", "node"], + exportsFields: ["exports"], + mainFields: ["loader", "main"], + extensions: [".js"], + mainFiles: ["index"] + }; + + return resolveOptions; +}; + +/** + * @param {InfrastructureLogging} infrastructureLogging options + * @returns {void} + */ +const applyInfrastructureLoggingDefaults = infrastructureLogging => { + F(infrastructureLogging, "stream", () => process.stderr); + const tty = + /** @type {any} */ (infrastructureLogging.stream).isTTY && + process.env.TERM !== "dumb"; + D(infrastructureLogging, "level", "info"); + D(infrastructureLogging, "debug", false); + D(infrastructureLogging, "colors", tty); + D(infrastructureLogging, "appendOnly", !tty); +}; + +module.exports.applyWebpackOptionsBaseDefaults = + applyWebpackOptionsBaseDefaults; +module.exports.applyWebpackOptionsDefaults = applyWebpackOptionsDefaults; diff --git a/webpack-lib/lib/config/normalization.js b/webpack-lib/lib/config/normalization.js new file mode 100644 index 00000000000..3ed0c81320b --- /dev/null +++ b/webpack-lib/lib/config/normalization.js @@ -0,0 +1,558 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); + +/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptions */ +/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").EntryStatic} EntryStatic */ +/** @typedef {import("../../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").Externals} Externals */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptionsNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunk} OptimizationRuntimeChunk */ +/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunkNormalized} OptimizationRuntimeChunkNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ +/** @typedef {import("../../declarations/WebpackOptions").Plugins} Plugins */ +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ +/** @typedef {import("../Entrypoint")} Entrypoint */ + +const handledDeprecatedNoEmitOnErrors = util.deprecate( + /** + * @param {boolean} noEmitOnErrors no emit on errors + * @param {boolean | undefined} emitOnErrors emit on errors + * @returns {boolean} emit on errors + */ + (noEmitOnErrors, emitOnErrors) => { + if (emitOnErrors !== undefined && !noEmitOnErrors === !emitOnErrors) { + throw new Error( + "Conflicting use of 'optimization.noEmitOnErrors' and 'optimization.emitOnErrors'. Remove deprecated 'optimization.noEmitOnErrors' from config." + ); + } + return !noEmitOnErrors; + }, + "optimization.noEmitOnErrors is deprecated in favor of optimization.emitOnErrors", + "DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS" +); + +/** + * @template T + * @template R + * @param {T|undefined} value value or not + * @param {function(T): R} fn nested handler + * @returns {R} result value + */ +const nestedConfig = (value, fn) => + value === undefined ? fn(/** @type {T} */ ({})) : fn(value); + +/** + * @template T + * @param {T|undefined} value value or not + * @returns {T} result value + */ +const cloneObject = value => /** @type {T} */ ({ ...value }); +/** + * @template T + * @template R + * @param {T|undefined} value value or not + * @param {function(T): R} fn nested handler + * @returns {R|undefined} result value + */ +const optionalNestedConfig = (value, fn) => + value === undefined ? undefined : fn(value); + +/** + * @template T + * @template R + * @param {T[]|undefined} value array or not + * @param {function(T[]): R[]} fn nested handler + * @returns {R[]|undefined} cloned value + */ +const nestedArray = (value, fn) => (Array.isArray(value) ? fn(value) : fn([])); + +/** + * @template T + * @template R + * @param {T[]|undefined} value array or not + * @param {function(T[]): R[]} fn nested handler + * @returns {R[]|undefined} cloned value + */ +const optionalNestedArray = (value, fn) => + Array.isArray(value) ? fn(value) : undefined; + +/** + * @template T + * @template R + * @param {Record|undefined} value value or not + * @param {function(T): R} fn nested handler + * @param {Record=} customKeys custom nested handler for some keys + * @returns {Record} result value + */ +const keyedNestedConfig = (value, fn, customKeys) => { + /* eslint-disable no-sequences */ + const result = + value === undefined + ? {} + : Object.keys(value).reduce( + (obj, key) => ( + (obj[key] = ( + customKeys && key in customKeys ? customKeys[key] : fn + )(value[key])), + obj + ), + /** @type {Record} */ ({}) + ); + /* eslint-enable no-sequences */ + if (customKeys) { + for (const key of Object.keys(customKeys)) { + if (!(key in result)) { + result[key] = customKeys[key](/** @type {T} */ ({})); + } + } + } + return result; +}; + +/** + * @param {WebpackOptions} config input config + * @returns {WebpackOptionsNormalized} normalized options + */ +const getNormalizedWebpackOptions = config => ({ + amd: config.amd, + bail: config.bail, + cache: + /** @type {NonNullable} */ + ( + optionalNestedConfig(config.cache, cache => { + if (cache === false) return false; + if (cache === true) { + return { + type: "memory", + maxGenerations: undefined + }; + } + switch (cache.type) { + case "filesystem": + return { + type: "filesystem", + allowCollectingMemory: cache.allowCollectingMemory, + maxMemoryGenerations: cache.maxMemoryGenerations, + maxAge: cache.maxAge, + profile: cache.profile, + buildDependencies: cloneObject(cache.buildDependencies), + cacheDirectory: cache.cacheDirectory, + cacheLocation: cache.cacheLocation, + hashAlgorithm: cache.hashAlgorithm, + compression: cache.compression, + idleTimeout: cache.idleTimeout, + idleTimeoutForInitialStore: cache.idleTimeoutForInitialStore, + idleTimeoutAfterLargeChanges: cache.idleTimeoutAfterLargeChanges, + name: cache.name, + store: cache.store, + version: cache.version, + readonly: cache.readonly + }; + case undefined: + case "memory": + return { + type: "memory", + maxGenerations: cache.maxGenerations + }; + default: + // @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339) + throw new Error(`Not implemented cache.type ${cache.type}`); + } + }) + ), + context: config.context, + dependencies: config.dependencies, + devServer: optionalNestedConfig(config.devServer, devServer => { + if (devServer === false) return false; + return { ...devServer }; + }), + devtool: config.devtool, + entry: + config.entry === undefined + ? { main: {} } + : typeof config.entry === "function" + ? ( + fn => () => + Promise.resolve().then(fn).then(getNormalizedEntryStatic) + )(config.entry) + : getNormalizedEntryStatic(config.entry), + experiments: nestedConfig(config.experiments, experiments => ({ + ...experiments, + buildHttp: optionalNestedConfig(experiments.buildHttp, options => + Array.isArray(options) ? { allowedUris: options } : options + ), + lazyCompilation: optionalNestedConfig( + experiments.lazyCompilation, + options => (options === true ? {} : options) + ) + })), + externals: /** @type {NonNullable} */ (config.externals), + externalsPresets: cloneObject(config.externalsPresets), + externalsType: config.externalsType, + ignoreWarnings: config.ignoreWarnings + ? config.ignoreWarnings.map(ignore => { + if (typeof ignore === "function") return ignore; + const i = ignore instanceof RegExp ? { message: ignore } : ignore; + return (warning, { requestShortener }) => { + if (!i.message && !i.module && !i.file) return false; + if (i.message && !i.message.test(warning.message)) { + return false; + } + if ( + i.module && + (!warning.module || + !i.module.test( + warning.module.readableIdentifier(requestShortener) + )) + ) { + return false; + } + if (i.file && (!warning.file || !i.file.test(warning.file))) { + return false; + } + return true; + }; + }) + : undefined, + infrastructureLogging: cloneObject(config.infrastructureLogging), + loader: cloneObject(config.loader), + mode: config.mode, + module: + /** @type {ModuleOptionsNormalized} */ + ( + nestedConfig(config.module, module => ({ + noParse: module.noParse, + unsafeCache: module.unsafeCache, + parser: keyedNestedConfig(module.parser, cloneObject, { + javascript: parserOptions => ({ + unknownContextRequest: module.unknownContextRequest, + unknownContextRegExp: module.unknownContextRegExp, + unknownContextRecursive: module.unknownContextRecursive, + unknownContextCritical: module.unknownContextCritical, + exprContextRequest: module.exprContextRequest, + exprContextRegExp: module.exprContextRegExp, + exprContextRecursive: module.exprContextRecursive, + exprContextCritical: module.exprContextCritical, + wrappedContextRegExp: module.wrappedContextRegExp, + wrappedContextRecursive: module.wrappedContextRecursive, + wrappedContextCritical: module.wrappedContextCritical, + // TODO webpack 6 remove + strictExportPresence: module.strictExportPresence, + strictThisContextOnImports: module.strictThisContextOnImports, + ...parserOptions + }) + }), + generator: cloneObject(module.generator), + defaultRules: optionalNestedArray(module.defaultRules, r => [...r]), + rules: nestedArray(module.rules, r => [...r]) + })) + ), + name: config.name, + node: nestedConfig( + config.node, + node => + node && { + ...node + } + ), + optimization: nestedConfig(config.optimization, optimization => ({ + ...optimization, + runtimeChunk: getNormalizedOptimizationRuntimeChunk( + optimization.runtimeChunk + ), + splitChunks: nestedConfig( + optimization.splitChunks, + splitChunks => + splitChunks && { + ...splitChunks, + defaultSizeTypes: splitChunks.defaultSizeTypes + ? [...splitChunks.defaultSizeTypes] + : ["..."], + cacheGroups: cloneObject(splitChunks.cacheGroups) + } + ), + emitOnErrors: + optimization.noEmitOnErrors !== undefined + ? handledDeprecatedNoEmitOnErrors( + optimization.noEmitOnErrors, + optimization.emitOnErrors + ) + : optimization.emitOnErrors + })), + output: nestedConfig(config.output, output => { + const { library } = output; + const libraryAsName = /** @type {LibraryName} */ (library); + const libraryBase = + typeof library === "object" && + library && + !Array.isArray(library) && + "type" in library + ? library + : libraryAsName || output.libraryTarget + ? /** @type {LibraryOptions} */ ({ + name: libraryAsName + }) + : undefined; + /** @type {OutputNormalized} */ + const result = { + assetModuleFilename: output.assetModuleFilename, + asyncChunks: output.asyncChunks, + charset: output.charset, + chunkFilename: output.chunkFilename, + chunkFormat: output.chunkFormat, + chunkLoading: output.chunkLoading, + chunkLoadingGlobal: output.chunkLoadingGlobal, + chunkLoadTimeout: output.chunkLoadTimeout, + cssFilename: output.cssFilename, + cssChunkFilename: output.cssChunkFilename, + clean: output.clean, + compareBeforeEmit: output.compareBeforeEmit, + crossOriginLoading: output.crossOriginLoading, + devtoolFallbackModuleFilenameTemplate: + output.devtoolFallbackModuleFilenameTemplate, + devtoolModuleFilenameTemplate: output.devtoolModuleFilenameTemplate, + devtoolNamespace: output.devtoolNamespace, + environment: cloneObject(output.environment), + enabledChunkLoadingTypes: output.enabledChunkLoadingTypes + ? [...output.enabledChunkLoadingTypes] + : ["..."], + enabledLibraryTypes: output.enabledLibraryTypes + ? [...output.enabledLibraryTypes] + : ["..."], + enabledWasmLoadingTypes: output.enabledWasmLoadingTypes + ? [...output.enabledWasmLoadingTypes] + : ["..."], + filename: output.filename, + globalObject: output.globalObject, + hashDigest: output.hashDigest, + hashDigestLength: output.hashDigestLength, + hashFunction: output.hashFunction, + hashSalt: output.hashSalt, + hotUpdateChunkFilename: output.hotUpdateChunkFilename, + hotUpdateGlobal: output.hotUpdateGlobal, + hotUpdateMainFilename: output.hotUpdateMainFilename, + ignoreBrowserWarnings: output.ignoreBrowserWarnings, + iife: output.iife, + importFunctionName: output.importFunctionName, + importMetaName: output.importMetaName, + scriptType: output.scriptType, + library: libraryBase && { + type: + output.libraryTarget !== undefined + ? output.libraryTarget + : libraryBase.type, + auxiliaryComment: + output.auxiliaryComment !== undefined + ? output.auxiliaryComment + : libraryBase.auxiliaryComment, + amdContainer: + output.amdContainer !== undefined + ? output.amdContainer + : libraryBase.amdContainer, + export: + output.libraryExport !== undefined + ? output.libraryExport + : libraryBase.export, + name: libraryBase.name, + umdNamedDefine: + output.umdNamedDefine !== undefined + ? output.umdNamedDefine + : libraryBase.umdNamedDefine + }, + module: output.module, + path: output.path, + pathinfo: output.pathinfo, + publicPath: output.publicPath, + sourceMapFilename: output.sourceMapFilename, + sourcePrefix: output.sourcePrefix, + strictModuleErrorHandling: output.strictModuleErrorHandling, + strictModuleExceptionHandling: output.strictModuleExceptionHandling, + trustedTypes: optionalNestedConfig(output.trustedTypes, trustedTypes => { + if (trustedTypes === true) return {}; + if (typeof trustedTypes === "string") + return { policyName: trustedTypes }; + return { ...trustedTypes }; + }), + uniqueName: output.uniqueName, + wasmLoading: output.wasmLoading, + webassemblyModuleFilename: output.webassemblyModuleFilename, + workerPublicPath: output.workerPublicPath, + workerChunkLoading: output.workerChunkLoading, + workerWasmLoading: output.workerWasmLoading + }; + return result; + }), + parallelism: config.parallelism, + performance: optionalNestedConfig(config.performance, performance => { + if (performance === false) return false; + return { + ...performance + }; + }), + plugins: /** @type {Plugins} */ (nestedArray(config.plugins, p => [...p])), + profile: config.profile, + recordsInputPath: + config.recordsInputPath !== undefined + ? config.recordsInputPath + : config.recordsPath, + recordsOutputPath: + config.recordsOutputPath !== undefined + ? config.recordsOutputPath + : config.recordsPath, + resolve: nestedConfig(config.resolve, resolve => ({ + ...resolve, + byDependency: keyedNestedConfig(resolve.byDependency, cloneObject) + })), + resolveLoader: cloneObject(config.resolveLoader), + snapshot: nestedConfig(config.snapshot, snapshot => ({ + resolveBuildDependencies: optionalNestedConfig( + snapshot.resolveBuildDependencies, + resolveBuildDependencies => ({ + timestamp: resolveBuildDependencies.timestamp, + hash: resolveBuildDependencies.hash + }) + ), + buildDependencies: optionalNestedConfig( + snapshot.buildDependencies, + buildDependencies => ({ + timestamp: buildDependencies.timestamp, + hash: buildDependencies.hash + }) + ), + resolve: optionalNestedConfig(snapshot.resolve, resolve => ({ + timestamp: resolve.timestamp, + hash: resolve.hash + })), + module: optionalNestedConfig(snapshot.module, module => ({ + timestamp: module.timestamp, + hash: module.hash + })), + immutablePaths: optionalNestedArray(snapshot.immutablePaths, p => [...p]), + managedPaths: optionalNestedArray(snapshot.managedPaths, p => [...p]), + unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, p => [...p]) + })), + stats: nestedConfig(config.stats, stats => { + if (stats === false) { + return { + preset: "none" + }; + } + if (stats === true) { + return { + preset: "normal" + }; + } + if (typeof stats === "string") { + return { + preset: stats + }; + } + return { + ...stats + }; + }), + target: config.target, + watch: config.watch, + watchOptions: cloneObject(config.watchOptions) +}); + +/** + * @param {EntryStatic} entry static entry options + * @returns {EntryStaticNormalized} normalized static entry options + */ +const getNormalizedEntryStatic = entry => { + if (typeof entry === "string") { + return { + main: { + import: [entry] + } + }; + } + if (Array.isArray(entry)) { + return { + main: { + import: entry + } + }; + } + /** @type {EntryStaticNormalized} */ + const result = {}; + for (const key of Object.keys(entry)) { + const value = entry[key]; + if (typeof value === "string") { + result[key] = { + import: [value] + }; + } else if (Array.isArray(value)) { + result[key] = { + import: value + }; + } else { + result[key] = { + import: + /** @type {EntryDescriptionNormalized["import"]} */ + ( + value.import && + (Array.isArray(value.import) ? value.import : [value.import]) + ), + filename: value.filename, + layer: value.layer, + runtime: value.runtime, + baseUri: value.baseUri, + publicPath: value.publicPath, + chunkLoading: value.chunkLoading, + asyncChunks: value.asyncChunks, + wasmLoading: value.wasmLoading, + dependOn: + /** @type {EntryDescriptionNormalized["dependOn"]} */ + ( + value.dependOn && + (Array.isArray(value.dependOn) + ? value.dependOn + : [value.dependOn]) + ), + library: value.library + }; + } + } + return result; +}; + +/** + * @param {OptimizationRuntimeChunk=} runtimeChunk runtimeChunk option + * @returns {OptimizationRuntimeChunkNormalized=} normalized runtimeChunk option + */ +const getNormalizedOptimizationRuntimeChunk = runtimeChunk => { + if (runtimeChunk === undefined) return; + if (runtimeChunk === false) return false; + if (runtimeChunk === "single") { + return { + name: () => "runtime" + }; + } + if (runtimeChunk === true || runtimeChunk === "multiple") { + return { + /** + * @param {Entrypoint} entrypoint entrypoint + * @returns {string} runtime chunk name + */ + name: entrypoint => `runtime~${entrypoint.name}` + }; + } + const { name } = runtimeChunk; + return { + name: typeof name === "function" ? name : () => name + }; +}; + +module.exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions; diff --git a/webpack-lib/lib/config/target.js b/webpack-lib/lib/config/target.js new file mode 100644 index 00000000000..2a7ed046c78 --- /dev/null +++ b/webpack-lib/lib/config/target.js @@ -0,0 +1,377 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const memoize = require("../util/memoize"); + +const getBrowserslistTargetHandler = memoize(() => + require("./browserslistTargetHandler") +); + +/** + * @param {string} context the context directory + * @returns {string} default target + */ +const getDefaultTarget = context => { + const browsers = getBrowserslistTargetHandler().load(null, context); + return browsers ? "browserslist" : "web"; +}; + +/** + * @typedef {object} PlatformTargetProperties + * @property {boolean | null} web web platform, importing of http(s) and std: is available + * @property {boolean | null} browser browser platform, running in a normal web browser + * @property {boolean | null} webworker (Web)Worker platform, running in a web/shared/service worker + * @property {boolean | null} node node platform, require of node built-in modules is available + * @property {boolean | null} nwjs nwjs platform, require of legacy nw.gui is available + * @property {boolean | null} electron electron platform, require of some electron built-in modules is available + */ + +/** + * @typedef {object} ElectronContextTargetProperties + * @property {boolean | null} electronMain in main context + * @property {boolean | null} electronPreload in preload context + * @property {boolean | null} electronRenderer in renderer context with node integration + */ + +/** + * @typedef {object} ApiTargetProperties + * @property {boolean | null} require has require function available + * @property {boolean | null} nodeBuiltins has node.js built-in modules available + * @property {boolean | null} nodePrefixForCoreModules node.js allows to use `node:` prefix for core modules + * @property {boolean | null} document has document available (allows script tags) + * @property {boolean | null} importScripts has importScripts available + * @property {boolean | null} importScriptsInWorker has importScripts available when creating a worker + * @property {boolean | null} fetchWasm has fetch function available for WebAssembly + * @property {boolean | null} global has global variable available + */ + +/** + * @typedef {object} EcmaTargetProperties + * @property {boolean | null} globalThis has globalThis variable available + * @property {boolean | null} bigIntLiteral big int literal syntax is available + * @property {boolean | null} const const and let variable declarations are available + * @property {boolean | null} arrowFunction arrow functions are available + * @property {boolean | null} forOf for of iteration is available + * @property {boolean | null} destructuring destructuring is available + * @property {boolean | null} dynamicImport async import() is available + * @property {boolean | null} dynamicImportInWorker async import() is available when creating a worker + * @property {boolean | null} module ESM syntax is available (when in module) + * @property {boolean | null} optionalChaining optional chaining is available + * @property {boolean | null} templateLiteral template literal is available + * @property {boolean | null} asyncFunction async functions and await are available + */ + +/** + * @template T + * @typedef {{ [P in keyof T]?: never }} Never + */ + +/** + * @template A + * @template B + * @typedef {(A & Never) | (Never & B) | (A & B)} Mix + */ + +/** @typedef {Mix, Mix>} TargetProperties */ + +/** + * @param {string} major major version + * @param {string | undefined} minor minor version + * @returns {(vMajor: number, vMinor?: number) => boolean | undefined} check if version is greater or equal + */ +const versionDependent = (major, minor) => { + if (!major) { + return () => /** @type {undefined} */ (undefined); + } + /** @type {number} */ + const nMajor = Number(major); + /** @type {number} */ + const nMinor = minor ? Number(minor) : 0; + return (vMajor, vMinor = 0) => + nMajor > vMajor || (nMajor === vMajor && nMinor >= vMinor); +}; + +/** @type {[string, string, RegExp, (...args: string[]) => Partial][]} */ +const TARGETS = [ + [ + "browserslist / browserslist:env / browserslist:query / browserslist:path-to-config / browserslist:path-to-config:env", + "Resolve features from browserslist. Will resolve browserslist config automatically. Only browser or node queries are supported (electron is not supported). Examples: 'browserslist:modern' to use 'modern' environment from browserslist config", + /^browserslist(?::(.+))?$/, + (rest, context) => { + const browserslistTargetHandler = getBrowserslistTargetHandler(); + const browsers = browserslistTargetHandler.load( + rest ? rest.trim() : null, + context + ); + if (!browsers) { + throw new Error(`No browserslist config found to handle the 'browserslist' target. +See https://github.com/browserslist/browserslist#queries for possible ways to provide a config. +The recommended way is to add a 'browserslist' key to your package.json and list supported browsers (resp. node.js versions). +You can also more options via the 'target' option: 'browserslist' / 'browserslist:env' / 'browserslist:query' / 'browserslist:path-to-config' / 'browserslist:path-to-config:env'`); + } + + return browserslistTargetHandler.resolve(browsers); + } + ], + [ + "web", + "Web browser.", + /^web$/, + () => ({ + node: false, + web: true, + webworker: null, + browser: true, + electron: false, + nwjs: false, + + document: true, + importScriptsInWorker: true, + fetchWasm: true, + nodeBuiltins: false, + importScripts: false, + require: false, + global: false + }) + ], + [ + "webworker", + "Web Worker, SharedWorker or Service Worker.", + /^webworker$/, + () => ({ + node: false, + web: true, + webworker: true, + browser: true, + electron: false, + nwjs: false, + + importScripts: true, + importScriptsInWorker: true, + fetchWasm: true, + nodeBuiltins: false, + require: false, + document: false, + global: false + }) + ], + [ + "[async-]node[X[.Y]]", + "Node.js in version X.Y. The 'async-' prefix will load chunks asynchronously via 'fs' and 'vm' instead of 'require()'. Examples: node14.5, async-node10.", + /^(async-)?node((\d+)(?:\.(\d+))?)?$/, + (asyncFlag, _, major, minor) => { + const v = versionDependent(major, minor); + // see https://node.green/ + return { + node: true, + web: false, + webworker: false, + browser: false, + electron: false, + nwjs: false, + + require: !asyncFlag, + nodeBuiltins: true, + // v16.0.0, v14.18.0 + nodePrefixForCoreModules: Number(major) < 15 ? v(14, 18) : v(16), + global: true, + document: false, + fetchWasm: false, + importScripts: false, + importScriptsInWorker: false, + + globalThis: v(12), + const: v(6), + templateLiteral: v(4), + optionalChaining: v(14), + arrowFunction: v(6), + asyncFunction: v(7, 6), + forOf: v(5), + destructuring: v(6), + bigIntLiteral: v(10, 4), + dynamicImport: v(12, 17), + dynamicImportInWorker: major ? false : undefined, + module: v(12, 17) + }; + } + ], + [ + "electron[X[.Y]]-main/preload/renderer", + "Electron in version X.Y. Script is running in main, preload resp. renderer context.", + /^electron((\d+)(?:\.(\d+))?)?-(main|preload|renderer)$/, + (_, major, minor, context) => { + const v = versionDependent(major, minor); + // see https://node.green/ + https://github.com/electron/releases + return { + node: true, + web: context !== "main", + webworker: false, + browser: false, + electron: true, + nwjs: false, + + electronMain: context === "main", + electronPreload: context === "preload", + electronRenderer: context === "renderer", + + global: true, + nodeBuiltins: true, + // 15.0.0 - Node.js v16.5 + // 14.0.0 - Mode.js v14.17, but prefixes only since v14.18 + nodePrefixForCoreModules: v(15), + + require: true, + document: context === "renderer", + fetchWasm: context === "renderer", + importScripts: false, + importScriptsInWorker: true, + + globalThis: v(5), + const: v(1, 1), + templateLiteral: v(1, 1), + optionalChaining: v(8), + arrowFunction: v(1, 1), + asyncFunction: v(1, 7), + forOf: v(0, 36), + destructuring: v(1, 1), + bigIntLiteral: v(4), + dynamicImport: v(11), + dynamicImportInWorker: major ? false : undefined, + module: v(11) + }; + } + ], + [ + "nwjs[X[.Y]] / node-webkit[X[.Y]]", + "NW.js in version X.Y.", + /^(?:nwjs|node-webkit)((\d+)(?:\.(\d+))?)?$/, + (_, major, minor) => { + const v = versionDependent(major, minor); + // see https://node.green/ + https://github.com/nwjs/nw.js/blob/nw48/CHANGELOG.md + return { + node: true, + web: true, + webworker: null, + browser: false, + electron: false, + nwjs: true, + + global: true, + nodeBuiltins: true, + document: false, + importScriptsInWorker: false, + fetchWasm: false, + importScripts: false, + require: false, + + globalThis: v(0, 43), + const: v(0, 15), + templateLiteral: v(0, 13), + optionalChaining: v(0, 44), + arrowFunction: v(0, 15), + asyncFunction: v(0, 21), + forOf: v(0, 13), + destructuring: v(0, 15), + bigIntLiteral: v(0, 32), + dynamicImport: v(0, 43), + dynamicImportInWorker: major ? false : undefined, + module: v(0, 43) + }; + } + ], + [ + "esX", + "EcmaScript in this version. Examples: es2020, es5.", + /^es(\d+)$/, + version => { + let v = Number(version); + if (v < 1000) v = v + 2009; + return { + const: v >= 2015, + templateLiteral: v >= 2015, + optionalChaining: v >= 2020, + arrowFunction: v >= 2015, + forOf: v >= 2015, + destructuring: v >= 2015, + module: v >= 2015, + asyncFunction: v >= 2017, + globalThis: v >= 2020, + bigIntLiteral: v >= 2020, + dynamicImport: v >= 2020, + dynamicImportInWorker: v >= 2020 + }; + } + ] +]; + +/** + * @param {string} target the target + * @param {string} context the context directory + * @returns {TargetProperties} target properties + */ +const getTargetProperties = (target, context) => { + for (const [, , regExp, handler] of TARGETS) { + const match = regExp.exec(target); + if (match) { + const [, ...args] = match; + const result = handler(...args, context); + if (result) return /** @type {TargetProperties} */ (result); + } + } + throw new Error( + `Unknown target '${target}'. The following targets are supported:\n${TARGETS.map( + ([name, description]) => `* ${name}: ${description}` + ).join("\n")}` + ); +}; + +/** + * @param {TargetProperties[]} targetProperties array of target properties + * @returns {TargetProperties} merged target properties + */ +const mergeTargetProperties = targetProperties => { + /** @type {Set} */ + const keys = new Set(); + for (const tp of targetProperties) { + for (const key of Object.keys(tp)) { + keys.add(/** @type {keyof TargetProperties} */ (key)); + } + } + /** @type {object} */ + const result = {}; + for (const key of keys) { + let hasTrue = false; + let hasFalse = false; + for (const tp of targetProperties) { + const value = tp[key]; + switch (value) { + case true: + hasTrue = true; + break; + case false: + hasFalse = true; + break; + } + } + if (hasTrue || hasFalse) + /** @type {TargetProperties} */ + (result)[key] = hasFalse && hasTrue ? null : Boolean(hasTrue); + } + return /** @type {TargetProperties} */ (result); +}; + +/** + * @param {string[]} targets the targets + * @param {string} context the context directory + * @returns {TargetProperties} target properties + */ +const getTargetsProperties = (targets, context) => + mergeTargetProperties(targets.map(t => getTargetProperties(t, context))); + +module.exports.getDefaultTarget = getDefaultTarget; +module.exports.getTargetProperties = getTargetProperties; +module.exports.getTargetsProperties = getTargetsProperties; diff --git a/webpack-lib/lib/container/ContainerEntryDependency.js b/webpack-lib/lib/container/ContainerEntryDependency.js new file mode 100644 index 00000000000..787d99cffac --- /dev/null +++ b/webpack-lib/lib/container/ContainerEntryDependency.js @@ -0,0 +1,48 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("./ContainerEntryModule").ExposeOptions} ExposeOptions */ +/** @typedef {import("./ContainerEntryModule").ExposesList} ExposesList */ + +class ContainerEntryDependency extends Dependency { + /** + * @param {string} name entry name + * @param {ExposesList} exposes list of exposed modules + * @param {string} shareScope name of the share scope + */ + constructor(name, exposes, shareScope) { + super(); + this.name = name; + this.exposes = exposes; + this.shareScope = shareScope; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return `container-entry-${this.name}`; + } + + get type() { + return "container entry"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ContainerEntryDependency, + "webpack/lib/container/ContainerEntryDependency" +); + +module.exports = ContainerEntryDependency; diff --git a/webpack-lib/lib/container/ContainerEntryModule.js b/webpack-lib/lib/container/ContainerEntryModule.js new file mode 100644 index 00000000000..3b22c712303 --- /dev/null +++ b/webpack-lib/lib/container/ContainerEntryModule.js @@ -0,0 +1,295 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const { OriginalSource, RawSource } = require("webpack-sources"); +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const Module = require("../Module"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const makeSerializable = require("../util/makeSerializable"); +const ContainerExposedDependency = require("./ContainerExposedDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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("./ContainerEntryDependency")} ContainerEntryDependency */ + +/** + * @typedef {object} ExposeOptions + * @property {string[]} import requests to exposed modules (last one is exported) + * @property {string} name custom chunk name for the exposed module + */ + +/** @typedef {[string, ExposeOptions][]} ExposesList */ + +class ContainerEntryModule extends Module { + /** + * @param {string} name container entry name + * @param {ExposesList} exposes list of exposed modules + * @param {string} shareScope name of the share scope + */ + constructor(name, exposes, shareScope) { + super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); + this._name = name; + this._exposes = exposes; + this._shareScope = shareScope; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `container entry (${this._shareScope}) ${JSON.stringify( + this._exposes + )}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return "container entry"; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return `${this.layer ? `(${this.layer})/` : ""}webpack/container/entry/${ + this._name + }`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + return callback(null, !this.buildMeta); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + strict: true, + topLevelDeclarations: new Set(["moduleMap", "get", "init"]) + }; + this.buildMeta.exportsType = "namespace"; + + this.clearDependenciesAndBlocks(); + + for (const [name, options] of this._exposes) { + const block = new AsyncDependenciesBlock( + { + name: options.name + }, + { name }, + options.import[options.import.length - 1] + ); + let idx = 0; + for (const request of options.import) { + const dep = new ContainerExposedDependency(name, request); + dep.loc = { + name, + index: idx++ + }; + + block.addDependency(dep); + } + this.addBlock(block); + } + this.addDependency(new StaticExportsDependency(["get", "init"], false)); + + callback(); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ moduleGraph, chunkGraph, runtimeTemplate }) { + const sources = new Map(); + const runtimeRequirements = new Set([ + RuntimeGlobals.definePropertyGetters, + RuntimeGlobals.hasOwnProperty, + RuntimeGlobals.exports + ]); + const getters = []; + + for (const block of this.blocks) { + const { dependencies } = block; + + const modules = dependencies.map(dependency => { + const dep = /** @type {ContainerExposedDependency} */ (dependency); + return { + name: dep.exposedName, + module: moduleGraph.getModule(dep), + request: dep.userRequest + }; + }); + + let str; + + if (modules.some(m => !m.module)) { + str = runtimeTemplate.throwMissingModuleErrorBlock({ + request: modules.map(m => m.request).join(", ") + }); + } else { + str = `return ${runtimeTemplate.blockPromise({ + block, + message: "", + chunkGraph, + runtimeRequirements + })}.then(${runtimeTemplate.returningFunction( + runtimeTemplate.returningFunction( + `(${modules + .map(({ module, request }) => + runtimeTemplate.moduleRaw({ + module, + chunkGraph, + request, + weak: false, + runtimeRequirements + }) + ) + .join(", ")})` + ) + )});`; + } + + getters.push( + `${JSON.stringify(modules[0].name)}: ${runtimeTemplate.basicFunction( + "", + str + )}` + ); + } + + const source = Template.asString([ + "var moduleMap = {", + Template.indent(getters.join(",\n")), + "};", + `var get = ${runtimeTemplate.basicFunction("module, getScope", [ + `${RuntimeGlobals.currentRemoteGetScope} = getScope;`, + // reusing the getScope variable to avoid creating a new var (and module is also used later) + "getScope = (", + Template.indent([ + `${RuntimeGlobals.hasOwnProperty}(moduleMap, module)`, + Template.indent([ + "? moduleMap[module]()", + `: Promise.resolve().then(${runtimeTemplate.basicFunction( + "", + "throw new Error('Module \"' + module + '\" does not exist in container.');" + )})` + ]) + ]), + ");", + `${RuntimeGlobals.currentRemoteGetScope} = undefined;`, + "return getScope;" + ])};`, + `var init = ${runtimeTemplate.basicFunction("shareScope, initScope", [ + `if (!${RuntimeGlobals.shareScopeMap}) return;`, + `var name = ${JSON.stringify(this._shareScope)}`, + `var oldScope = ${RuntimeGlobals.shareScopeMap}[name];`, + 'if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");', + `${RuntimeGlobals.shareScopeMap}[name] = shareScope;`, + `return ${RuntimeGlobals.initializeSharing}(name, initScope);` + ])};`, + "", + "// This exports getters to disallow modifications", + `${RuntimeGlobals.definePropertyGetters}(exports, {`, + Template.indent([ + `get: ${runtimeTemplate.returningFunction("get")},`, + `init: ${runtimeTemplate.returningFunction("init")}` + ]), + "});" + ]); + + sources.set( + "javascript", + this.useSourceMap || this.useSimpleSourceMap + ? new OriginalSource(source, "webpack/container-entry") + : new RawSource(source) + ); + + return { + sources, + runtimeRequirements + }; + } + + /** + * @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; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this._name); + write(this._exposes); + write(this._shareScope); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ContainerEntryModule} deserialized container entry module + */ + static deserialize(context) { + const { read } = context; + const obj = new ContainerEntryModule(read(), read(), read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + ContainerEntryModule, + "webpack/lib/container/ContainerEntryModule" +); + +module.exports = ContainerEntryModule; diff --git a/webpack-lib/lib/container/ContainerEntryModuleFactory.js b/webpack-lib/lib/container/ContainerEntryModuleFactory.js new file mode 100644 index 00000000000..4febfebe059 --- /dev/null +++ b/webpack-lib/lib/container/ContainerEntryModuleFactory.js @@ -0,0 +1,27 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const ModuleFactory = require("../ModuleFactory"); +const ContainerEntryModule = require("./ContainerEntryModule"); + +/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./ContainerEntryDependency")} ContainerEntryDependency */ + +module.exports = class ContainerEntryModuleFactory extends ModuleFactory { + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create({ dependencies: [dependency] }, callback) { + const dep = /** @type {ContainerEntryDependency} */ (dependency); + callback(null, { + module: new ContainerEntryModule(dep.name, dep.exposes, dep.shareScope) + }); + } +}; diff --git a/webpack-lib/lib/container/ContainerExposedDependency.js b/webpack-lib/lib/container/ContainerExposedDependency.js new file mode 100644 index 00000000000..2cbf04a694d --- /dev/null +++ b/webpack-lib/lib/container/ContainerExposedDependency.js @@ -0,0 +1,61 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const ModuleDependency = require("../dependencies/ModuleDependency"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ContainerExposedDependency extends ModuleDependency { + /** + * @param {string} exposedName public name + * @param {string} request request to module + */ + constructor(exposedName, request) { + super(request); + this.exposedName = exposedName; + } + + get type() { + return "container exposed"; + } + + get category() { + return "esm"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return `exposed dependency ${this.exposedName}=${this.request}`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + context.write(this.exposedName); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + this.exposedName = context.read(); + super.deserialize(context); + } +} + +makeSerializable( + ContainerExposedDependency, + "webpack/lib/container/ContainerExposedDependency" +); + +module.exports = ContainerExposedDependency; diff --git a/webpack-lib/lib/container/ContainerPlugin.js b/webpack-lib/lib/container/ContainerPlugin.js new file mode 100644 index 00000000000..ec3fe84091d --- /dev/null +++ b/webpack-lib/lib/container/ContainerPlugin.js @@ -0,0 +1,119 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const createSchemaValidation = require("../util/create-schema-validation"); +const memoize = require("../util/memoize"); +const ContainerEntryDependency = require("./ContainerEntryDependency"); +const ContainerEntryModuleFactory = require("./ContainerEntryModuleFactory"); +const ContainerExposedDependency = require("./ContainerExposedDependency"); +const { parseOptions } = require("./options"); + +/** @typedef {import("../../declarations/plugins/container/ContainerPlugin").ContainerPluginOptions} ContainerPluginOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("./ContainerEntryModule").ExposeOptions} ExposeOptions */ +/** @typedef {import("./ContainerEntryModule").ExposesList} ExposesList */ + +const getModuleFederationPlugin = memoize(() => + require("./ModuleFederationPlugin") +); + +const validate = createSchemaValidation( + require("../../schemas/plugins/container/ContainerPlugin.check.js"), + () => require("../../schemas/plugins/container/ContainerPlugin.json"), + { + name: "Container Plugin", + baseDataPath: "options" + } +); + +const PLUGIN_NAME = "ContainerPlugin"; + +class ContainerPlugin { + /** + * @param {ContainerPluginOptions} options options + */ + constructor(options) { + validate(options); + + this._options = { + name: options.name, + shareScope: options.shareScope || "default", + library: options.library || { + type: "var", + name: options.name + }, + runtime: options.runtime, + filename: options.filename || undefined, + exposes: /** @type {ExposesList} */ ( + parseOptions( + options.exposes, + item => ({ + import: Array.isArray(item) ? item : [item], + name: undefined + }), + item => ({ + import: Array.isArray(item.import) ? item.import : [item.import], + name: item.name || undefined + }) + ) + ) + }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { name, exposes, shareScope, filename, library, runtime } = + this._options; + + if (!compiler.options.output.enabledLibraryTypes.includes(library.type)) { + compiler.options.output.enabledLibraryTypes.push(library.type); + } + + compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => { + const hooks = + getModuleFederationPlugin().getCompilationHooks(compilation); + const dep = new ContainerEntryDependency(name, exposes, shareScope); + dep.loc = { name }; + compilation.addEntry( + /** @type {string} */ (compilation.options.context), + dep, + { + name, + filename, + runtime, + library + }, + error => { + if (error) return callback(error); + hooks.addContainerEntryDependency.call(dep); + callback(); + } + ); + }); + + compiler.hooks.thisCompilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + ContainerEntryDependency, + new ContainerEntryModuleFactory() + ); + + compilation.dependencyFactories.set( + ContainerExposedDependency, + normalModuleFactory + ); + } + ); + } +} + +module.exports = ContainerPlugin; diff --git a/webpack-lib/lib/container/ContainerReferencePlugin.js b/webpack-lib/lib/container/ContainerReferencePlugin.js new file mode 100644 index 00000000000..59657c1ffd7 --- /dev/null +++ b/webpack-lib/lib/container/ContainerReferencePlugin.js @@ -0,0 +1,142 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const ExternalsPlugin = require("../ExternalsPlugin"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const createSchemaValidation = require("../util/create-schema-validation"); +const FallbackDependency = require("./FallbackDependency"); +const FallbackItemDependency = require("./FallbackItemDependency"); +const FallbackModuleFactory = require("./FallbackModuleFactory"); +const RemoteModule = require("./RemoteModule"); +const RemoteRuntimeModule = require("./RemoteRuntimeModule"); +const RemoteToExternalDependency = require("./RemoteToExternalDependency"); +const { parseOptions } = require("./options"); + +/** @typedef {import("../../declarations/plugins/container/ContainerReferencePlugin").ContainerReferencePluginOptions} ContainerReferencePluginOptions */ +/** @typedef {import("../../declarations/plugins/container/ContainerReferencePlugin").RemotesConfig} RemotesConfig */ +/** @typedef {import("../Compiler")} Compiler */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/container/ContainerReferencePlugin.check.js"), + () => + require("../../schemas/plugins/container/ContainerReferencePlugin.json"), + { + name: "Container Reference Plugin", + baseDataPath: "options" + } +); + +const slashCode = "/".charCodeAt(0); + +class ContainerReferencePlugin { + /** + * @param {ContainerReferencePluginOptions} options options + */ + constructor(options) { + validate(options); + + this._remoteType = options.remoteType; + this._remotes = parseOptions( + options.remotes, + item => ({ + external: Array.isArray(item) ? item : [item], + shareScope: options.shareScope || "default" + }), + item => ({ + external: Array.isArray(item.external) + ? item.external + : [item.external], + shareScope: item.shareScope || options.shareScope || "default" + }) + ); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _remotes: remotes, _remoteType: remoteType } = this; + + /** @type {Record} */ + const remoteExternals = {}; + for (const [key, config] of remotes) { + let i = 0; + for (const external of config.external) { + if (external.startsWith("internal ")) continue; + remoteExternals[ + `webpack/container/reference/${key}${i ? `/fallback-${i}` : ""}` + ] = external; + i++; + } + } + + new ExternalsPlugin(remoteType, remoteExternals).apply(compiler); + + compiler.hooks.compilation.tap( + "ContainerReferencePlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + RemoteToExternalDependency, + normalModuleFactory + ); + + compilation.dependencyFactories.set( + FallbackItemDependency, + normalModuleFactory + ); + + compilation.dependencyFactories.set( + FallbackDependency, + new FallbackModuleFactory() + ); + + normalModuleFactory.hooks.factorize.tap( + "ContainerReferencePlugin", + data => { + if (!data.request.includes("!")) { + for (const [key, config] of remotes) { + if ( + data.request.startsWith(`${key}`) && + (data.request.length === key.length || + data.request.charCodeAt(key.length) === slashCode) + ) { + return new RemoteModule( + data.request, + config.external.map((external, i) => + external.startsWith("internal ") + ? external.slice(9) + : `webpack/container/reference/${key}${ + i ? `/fallback-${i}` : "" + }` + ), + `.${data.request.slice(key.length)}`, + config.shareScope + ); + } + } + } + } + ); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("ContainerReferencePlugin", (chunk, set) => { + set.add(RuntimeGlobals.module); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.hasOwnProperty); + set.add(RuntimeGlobals.initializeSharing); + set.add(RuntimeGlobals.shareScopeMap); + compilation.addRuntimeModule(chunk, new RemoteRuntimeModule()); + }); + } + ); + } +} + +module.exports = ContainerReferencePlugin; diff --git a/webpack-lib/lib/container/FallbackDependency.js b/webpack-lib/lib/container/FallbackDependency.js new file mode 100644 index 00000000000..088720f5e32 --- /dev/null +++ b/webpack-lib/lib/container/FallbackDependency.js @@ -0,0 +1,64 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class FallbackDependency extends Dependency { + /** + * @param {string[]} requests requests + */ + constructor(requests) { + super(); + this.requests = requests; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return `fallback ${this.requests.join(" ")}`; + } + + get type() { + return "fallback"; + } + + get category() { + return "esm"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.requests); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {FallbackDependency} deserialize fallback dependency + */ + static deserialize(context) { + const { read } = context; + const obj = new FallbackDependency(read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + FallbackDependency, + "webpack/lib/container/FallbackDependency" +); + +module.exports = FallbackDependency; diff --git a/webpack-lib/lib/container/FallbackItemDependency.js b/webpack-lib/lib/container/FallbackItemDependency.js new file mode 100644 index 00000000000..f09f8cf8c3c --- /dev/null +++ b/webpack-lib/lib/container/FallbackItemDependency.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("../dependencies/ModuleDependency"); +const makeSerializable = require("../util/makeSerializable"); + +class FallbackItemDependency extends ModuleDependency { + /** + * @param {string} request request + */ + constructor(request) { + super(request); + } + + get type() { + return "fallback item"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + FallbackItemDependency, + "webpack/lib/container/FallbackItemDependency" +); + +module.exports = FallbackItemDependency; diff --git a/webpack-lib/lib/container/FallbackModule.js b/webpack-lib/lib/container/FallbackModule.js new file mode 100644 index 00000000000..50ea21b7e4d --- /dev/null +++ b/webpack-lib/lib/container/FallbackModule.js @@ -0,0 +1,184 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const Module = require("../Module"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const { WEBPACK_MODULE_TYPE_FALLBACK } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const makeSerializable = require("../util/makeSerializable"); +const FallbackItemDependency = require("./FallbackItemDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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 */ + +const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); + +class FallbackModule extends Module { + /** + * @param {string[]} requests list of requests to choose one + */ + constructor(requests) { + super(WEBPACK_MODULE_TYPE_FALLBACK); + this.requests = requests; + this._identifier = `fallback ${this.requests.join(" ")}`; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this._identifier; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return this._identifier; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return `${this.layer ? `(${this.layer})/` : ""}webpack/container/fallback/${ + this.requests[0] + }/and ${this.requests.length - 1} more`; + } + + /** + * @param {Chunk} chunk the chunk which condition should be checked + * @param {Compilation} compilation the compilation + * @returns {boolean} true, if the chunk is ok for the module + */ + chunkCondition(chunk, { chunkGraph }) { + return chunkGraph.getNumberOfEntryModules(chunk) > 0; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + callback(null, !this.buildInfo); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + strict: true + }; + + this.clearDependenciesAndBlocks(); + for (const request of this.requests) + this.addDependency(new FallbackItemDependency(request)); + + callback(); + } + + /** + * @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 this.requests.length * 5 + 42; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { + const ids = this.dependencies.map(dep => + chunkGraph.getModuleId(/** @type {Module} */ (moduleGraph.getModule(dep))) + ); + const code = Template.asString([ + `var ids = ${JSON.stringify(ids)};`, + "var error, result, i = 0;", + `var loop = ${runtimeTemplate.basicFunction("next", [ + "while(i < ids.length) {", + Template.indent([ + `try { next = ${RuntimeGlobals.require}(ids[i++]); } catch(e) { return handleError(e); }`, + "if(next) return next.then ? next.then(handleResult, handleError) : handleResult(next);" + ]), + "}", + "if(error) throw error;" + ])}`, + `var handleResult = ${runtimeTemplate.basicFunction("result", [ + "if(result) return result;", + "return loop();" + ])};`, + `var handleError = ${runtimeTemplate.basicFunction("e", [ + "error = e;", + "return loop();" + ])};`, + "module.exports = loop();" + ]); + const sources = new Map(); + sources.set("javascript", new RawSource(code)); + return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.requests); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {FallbackModule} deserialized fallback module + */ + static deserialize(context) { + const { read } = context; + const obj = new FallbackModule(read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable(FallbackModule, "webpack/lib/container/FallbackModule"); + +module.exports = FallbackModule; diff --git a/webpack-lib/lib/container/FallbackModuleFactory.js b/webpack-lib/lib/container/FallbackModuleFactory.js new file mode 100644 index 00000000000..6a9eaeca0ae --- /dev/null +++ b/webpack-lib/lib/container/FallbackModuleFactory.js @@ -0,0 +1,27 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr +*/ + +"use strict"; + +const ModuleFactory = require("../ModuleFactory"); +const FallbackModule = require("./FallbackModule"); + +/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./FallbackDependency")} FallbackDependency */ + +module.exports = class FallbackModuleFactory extends ModuleFactory { + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create({ dependencies: [dependency] }, callback) { + const dep = /** @type {FallbackDependency} */ (dependency); + callback(null, { + module: new FallbackModule(dep.requests) + }); + } +}; diff --git a/webpack-lib/lib/container/HoistContainerReferencesPlugin.js b/webpack-lib/lib/container/HoistContainerReferencesPlugin.js new file mode 100644 index 00000000000..3435c98ef2f --- /dev/null +++ b/webpack-lib/lib/container/HoistContainerReferencesPlugin.js @@ -0,0 +1,250 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const ExternalModule = require("../ExternalModule"); +const { STAGE_ADVANCED } = require("../OptimizationStages"); +const memoize = require("../util/memoize"); +const { forEachRuntime } = require("../util/runtime"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Module")} Module */ + +const getModuleFederationPlugin = memoize(() => + require("./ModuleFederationPlugin") +); + +const PLUGIN_NAME = "HoistContainerReferences"; + +/** + * This class is used to hoist container references in the code. + */ +class HoistContainerReferences { + /** + * Apply the plugin to the compiler. + * @param {Compiler} compiler The webpack compiler instance. + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const hooks = + getModuleFederationPlugin().getCompilationHooks(compilation); + const depsToTrace = new Set(); + const entryExternalsToHoist = new Set(); + hooks.addContainerEntryDependency.tap(PLUGIN_NAME, dep => { + depsToTrace.add(dep); + }); + hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, dep => { + depsToTrace.add(dep); + }); + + compilation.hooks.addEntry.tap(PLUGIN_NAME, entryDep => { + if (entryDep.type === "entry") { + entryExternalsToHoist.add(entryDep); + } + }); + + // Hook into the optimizeChunks phase + compilation.hooks.optimizeChunks.tap( + { + name: PLUGIN_NAME, + // advanced stage is where SplitChunksPlugin runs. + stage: STAGE_ADVANCED + 1 + }, + chunks => { + this.hoistModulesInChunks( + compilation, + depsToTrace, + entryExternalsToHoist + ); + } + ); + }); + } + + /** + * Hoist modules in chunks. + * @param {Compilation} compilation The webpack compilation instance. + * @param {Set} depsToTrace Set of container entry dependencies. + * @param {Set} entryExternalsToHoist Set of container entry dependencies to hoist. + */ + hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) { + const { chunkGraph, moduleGraph } = compilation; + + // loop over entry points + for (const dep of entryExternalsToHoist) { + const entryModule = moduleGraph.getModule(dep); + if (!entryModule) continue; + // get all the external module types and hoist them to the runtime chunk, this will get RemoteModule externals + const allReferencedModules = getAllReferencedModules( + compilation, + entryModule, + "external", + false + ); + + const containerRuntimes = chunkGraph.getModuleRuntimes(entryModule); + const runtimes = new Set(); + + for (const runtimeSpec of containerRuntimes) { + forEachRuntime(runtimeSpec, runtimeKey => { + if (runtimeKey) { + runtimes.add(runtimeKey); + } + }); + } + + for (const runtime of runtimes) { + const runtimeChunk = compilation.namedChunks.get(runtime); + if (!runtimeChunk) continue; + + for (const module of allReferencedModules) { + if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { + chunkGraph.connectChunkAndModule(runtimeChunk, module); + } + } + } + this.cleanUpChunks(compilation, allReferencedModules); + } + + // handle container entry specifically + for (const dep of depsToTrace) { + const containerEntryModule = moduleGraph.getModule(dep); + if (!containerEntryModule) continue; + const allReferencedModules = getAllReferencedModules( + compilation, + containerEntryModule, + "initial", + false + ); + + const allRemoteReferences = getAllReferencedModules( + compilation, + containerEntryModule, + "external", + false + ); + + for (const remote of allRemoteReferences) { + allReferencedModules.add(remote); + } + + const containerRuntimes = + chunkGraph.getModuleRuntimes(containerEntryModule); + const runtimes = new Set(); + + for (const runtimeSpec of containerRuntimes) { + forEachRuntime(runtimeSpec, runtimeKey => { + if (runtimeKey) { + runtimes.add(runtimeKey); + } + }); + } + + for (const runtime of runtimes) { + const runtimeChunk = compilation.namedChunks.get(runtime); + if (!runtimeChunk) continue; + + for (const module of allReferencedModules) { + if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { + chunkGraph.connectChunkAndModule(runtimeChunk, module); + } + } + } + this.cleanUpChunks(compilation, allReferencedModules); + } + } + + /** + * Clean up chunks by disconnecting unused modules. + * @param {Compilation} compilation The webpack compilation instance. + * @param {Set} modules Set of modules to clean up. + */ + cleanUpChunks(compilation, modules) { + const { chunkGraph } = compilation; + for (const module of modules) { + for (const chunk of chunkGraph.getModuleChunks(module)) { + if (!chunk.hasRuntime()) { + chunkGraph.disconnectChunkAndModule(chunk, module); + if ( + chunkGraph.getNumberOfChunkModules(chunk) === 0 && + chunkGraph.getNumberOfEntryModules(chunk) === 0 + ) { + chunkGraph.disconnectChunk(chunk); + compilation.chunks.delete(chunk); + if (chunk.name) { + compilation.namedChunks.delete(chunk.name); + } + } + } + } + } + modules.clear(); + } +} + +/** + * Helper method to collect all referenced modules recursively. + * @param {Compilation} compilation The webpack compilation instance. + * @param {Module} module The module to start collecting from. + * @param {string} type The type of modules to collect ("initial", "external", or "all"). + * @param {boolean} includeInitial Should include the referenced module passed + * @returns {Set} Set of collected modules. + */ +function getAllReferencedModules(compilation, module, type, includeInitial) { + const collectedModules = new Set(includeInitial ? [module] : []); + const visitedModules = new WeakSet([module]); + const stack = [module]; + + while (stack.length > 0) { + const currentModule = stack.pop(); + if (!currentModule) continue; + + const outgoingConnections = + compilation.moduleGraph.getOutgoingConnections(currentModule); + if (outgoingConnections) { + for (const connection of outgoingConnections) { + const connectedModule = connection.module; + + // Skip if module has already been visited + if (!connectedModule || visitedModules.has(connectedModule)) { + continue; + } + + // Handle 'initial' type (skipping async blocks) + if (type === "initial") { + const parentBlock = compilation.moduleGraph.getParentBlock( + /** @type {Dependency} */ + (connection.dependency) + ); + if (parentBlock instanceof AsyncDependenciesBlock) { + continue; + } + } + + // Handle 'external' type (collecting only external modules) + if (type === "external") { + if (connection.module instanceof ExternalModule) { + collectedModules.add(connectedModule); + } + } else { + // Handle 'all' or unspecified types + collectedModules.add(connectedModule); + } + + // Add connected module to the stack and mark it as visited + visitedModules.add(connectedModule); + stack.push(connectedModule); + } + } + } + + return collectedModules; +} + +module.exports = HoistContainerReferences; diff --git a/webpack-lib/lib/container/ModuleFederationPlugin.js b/webpack-lib/lib/container/ModuleFederationPlugin.js new file mode 100644 index 00000000000..94e2aacee53 --- /dev/null +++ b/webpack-lib/lib/container/ModuleFederationPlugin.js @@ -0,0 +1,131 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const { SyncHook } = require("tapable"); +const isValidExternalsType = require("../../schemas/plugins/container/ExternalsType.check.js"); +const Compilation = require("../Compilation"); +const SharePlugin = require("../sharing/SharePlugin"); +const createSchemaValidation = require("../util/create-schema-validation"); +const ContainerPlugin = require("./ContainerPlugin"); +const ContainerReferencePlugin = require("./ContainerReferencePlugin"); +const HoistContainerReferences = require("./HoistContainerReferencesPlugin"); + +/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").ExternalsType} ExternalsType */ +/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").ModuleFederationPluginOptions} ModuleFederationPluginOptions */ +/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").Shared} Shared */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency")} Dependency */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncHook} addContainerEntryDependency + * @property {SyncHook} addFederationRuntimeDependency + */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/container/ModuleFederationPlugin.check.js"), + () => require("../../schemas/plugins/container/ModuleFederationPlugin.json"), + { + name: "Module Federation Plugin", + baseDataPath: "options" + } +); + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class ModuleFederationPlugin { + /** + * @param {ModuleFederationPluginOptions} options options + */ + constructor(options) { + validate(options); + + this._options = options; + } + + /** + * Get the compilation hooks associated with this plugin. + * @param {Compilation} compilation The compilation instance. + * @returns {CompilationHooks} The hooks for the compilation. + */ + 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) { + hooks = { + addContainerEntryDependency: new SyncHook(["dependency"]), + addFederationRuntimeDependency: new SyncHook(["dependency"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _options: options } = this; + const library = options.library || { type: "var", name: options.name }; + const remoteType = + options.remoteType || + (options.library && isValidExternalsType(options.library.type) + ? /** @type {ExternalsType} */ (options.library.type) + : "script"); + if ( + library && + !compiler.options.output.enabledLibraryTypes.includes(library.type) + ) { + compiler.options.output.enabledLibraryTypes.push(library.type); + } + compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { + if ( + options.exposes && + (Array.isArray(options.exposes) + ? options.exposes.length > 0 + : Object.keys(options.exposes).length > 0) + ) { + new ContainerPlugin({ + name: /** @type {string} */ (options.name), + library, + filename: options.filename, + runtime: options.runtime, + shareScope: options.shareScope, + exposes: options.exposes + }).apply(compiler); + } + if ( + options.remotes && + (Array.isArray(options.remotes) + ? options.remotes.length > 0 + : Object.keys(options.remotes).length > 0) + ) { + new ContainerReferencePlugin({ + remoteType, + shareScope: options.shareScope, + remotes: options.remotes + }).apply(compiler); + } + if (options.shared) { + new SharePlugin({ + shared: options.shared, + shareScope: options.shareScope + }).apply(compiler); + } + new HoistContainerReferences().apply(compiler); + }); + } +} + +module.exports = ModuleFederationPlugin; diff --git a/webpack-lib/lib/container/RemoteModule.js b/webpack-lib/lib/container/RemoteModule.js new file mode 100644 index 00000000000..4a2cf128de1 --- /dev/null +++ b/webpack-lib/lib/container/RemoteModule.js @@ -0,0 +1,184 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const Module = require("../Module"); +const { + REMOTE_AND_SHARE_INIT_TYPES +} = require("../ModuleSourceTypesConstants"); +const { WEBPACK_MODULE_TYPE_REMOTE } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const FallbackDependency = require("./FallbackDependency"); +const RemoteToExternalDependency = require("./RemoteToExternalDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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 */ + +const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); + +class RemoteModule extends Module { + /** + * @param {string} request request string + * @param {string[]} externalRequests list of external requests to containers + * @param {string} internalRequest name of exposed module in container + * @param {string} shareScope the used share scope name + */ + constructor(request, externalRequests, internalRequest, shareScope) { + super(WEBPACK_MODULE_TYPE_REMOTE); + this.request = request; + this.externalRequests = externalRequests; + this.internalRequest = internalRequest; + this.shareScope = shareScope; + this._identifier = `remote (${shareScope}) ${this.externalRequests.join( + " " + )} ${this.internalRequest}`; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this._identifier; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `remote ${this.request}`; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return `${this.layer ? `(${this.layer})/` : ""}webpack/container/remote/${ + this.request + }`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + callback(null, !this.buildInfo); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + strict: true + }; + + this.clearDependenciesAndBlocks(); + if (this.externalRequests.length === 1) { + this.addDependency( + new RemoteToExternalDependency(this.externalRequests[0]) + ); + } else { + this.addDependency(new FallbackDependency(this.externalRequests)); + } + + callback(); + } + + /** + * @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 6; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return REMOTE_AND_SHARE_INIT_TYPES; + } + + /** + * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) + */ + nameForCondition() { + return this.request; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { + const module = moduleGraph.getModule(this.dependencies[0]); + const id = module && chunkGraph.getModuleId(module); + const sources = new Map(); + sources.set("remote", new RawSource("")); + const data = new Map(); + data.set("share-init", [ + { + shareScope: this.shareScope, + initStage: 20, + init: id === undefined ? "" : `initExternal(${JSON.stringify(id)});` + } + ]); + return { sources, data, runtimeRequirements: RUNTIME_REQUIREMENTS }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.request); + write(this.externalRequests); + write(this.internalRequest); + write(this.shareScope); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RemoteModule} deserialized module + */ + static deserialize(context) { + const { read } = context; + const obj = new RemoteModule(read(), read(), read(), read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable(RemoteModule, "webpack/lib/container/RemoteModule"); + +module.exports = RemoteModule; diff --git a/webpack-lib/lib/container/RemoteRuntimeModule.js b/webpack-lib/lib/container/RemoteRuntimeModule.js new file mode 100644 index 00000000000..1e871e2da2f --- /dev/null +++ b/webpack-lib/lib/container/RemoteRuntimeModule.js @@ -0,0 +1,144 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("./RemoteModule")} RemoteModule */ + +class RemoteRuntimeModule extends RuntimeModule { + constructor() { + super("remotes loading"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const { runtimeTemplate, moduleGraph } = compilation; + /** @type {Record} */ + const chunkToRemotesMapping = {}; + /** @type {Record} */ + const idToExternalAndNameMapping = {}; + for (const chunk of /** @type {Chunk} */ ( + this.chunk + ).getAllReferencedChunks()) { + const modules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + "remote" + ); + if (!modules) continue; + /** @type {ModuleId[]} */ + const remotes = (chunkToRemotesMapping[ + /** @type {ChunkId} */ + (chunk.id) + ] = []); + for (const m of modules) { + const module = /** @type {RemoteModule} */ (m); + const name = module.internalRequest; + const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + const shareScope = module.shareScope; + const dep = module.dependencies[0]; + const externalModule = moduleGraph.getModule(dep); + const externalModuleId = + /** @type {ModuleId} */ + (externalModule && chunkGraph.getModuleId(externalModule)); + remotes.push(id); + idToExternalAndNameMapping[id] = [shareScope, name, externalModuleId]; + } + } + return Template.asString([ + `var chunkMapping = ${JSON.stringify( + chunkToRemotesMapping, + null, + "\t" + )};`, + `var idToExternalAndNameMapping = ${JSON.stringify( + idToExternalAndNameMapping, + null, + "\t" + )};`, + `${ + RuntimeGlobals.ensureChunkHandlers + }.remotes = ${runtimeTemplate.basicFunction("chunkId, promises", [ + `if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`, + Template.indent([ + `chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction("id", [ + `var getScope = ${RuntimeGlobals.currentRemoteGetScope};`, + "if(!getScope) getScope = [];", + "var data = idToExternalAndNameMapping[id];", + "if(getScope.indexOf(data) >= 0) return;", + "getScope.push(data);", + "if(data.p) return promises.push(data.p);", + `var onError = ${runtimeTemplate.basicFunction("error", [ + 'if(!error) error = new Error("Container missing");', + 'if(typeof error.message === "string")', + Template.indent( + "error.message += '\\nwhile loading \"' + data[1] + '\" from ' + data[2];" + ), + `${ + RuntimeGlobals.moduleFactories + }[id] = ${runtimeTemplate.basicFunction("", ["throw error;"])}`, + "data.p = 0;" + ])};`, + `var handleFunction = ${runtimeTemplate.basicFunction( + "fn, arg1, arg2, d, next, first", + [ + "try {", + Template.indent([ + "var promise = fn(arg1, arg2);", + "if(promise && promise.then) {", + Template.indent([ + `var p = promise.then(${runtimeTemplate.returningFunction( + "next(result, d)", + "result" + )}, onError);`, + "if(first) promises.push(data.p = p); else return p;" + ]), + "} else {", + Template.indent(["return next(promise, d, first);"]), + "}" + ]), + "} catch(error) {", + Template.indent(["onError(error);"]), + "}" + ] + )}`, + `var onExternal = ${runtimeTemplate.returningFunction( + `external ? handleFunction(${RuntimeGlobals.initializeSharing}, data[0], 0, external, onInitialized, first) : onError()`, + "external, _, first" + )};`, + `var onInitialized = ${runtimeTemplate.returningFunction( + "handleFunction(external.get, data[1], getScope, 0, onFactory, first)", + "_, external, first" + )};`, + `var onFactory = ${runtimeTemplate.basicFunction("factory", [ + "data.p = 1;", + `${ + RuntimeGlobals.moduleFactories + }[id] = ${runtimeTemplate.basicFunction("module", [ + "module.exports = factory();" + ])}` + ])};`, + `handleFunction(${RuntimeGlobals.require}, data[2], 0, 0, onExternal, 1);` + ])});` + ]), + "}" + ])}` + ]); + } +} + +module.exports = RemoteRuntimeModule; diff --git a/webpack-lib/lib/container/RemoteToExternalDependency.js b/webpack-lib/lib/container/RemoteToExternalDependency.js new file mode 100644 index 00000000000..df7fd1f8158 --- /dev/null +++ b/webpack-lib/lib/container/RemoteToExternalDependency.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("../dependencies/ModuleDependency"); +const makeSerializable = require("../util/makeSerializable"); + +class RemoteToExternalDependency extends ModuleDependency { + /** + * @param {string} request request + */ + constructor(request) { + super(request); + } + + get type() { + return "remote to external"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + RemoteToExternalDependency, + "webpack/lib/container/RemoteToExternalDependency" +); + +module.exports = RemoteToExternalDependency; diff --git a/webpack-lib/lib/container/options.js b/webpack-lib/lib/container/options.js new file mode 100644 index 00000000000..cb7df0d55fb --- /dev/null +++ b/webpack-lib/lib/container/options.js @@ -0,0 +1,105 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template T + * @typedef {Record} Item + */ + +/** + * @template T + * @typedef {(string | Item)[] | Item} ContainerOptionsFormat + */ + +/** + * @template T + * @template N + * @param {ContainerOptionsFormat} options options passed by the user + * @param {function(string | string[], string) : N} normalizeSimple normalize a simple item + * @param {function(T, string) : N} normalizeOptions normalize a complex item + * @param {function(string, N): void} fn processing function + * @returns {void} + */ +const process = (options, normalizeSimple, normalizeOptions, fn) => { + /** + * @param {(string | Item)[]} items items + */ + const array = items => { + for (const item of items) { + if (typeof item === "string") { + fn(item, normalizeSimple(item, item)); + } else if (item && typeof item === "object") { + object(item); + } else { + throw new Error("Unexpected options format"); + } + } + }; + /** + * @param {Item} obj an object + */ + const object = obj => { + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string" || Array.isArray(value)) { + fn(key, normalizeSimple(value, key)); + } else { + fn(key, normalizeOptions(value, key)); + } + } + }; + if (!options) { + // Do nothing + } else if (Array.isArray(options)) { + array(options); + } else if (typeof options === "object") { + object(options); + } else { + throw new Error("Unexpected options format"); + } +}; + +/** + * @template T + * @template R + * @param {ContainerOptionsFormat} options options passed by the user + * @param {function(string | string[], string) : R} normalizeSimple normalize a simple item + * @param {function(T, string) : R} normalizeOptions normalize a complex item + * @returns {[string, R][]} parsed options + */ +const parseOptions = (options, normalizeSimple, normalizeOptions) => { + /** @type {[string, R][]} */ + const items = []; + process(options, normalizeSimple, normalizeOptions, (key, value) => { + items.push([key, value]); + }); + return items; +}; + +/** + * @template T + * @param {string} scope scope name + * @param {ContainerOptionsFormat} options options passed by the user + * @returns {Record} options to spread or pass + */ +const scope = (scope, options) => { + /** @type {Record} */ + const obj = {}; + process( + options, + item => /** @type {string | string[] | T} */ (item), + item => /** @type {string | string[] | T} */ (item), + (key, value) => { + obj[ + key.startsWith("./") ? `${scope}${key.slice(1)}` : `${scope}/${key}` + ] = value; + } + ); + return obj; +}; + +module.exports.parseOptions = parseOptions; +module.exports.scope = scope; diff --git a/webpack-lib/lib/css/CssGenerator.js b/webpack-lib/lib/css/CssGenerator.js new file mode 100644 index 00000000000..4efdc73c4ce --- /dev/null +++ b/webpack-lib/lib/css/CssGenerator.js @@ -0,0 +1,261 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const Generator = require("../Generator"); +const InitFragment = require("../InitFragment"); +const { + JS_AND_CSS_EXPORT_TYPES, + JS_AND_CSS_TYPES +} = require("../ModuleSourceTypesConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").CssAutoGeneratorOptions} CssAutoGeneratorOptions */ +/** @typedef {import("../../declarations/WebpackOptions").CssGlobalGeneratorOptions} CssGlobalGeneratorOptions */ +/** @typedef {import("../../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").CssData} CssData */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../util/Hash")} Hash */ + +class CssGenerator extends Generator { + /** + * @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options + */ + constructor(options) { + super(); + this.convention = options.exportsConvention; + this.localIdentName = options.localIdentName; + this.exportsOnly = options.exportsOnly; + this.esModule = options.esModule; + } + + /** + * @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) { + if (!this.esModule) { + return "Module is not an ECMAScript module"; + } + + return undefined; + } + + /** + * @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 source = + generateContext.type === "javascript" + ? new ReplaceSource(new RawSource("")) + : new ReplaceSource(/** @type {Source} */ (module.originalSource())); + + /** @type {InitFragment[]} */ + const initFragments = []; + /** @type {CssData} */ + const cssData = { + esModule: this.esModule, + exports: new Map() + }; + + /** @type {InitFragment[] | undefined} */ + let chunkInitFragments; + /** @type {DependencyTemplateContext} */ + const templateContext = { + runtimeTemplate: generateContext.runtimeTemplate, + dependencyTemplates: generateContext.dependencyTemplates, + moduleGraph: generateContext.moduleGraph, + chunkGraph: generateContext.chunkGraph, + module, + runtime: generateContext.runtime, + runtimeRequirements: generateContext.runtimeRequirements, + concatenationScope: generateContext.concatenationScope, + codeGenerationResults: + /** @type {CodeGenerationResults} */ + (generateContext.codeGenerationResults), + initFragments, + cssData, + get chunkInitFragments() { + if (!chunkInitFragments) { + const data = + /** @type {NonNullable} */ + (generateContext.getData)(); + chunkInitFragments = data.get("chunkInitFragments"); + if (!chunkInitFragments) { + chunkInitFragments = []; + data.set("chunkInitFragments", chunkInitFragments); + } + } + + return chunkInitFragments; + } + }; + + /** + * @param {Dependency} dependency dependency + */ + const handleDependency = dependency => { + const constructor = + /** @type {new (...args: EXPECTED_ANY[]) => Dependency} */ + (dependency.constructor); + const template = generateContext.dependencyTemplates.get(constructor); + if (!template) { + throw new Error( + `No template for dependency: ${dependency.constructor.name}` + ); + } + + template.apply(dependency, source, templateContext); + }; + + for (const dependency of module.dependencies) { + handleDependency(dependency); + } + + switch (generateContext.type) { + case "javascript": { + module.buildInfo.cssData = cssData; + + generateContext.runtimeRequirements.add(RuntimeGlobals.module); + + if (generateContext.concatenationScope) { + const source = new ConcatSource(); + const usedIdentifiers = new Set(); + for (const [name, v] of cssData.exports) { + const usedName = generateContext.moduleGraph + .getExportInfo(module, name) + .getUsedName(name, generateContext.runtime); + if (!usedName) { + continue; + } + let identifier = Template.toIdentifier(usedName); + const { RESERVED_IDENTIFIER } = require("../util/propertyName"); + if (RESERVED_IDENTIFIER.has(identifier)) { + identifier = `_${identifier}`; + } + const i = 0; + while (usedIdentifiers.has(identifier)) { + identifier = Template.toIdentifier(name + i); + } + usedIdentifiers.add(identifier); + generateContext.concatenationScope.registerExport(name, identifier); + source.add( + `${ + generateContext.runtimeTemplate.supportsConst() + ? "const" + : "var" + } ${identifier} = ${JSON.stringify(v)};\n` + ); + } + return source; + } + + const needNsObj = + this.esModule && + generateContext.moduleGraph + .getExportsInfo(module) + .otherExportsInfo.getUsed(generateContext.runtime) !== + UsageState.Unused; + + if (needNsObj) { + generateContext.runtimeRequirements.add( + RuntimeGlobals.makeNamespaceObject + ); + } + + const exports = []; + + for (const [name, v] of cssData.exports) { + exports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`); + } + + return new RawSource( + `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${ + module.moduleArgument + }.exports = {\n${exports.join(",\n")}\n}${needNsObj ? ")" : ""};` + ); + } + case "css": { + if (module.presentationalDependencies !== undefined) { + for (const dependency of module.presentationalDependencies) { + handleDependency(dependency); + } + } + + generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules); + + return InitFragment.addToSource(source, initFragments, generateContext); + } + } + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + // TODO, find a better way to prevent the original module from being removed after concatenation, maybe it is a bug + return this.exportsOnly ? JS_AND_CSS_EXPORT_TYPES : JS_AND_CSS_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + switch (type) { + case "javascript": { + if (!module.buildInfo.cssData) { + return 42; + } + + const exports = module.buildInfo.cssData.exports; + const stringifiedExports = JSON.stringify( + Array.from(exports).reduce((obj, [key, value]) => { + obj[key] = value; + return obj; + }, {}) + ); + + return stringifiedExports.length + 42; + } + case "css": { + const originalSource = module.originalSource(); + + if (!originalSource) { + return 0; + } + + return originalSource.size(); + } + } + } + + /** + * @param {Hash} hash hash that will be modified + * @param {UpdateHashContext} updateHashContext context for updating hash + */ + updateHash(hash, { module }) { + hash.update(this.esModule.toString()); + } +} + +module.exports = CssGenerator; diff --git a/webpack-lib/lib/css/CssLoadingRuntimeModule.js b/webpack-lib/lib/css/CssLoadingRuntimeModule.js new file mode 100644 index 00000000000..a06d329d189 --- /dev/null +++ b/webpack-lib/lib/css/CssLoadingRuntimeModule.js @@ -0,0 +1,503 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { chunkHasCss } = require("./CssModulesPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").Environment} Environment */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +/** + * @typedef {object} CssLoadingRuntimeModulePluginHooks + * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet + * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload + * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class CssLoadingRuntimeModule extends RuntimeModule { + /** + * @param {Compilation} compilation the compilation + * @returns {CssLoadingRuntimeModulePluginHooks} 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 = { + createStylesheet: new SyncWaterfallHook(["source", "chunk"]), + linkPreload: new SyncWaterfallHook(["source", "chunk"]), + linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("css loading", 10); + + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { _runtimeRequirements } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const chunk = /** @type {Chunk} */ (this.chunk); + const { + chunkGraph, + runtimeTemplate, + outputOptions: { + crossOriginLoading, + uniqueName, + chunkLoadTimeout: loadTimeout + } + } = compilation; + const fn = RuntimeGlobals.ensureChunkHandlers; + const conditionMap = chunkGraph.getChunkConditionMap( + /** @type {Chunk} */ (chunk), + /** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {boolean} true, if the chunk has css + */ + (chunk, chunkGraph) => + Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) + ); + const hasCssMatcher = compileBooleanMatcher(conditionMap); + + const withLoading = + _runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) && + hasCssMatcher !== false; + /** @type {boolean} */ + const withHmr = _runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + /** @type {Set} */ + const initialChunkIds = new Set(); + for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) { + if (chunkHasCss(c, chunkGraph)) { + initialChunkIds.add(c.id); + } + } + + if (!withLoading && !withHmr) { + return null; + } + + const environment = + /** @type {Environment} */ + (compilation.outputOptions.environment); + const isNeutralPlatform = runtimeTemplate.isNeutralPlatform(); + const withPrefetch = + this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && + (environment.document || isNeutralPlatform) && + chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasCss); + const withPreload = + this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && + (environment.document || isNeutralPlatform) && + chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasCss); + + const { linkPreload, linkPrefetch } = + CssLoadingRuntimeModule.getCompilationHooks(compilation); + + const withFetchPriority = _runtimeRequirements.has( + RuntimeGlobals.hasFetchPriority + ); + + const { createStylesheet } = + CssLoadingRuntimeModule.getCompilationHooks(compilation); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css` + : undefined; + + const code = Template.asString([ + "link = document.createElement('link');", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + uniqueName + ? 'link.setAttribute("data-webpack", uniqueName + ":" + key);' + : "", + withFetchPriority + ? Template.asString([ + "if(fetchPriority) {", + Template.indent( + 'link.setAttribute("fetchpriority", fetchPriority);' + ), + "}" + ]) + : "", + "link.setAttribute(loadingAttribute, 1);", + 'link.rel = "stylesheet";', + "link.href = url;", + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + ), + "}" + ]) + : "" + ]); + + return Template.asString([ + "// object to store loaded and loading chunks", + "// undefined = chunk not loaded, null = chunk preloaded/prefetched", + "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded", + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( + ",\n" + ) + ), + "};", + "", + uniqueName + ? `var uniqueName = ${JSON.stringify( + runtimeTemplate.outputOptions.uniqueName + )};` + : "// data-webpack is not used as build has no uniqueName", + withLoading || withHmr + ? Template.asString([ + 'var loadingAttribute = "data-webpack-loading";', + `var loadStylesheet = ${runtimeTemplate.basicFunction( + `chunkId, url, done${ + withFetchPriority ? ", fetchPriority" : "" + }${withHmr ? ", hmr" : ""}`, + [ + 'var link, needAttach, key = "chunk-" + chunkId;', + withHmr ? "if(!hmr) {" : "", + 'var links = document.getElementsByTagName("link");', + "for(var i = 0; i < links.length; i++) {", + Template.indent([ + "var l = links[i];", + `if(l.rel == "stylesheet" && (${ + withHmr + ? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)' + : 'l.href == url || l.getAttribute("href") == url' + }${ + uniqueName + ? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key' + : "" + })) { link = l; break; }` + ]), + "}", + "if(!done) return link;", + withHmr ? "}" : "", + "if(!link) {", + Template.indent([ + "needAttach = true;", + createStylesheet.call(code, /** @type {Chunk} */ (this.chunk)) + ]), + "}", + `var onLinkComplete = ${runtimeTemplate.basicFunction( + "prev, event", + Template.asString([ + "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)) {", + Template.indent([ + `var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`, + "link.onerror = onLinkComplete.bind(null, link.onerror);", + "link.onload = onLinkComplete.bind(null, link.onload);" + ]), + "} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking + withHmr && withFetchPriority + ? 'if (hmr && hmr.getAttribute("fetchpriority")) link.setAttribute("fetchpriority", hmr.getAttribute("fetchpriority"));' + : "", + withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "", + "needAttach && document.head.appendChild(link);", + "return link;" + ] + )};` + ]) + : "", + withLoading + ? Template.asString([ + `${fn}.css = ${runtimeTemplate.basicFunction( + `chunkId, promises${withFetchPriority ? " , fetchPriority" : ""}`, + [ + "// css chunk loading", + `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + "", + '// a Promise means "currently loading".', + "if(installedChunkData) {", + Template.indent(["promises.push(installedChunkData[2]);"]), + "} else {", + Template.indent([ + hasCssMatcher === true + ? "if(true) { // all chunks have CSS" + : `if(${hasCssMatcher("chunkId")}) {`, + Template.indent([ + "// setup Promise in chunk cache", + `var promise = new Promise(${runtimeTemplate.expressionFunction( + "installedChunkData = installedChunks[chunkId] = [resolve, reject]", + "resolve, reject" + )});`, + "promises.push(installedChunkData[2] = promise);", + "", + "// start chunk loading", + `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + "// create error before stack unwound to get useful stacktrace later", + "var error = new Error();", + `var loadingEnded = ${runtimeTemplate.basicFunction( + "event", + [ + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, + Template.indent([ + "installedChunkData = installedChunks[chunkId];", + "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", + "if(installedChunkData) {", + Template.indent([ + 'if(event.type !== "load") {', + Template.indent([ + "var errorType = event && event.type;", + "var 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 {", + Template.indent([ + "installedChunks[chunkId] = 0;", + "installedChunkData[0]();" + ]), + "}" + ]), + "}" + ]), + "}" + ] + )};`, + isNeutralPlatform + ? "if (typeof document !== 'undefined') {" + : "", + Template.indent([ + `loadStylesheet(chunkId, url, loadingEnded${ + withFetchPriority ? ", fetchPriority" : "" + });` + ]), + isNeutralPlatform + ? "} else { loadingEnded({ type: 'load' }); }" + : "" + ]), + "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ] + )};` + ]) + : "// no chunk loading", + "", + withPrefetch && hasCssMatcher !== false + ? `${ + RuntimeGlobals.prefetchChunkHandlers + }.s = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + isNeutralPlatform + ? "if (typeof document === 'undefined') return;" + : "", + linkPrefetch.call( + Template.asString([ + "var link = document.createElement('link');", + crossOriginLoading + ? `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + : "", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "prefetch";', + 'link.as = "style";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);` + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no prefetching", + "", + withPreload && hasCssMatcher !== false + ? `${ + RuntimeGlobals.preloadChunkHandlers + }.s = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + isNeutralPlatform + ? "if (typeof document === 'undefined') return;" + : "", + linkPreload.call( + Template.asString([ + "var link = document.createElement('link');", + "link.charset = 'utf-8';", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "preload";', + 'link.as = "style";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + ), + "}" + ]) + : "" + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no preloaded", + withHmr + ? Template.asString([ + "var oldTags = [];", + "var newTags = [];", + `var applyHandler = ${runtimeTemplate.basicFunction("options", [ + `return { dispose: ${runtimeTemplate.basicFunction("", [ + "while(oldTags.length) {", + Template.indent([ + "var oldTag = oldTags.pop();", + "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);" + ]), + "}" + ])}, apply: ${runtimeTemplate.basicFunction("", [ + "while(newTags.length) {", + Template.indent([ + "var newTag = newTags.pop();", + "newTag.sheet.disabled = false" + ]), + "}" + ])} };` + ])}`, + `var cssTextKey = ${runtimeTemplate.returningFunction( + `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction( + "r.cssText", + "r" + )}).join()`, + "link" + )};`, + `${ + RuntimeGlobals.hmrDownloadUpdateHandlers + }.css = ${runtimeTemplate.basicFunction( + "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList", + [ + isNeutralPlatform + ? "if (typeof document === 'undefined') return;" + : "", + "applyHandlers.push(applyHandler);", + `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [ + `var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, + `var url = ${RuntimeGlobals.publicPath} + filename;`, + "var oldTag = loadStylesheet(chunkId, url);", + "if(!oldTag) return;", + `promises.push(new Promise(${runtimeTemplate.basicFunction( + "resolve, reject", + [ + `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction( + "event", + [ + 'if(event.type !== "load") {', + Template.indent([ + "var errorType = event && event.type;", + "var realHref = event && event.target && event.target.href;", + "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';", + "error.name = 'ChunkLoadError';", + "error.type = errorType;", + "error.request = realHref;", + "reject(error);" + ]), + "} else {", + Template.indent([ + "try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}", + "link.sheet.disabled = true;", + "oldTags.push(oldTag);", + "newTags.push(link);", + "resolve();" + ]), + "}" + ] + )}, ${withFetchPriority ? "undefined," : ""} oldTag);` + ] + )}));` + ])});` + ] + )}` + ]) + : "// no hmr" + ]); + } +} + +module.exports = CssLoadingRuntimeModule; diff --git a/webpack-lib/lib/css/CssModulesPlugin.js b/webpack-lib/lib/css/CssModulesPlugin.js new file mode 100644 index 00000000000..fcf451926f1 --- /dev/null +++ b/webpack-lib/lib/css/CssModulesPlugin.js @@ -0,0 +1,887 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook, SyncHook } = require("tapable"); +const { + ConcatSource, + PrefixSource, + ReplaceSource, + CachedSource, + RawSource +} = require("webpack-sources"); +const Compilation = require("../Compilation"); +const CssModule = require("../CssModule"); +const { tryRunOrWebpackError } = require("../HookWebpackError"); +const HotUpdateChunk = require("../HotUpdateChunk"); +const { + CSS_MODULE_TYPE, + CSS_MODULE_TYPE_GLOBAL, + CSS_MODULE_TYPE_MODULE, + CSS_MODULE_TYPE_AUTO +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const SelfModuleFactory = require("../SelfModuleFactory"); +const Template = require("../Template"); +const WebpackError = require("../WebpackError"); +const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency"); +const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency"); +const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency"); +const CssImportDependency = require("../dependencies/CssImportDependency"); +const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); +const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const createHash = require("../util/createHash"); +const { getUndoPath } = require("../util/identifier"); +const memoize = require("../util/memoize"); +const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); +const CssGenerator = require("./CssGenerator"); +const CssParser = require("./CssParser"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../CssModule").Inheritance} Inheritance */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/createHash").Algorithm} Algorithm */ +/** @typedef {import("../util/memoize")} Memoize */ + +/** + * @typedef {object} RenderContext + * @property {Chunk} chunk the chunk + * @property {ChunkGraph} chunkGraph the chunk graph + * @property {CodeGenerationResults} codeGenerationResults results of code generation + * @property {RuntimeTemplate} runtimeTemplate the runtime template + * @property {string} uniqueName the unique name + * @property {string} undoPath undo path to css file + * @property {CssModule[]} modules modules + */ + +/** + * @typedef {object} ChunkRenderContext + * @property {Chunk} chunk the chunk + * @property {ChunkGraph} chunkGraph the chunk graph + * @property {CodeGenerationResults} codeGenerationResults results of code generation + * @property {RuntimeTemplate} runtimeTemplate the runtime template + * @property {string} undoPath undo path to css file + */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage + * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash + */ + +const getCssLoadingRuntimeModule = memoize(() => + require("./CssLoadingRuntimeModule") +); + +/** + * @param {string} name name + * @returns {{oneOf: [{$ref: string}], definitions: *}} schema + */ +const getSchema = name => { + const { definitions } = require("../../schemas/WebpackOptions.json"); + return { + definitions, + oneOf: [{ $ref: `#/definitions/${name}` }] + }; +}; + +const generatorValidationOptions = { + name: "Css Modules Plugin", + baseDataPath: "generator" +}; +const validateGeneratorOptions = { + css: createSchemaValidation( + require("../../schemas/plugins/css/CssGeneratorOptions.check.js"), + () => getSchema("CssGeneratorOptions"), + generatorValidationOptions + ), + "css/auto": createSchemaValidation( + require("../../schemas/plugins/css/CssAutoGeneratorOptions.check.js"), + () => getSchema("CssAutoGeneratorOptions"), + generatorValidationOptions + ), + "css/module": createSchemaValidation( + require("../../schemas/plugins/css/CssModuleGeneratorOptions.check.js"), + () => getSchema("CssModuleGeneratorOptions"), + generatorValidationOptions + ), + "css/global": createSchemaValidation( + require("../../schemas/plugins/css/CssGlobalGeneratorOptions.check.js"), + () => getSchema("CssGlobalGeneratorOptions"), + generatorValidationOptions + ) +}; + +const parserValidationOptions = { + name: "Css Modules Plugin", + baseDataPath: "parser" +}; +const validateParserOptions = { + css: createSchemaValidation( + require("../../schemas/plugins/css/CssParserOptions.check.js"), + () => getSchema("CssParserOptions"), + parserValidationOptions + ), + "css/auto": createSchemaValidation( + require("../../schemas/plugins/css/CssAutoParserOptions.check.js"), + () => getSchema("CssAutoParserOptions"), + parserValidationOptions + ), + "css/module": createSchemaValidation( + require("../../schemas/plugins/css/CssModuleParserOptions.check.js"), + () => getSchema("CssModuleParserOptions"), + parserValidationOptions + ), + "css/global": createSchemaValidation( + require("../../schemas/plugins/css/CssGlobalParserOptions.check.js"), + () => getSchema("CssGlobalParserOptions"), + parserValidationOptions + ) +}; + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +const PLUGIN_NAME = "CssModulesPlugin"; + +class CssModulesPlugin { + /** + * @param {Compilation} compilation the compilation + * @returns {CompilationHooks} 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 = { + renderModulePackage: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]), + chunkHash: new SyncHook(["chunk", "hash", "context"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + constructor() { + /** @type {WeakMap} */ + this._moduleFactoryCache = new WeakMap(); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const hooks = CssModulesPlugin.getCompilationHooks(compilation); + const selfFactory = new SelfModuleFactory(compilation.moduleGraph); + compilation.dependencyFactories.set( + CssImportDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CssImportDependency, + new CssImportDependency.Template() + ); + compilation.dependencyFactories.set( + CssUrlDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CssUrlDependency, + new CssUrlDependency.Template() + ); + compilation.dependencyTemplates.set( + CssLocalIdentifierDependency, + new CssLocalIdentifierDependency.Template() + ); + compilation.dependencyFactories.set( + CssSelfLocalIdentifierDependency, + selfFactory + ); + compilation.dependencyTemplates.set( + CssSelfLocalIdentifierDependency, + new CssSelfLocalIdentifierDependency.Template() + ); + compilation.dependencyFactories.set( + CssIcssImportDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CssIcssImportDependency, + new CssIcssImportDependency.Template() + ); + compilation.dependencyTemplates.set( + CssIcssExportDependency, + new CssIcssExportDependency.Template() + ); + compilation.dependencyTemplates.set( + CssIcssSymbolDependency, + new CssIcssSymbolDependency.Template() + ); + compilation.dependencyTemplates.set( + StaticExportsDependency, + new StaticExportsDependency.Template() + ); + for (const type of [ + CSS_MODULE_TYPE, + CSS_MODULE_TYPE_GLOBAL, + CSS_MODULE_TYPE_MODULE, + CSS_MODULE_TYPE_AUTO + ]) { + normalModuleFactory.hooks.createParser + .for(type) + .tap(PLUGIN_NAME, parserOptions => { + validateParserOptions[type](parserOptions); + const { url, import: importOption, namedExports } = parserOptions; + + switch (type) { + case CSS_MODULE_TYPE: + return new CssParser({ + importOption, + url, + namedExports + }); + case CSS_MODULE_TYPE_GLOBAL: + return new CssParser({ + defaultMode: "global", + importOption, + url, + namedExports + }); + case CSS_MODULE_TYPE_MODULE: + return new CssParser({ + defaultMode: "local", + importOption, + url, + namedExports + }); + case CSS_MODULE_TYPE_AUTO: + return new CssParser({ + defaultMode: "auto", + importOption, + url, + namedExports + }); + } + }); + normalModuleFactory.hooks.createGenerator + .for(type) + .tap(PLUGIN_NAME, generatorOptions => { + validateGeneratorOptions[type](generatorOptions); + + return new CssGenerator(generatorOptions); + }); + normalModuleFactory.hooks.createModuleClass + .for(type) + .tap(PLUGIN_NAME, (createData, resolveData) => { + if (resolveData.dependencies.length > 0) { + // When CSS is imported from CSS there is only one dependency + const dependency = resolveData.dependencies[0]; + + if (dependency instanceof CssImportDependency) { + const parent = + /** @type {CssModule} */ + (compilation.moduleGraph.getParentModule(dependency)); + + if (parent instanceof CssModule) { + /** @type {import("../CssModule").Inheritance | undefined} */ + let inheritance; + + if ( + parent.cssLayer !== undefined || + parent.supports || + parent.media + ) { + if (!inheritance) { + inheritance = []; + } + + inheritance.push([ + parent.cssLayer, + parent.supports, + parent.media + ]); + } + + if (parent.inheritance) { + if (!inheritance) { + inheritance = []; + } + + inheritance.push(...parent.inheritance); + } + + return new CssModule({ + ...createData, + cssLayer: dependency.layer, + supports: dependency.supports, + media: dependency.media, + inheritance + }); + } + + return new CssModule({ + ...createData, + cssLayer: dependency.layer, + supports: dependency.supports, + media: dependency.media + }); + } + } + + return new CssModule(createData); + }); + } + + JavascriptModulesPlugin.getCompilationHooks( + compilation + ).renderModuleContent.tap(PLUGIN_NAME, (source, module) => { + if (module instanceof CssModule && module.hot) { + const exports = module.buildInfo.cssData.exports; + const stringifiedExports = JSON.stringify( + JSON.stringify( + Array.from(exports).reduce((obj, [key, value]) => { + obj[key] = value; + return obj; + }, {}) + ) + ); + + const hmrCode = Template.asString([ + "", + `var __webpack_css_exports__ = ${stringifiedExports};`, + "// only invalidate when locals change", + "if (module.hot.data && module.hot.data.__webpack_css_exports__ && module.hot.data.__webpack_css_exports__ != __webpack_css_exports__) {", + Template.indent("module.hot.invalidate();"), + "} else {", + Template.indent("module.hot.accept();"), + "}", + "module.hot.dispose(function(data) { data.__webpack_css_exports__ = __webpack_css_exports__; });" + ]); + + return new ConcatSource(source, "\n", new RawSource(hmrCode)); + } + }); + const orderedCssModulesPerChunk = new WeakMap(); + compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => { + const { chunkGraph } = compilation; + for (const chunk of compilation.chunks) { + if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) { + orderedCssModulesPerChunk.set( + chunk, + this.getOrderedChunkCssModules(chunk, chunkGraph, compilation) + ); + } + } + }); + compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => { + hooks.chunkHash.call(chunk, hash, context); + }); + compilation.hooks.contentHash.tap(PLUGIN_NAME, chunk => { + const { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate, + outputOptions: { + hashSalt, + hashDigest, + hashDigestLength, + hashFunction + } + } = compilation; + const hash = createHash(/** @type {Algorithm} */ (hashFunction)); + if (hashSalt) hash.update(hashSalt); + hooks.chunkHash.call(chunk, hash, { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate + }); + const modules = orderedCssModulesPerChunk.get(chunk); + if (modules) { + for (const module of modules) { + hash.update(chunkGraph.getModuleHash(module, chunk.runtime)); + } + } + const digest = /** @type {string} */ (hash.digest(hashDigest)); + chunk.contentHash.css = nonNumericOnlyHash( + digest, + /** @type {number} */ + (hashDigestLength) + ); + }); + compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { + const { chunkGraph } = compilation; + const { hash, chunk, codeGenerationResults, runtimeTemplate } = + options; + + if (chunk instanceof HotUpdateChunk) return result; + + /** @type {CssModule[] | undefined} */ + const modules = orderedCssModulesPerChunk.get(chunk); + if (modules !== undefined) { + const { path: filename, info } = compilation.getPathWithInfo( + CssModulesPlugin.getChunkFilenameTemplate( + chunk, + compilation.outputOptions + ), + { + hash, + runtime: chunk.runtime, + chunk, + contentHashType: "css" + } + ); + const undoPath = getUndoPath( + filename, + /** @type {string} */ + (compilation.outputOptions.path), + false + ); + result.push({ + render: () => + this.renderChunk( + { + chunk, + chunkGraph, + codeGenerationResults, + uniqueName: compilation.outputOptions.uniqueName, + undoPath, + modules, + runtimeTemplate + }, + hooks + ), + filename, + info, + identifier: `css${chunk.id}`, + hash: chunk.contentHash.css + }); + } + return result; + }); + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, when enabled + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === "jsonp" || chunkLoading === "import"; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk to check + * @param {Set} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + + set.add(RuntimeGlobals.makeNamespaceObject); + + const CssLoadingRuntimeModule = getCssLoadingRuntimeModule(); + compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set)); + }; + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hasCssModules) + .tap(PLUGIN_NAME, handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => + m.type === CSS_MODULE_TYPE || + m.type === CSS_MODULE_TYPE_GLOBAL || + m.type === CSS_MODULE_TYPE_MODULE || + m.type === CSS_MODULE_TYPE_AUTO + ) + ) { + return; + } + + set.add(RuntimeGlobals.hasOwnProperty); + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getChunkCssFilename); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => + m.type === CSS_MODULE_TYPE || + m.type === CSS_MODULE_TYPE_GLOBAL || + m.type === CSS_MODULE_TYPE_MODULE || + m.type === CSS_MODULE_TYPE_AUTO + ) + ) { + return; + } + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getChunkCssFilename); + }); + } + ); + } + + /** + * @param {Chunk} chunk chunk + * @param {Iterable} modules unordered modules + * @param {Compilation} compilation compilation + * @returns {Module[]} ordered modules + */ + getModulesInOrder(chunk, modules, compilation) { + if (!modules) return []; + + /** @type {Module[]} */ + const modulesList = [...modules]; + + // Get ordered list of modules per chunk group + // Lists are in reverse order to allow to use Array.pop() + const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => { + const sortedModules = modulesList + .map(module => ({ + module, + index: chunkGroup.getModulePostOrderIndex(module) + })) + .filter(item => item.index !== undefined) + .sort( + (a, b) => + /** @type {number} */ (b.index) - /** @type {number} */ (a.index) + ) + .map(item => item.module); + + return { list: sortedModules, set: new Set(sortedModules) }; + }); + + if (modulesByChunkGroup.length === 1) + return modulesByChunkGroup[0].list.reverse(); + + /** + * @param {{ list: Module[] }} a a + * @param {{ list: Module[] }} b b + * @returns {-1 | 0 | 1} result + */ + const compareModuleLists = ({ list: a }, { list: b }) => { + if (a.length === 0) { + return b.length === 0 ? 0 : 1; + } + if (b.length === 0) return -1; + return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]); + }; + + modulesByChunkGroup.sort(compareModuleLists); + + /** @type {Module[]} */ + const finalModules = []; + + for (;;) { + const failedModules = new Set(); + const list = modulesByChunkGroup[0].list; + if (list.length === 0) { + // done, everything empty + break; + } + /** @type {Module} */ + let selectedModule = list[list.length - 1]; + let hasFailed; + outer: for (;;) { + for (const { list, set } of modulesByChunkGroup) { + if (list.length === 0) continue; + const lastModule = list[list.length - 1]; + if (lastModule === selectedModule) continue; + if (!set.has(selectedModule)) continue; + failedModules.add(selectedModule); + if (failedModules.has(lastModule)) { + // There is a conflict, try other alternatives + hasFailed = lastModule; + continue; + } + selectedModule = lastModule; + hasFailed = false; + continue outer; // restart + } + break; + } + if (hasFailed) { + // There is a not resolve-able conflict with the selectedModule + // TODO print better warning + compilation.warnings.push( + new WebpackError( + `chunk ${chunk.name || chunk.id}\nConflicting order between ${ + /** @type {Module} */ + (hasFailed).readableIdentifier(compilation.requestShortener) + } and ${selectedModule.readableIdentifier( + compilation.requestShortener + )}` + ) + ); + selectedModule = /** @type {Module} */ (hasFailed); + } + // Insert the selected module into the final modules list + finalModules.push(selectedModule); + // Remove the selected module from all lists + for (const { list, set } of modulesByChunkGroup) { + const lastModule = list[list.length - 1]; + if (lastModule === selectedModule) list.pop(); + else if (hasFailed && set.has(selectedModule)) { + const idx = list.indexOf(selectedModule); + if (idx >= 0) list.splice(idx, 1); + } + } + modulesByChunkGroup.sort(compareModuleLists); + } + return finalModules; + } + + /** + * @param {Chunk} chunk chunk + * @param {ChunkGraph} chunkGraph chunk graph + * @param {Compilation} compilation compilation + * @returns {Module[]} ordered css modules + */ + getOrderedChunkCssModules(chunk, chunkGraph, compilation) { + return [ + ...this.getModulesInOrder( + chunk, + /** @type {Iterable} */ + ( + chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "css-import", + compareModulesByIdentifier + ) + ), + compilation + ), + ...this.getModulesInOrder( + chunk, + /** @type {Iterable} */ + ( + chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "css", + compareModulesByIdentifier + ) + ), + compilation + ) + ]; + } + + /** + * @param {CssModule} module css module + * @param {ChunkRenderContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @returns {Source} css module source + */ + renderModule(module, renderContext, hooks) { + const { codeGenerationResults, chunk, undoPath } = renderContext; + const codeGenResult = codeGenerationResults.get(module, chunk.runtime); + const moduleSourceContent = + /** @type {Source} */ + ( + codeGenResult.sources.get("css") || + codeGenResult.sources.get("css-import") + ); + const cacheEntry = this._moduleFactoryCache.get(moduleSourceContent); + + /** @type {Inheritance} */ + const inheritance = [[module.cssLayer, module.supports, module.media]]; + if (module.inheritance) { + inheritance.push(...module.inheritance); + } + + let source; + if ( + cacheEntry && + cacheEntry.undoPath === undoPath && + cacheEntry.inheritance.every(([layer, supports, media], i) => { + const item = inheritance[i]; + if (Array.isArray(item)) { + return layer === item[0] && supports === item[1] && media === item[2]; + } + return false; + }) + ) { + source = cacheEntry.source; + } else { + const moduleSourceCode = + /** @type {string} */ + (moduleSourceContent.source()); + const publicPathAutoRegex = new RegExp( + CssUrlDependency.PUBLIC_PATH_AUTO, + "g" + ); + /** @type {Source} */ + let moduleSource = new ReplaceSource(moduleSourceContent); + let match; + while ((match = publicPathAutoRegex.exec(moduleSourceCode))) { + /** @type {ReplaceSource} */ (moduleSource).replace( + match.index, + (match.index += match[0].length - 1), + undoPath + ); + } + + for (let i = 0; i < inheritance.length; i++) { + const layer = inheritance[i][0]; + const supports = inheritance[i][1]; + const media = inheritance[i][2]; + + if (media) { + moduleSource = new ConcatSource( + `@media ${media} {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + + if (supports) { + moduleSource = new ConcatSource( + `@supports (${supports}) {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + + // Layer can be anonymous + if (layer !== undefined && layer !== null) { + moduleSource = new ConcatSource( + `@layer${layer ? ` ${layer}` : ""} {\n`, + new PrefixSource("\t", moduleSource), + "}\n" + ); + } + } + + if (moduleSource) { + moduleSource = new ConcatSource(moduleSource, "\n"); + } + + source = new CachedSource(moduleSource); + this._moduleFactoryCache.set(moduleSourceContent, { + inheritance, + undoPath, + source + }); + } + + return tryRunOrWebpackError( + () => hooks.renderModulePackage.call(source, module, renderContext), + "CssModulesPlugin.getCompilationHooks().renderModulePackage" + ); + } + + /** + * @param {RenderContext} renderContext the render context + * @param {CompilationHooks} hooks hooks + * @returns {Source} generated source + */ + renderChunk( + { + undoPath, + chunk, + chunkGraph, + codeGenerationResults, + modules, + runtimeTemplate + }, + hooks + ) { + const source = new ConcatSource(); + for (const module of modules) { + try { + const moduleSource = this.renderModule( + module, + { + undoPath, + chunk, + chunkGraph, + codeGenerationResults, + runtimeTemplate + }, + hooks + ); + source.add(moduleSource); + } catch (err) { + /** @type {Error} */ + (err).message += `\nduring rendering of css ${module.identifier()}`; + throw err; + } + } + chunk.rendered = true; + return source; + } + + /** + * @param {Chunk} chunk chunk + * @param {OutputOptions} outputOptions output options + * @returns {TemplatePath} used filename template + */ + static getChunkFilenameTemplate(chunk, outputOptions) { + if (chunk.cssFilenameTemplate) { + return chunk.cssFilenameTemplate; + } else if (chunk.canBeInitial()) { + return /** @type {TemplatePath} */ (outputOptions.cssFilename); + } + return /** @type {TemplatePath} */ (outputOptions.cssChunkFilename); + } + + /** + * @param {Chunk} chunk chunk + * @param {ChunkGraph} chunkGraph chunk graph + * @returns {boolean} true, when the chunk has css + */ + static chunkHasCss(chunk, chunkGraph) { + return ( + Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) || + Boolean( + chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import") + ) + ); + } +} + +module.exports = CssModulesPlugin; diff --git a/webpack-lib/lib/css/CssParser.js b/webpack-lib/lib/css/CssParser.js new file mode 100644 index 00000000000..c8ae863d9a5 --- /dev/null +++ b/webpack-lib/lib/css/CssParser.js @@ -0,0 +1,1602 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const vm = require("vm"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const ModuleDependencyWarning = require("../ModuleDependencyWarning"); +const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants"); +const Parser = require("../Parser"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const WebpackError = require("../WebpackError"); +const ConstDependency = require("../dependencies/ConstDependency"); +const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency"); +const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency"); +const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency"); +const CssImportDependency = require("../dependencies/CssImportDependency"); +const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); +const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); +const CssUrlDependency = require("../dependencies/CssUrlDependency"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const binarySearchBounds = require("../util/binarySearchBounds"); +const { parseResource } = require("../util/identifier"); +const { + webpackCommentRegExp, + createMagicCommentContext +} = require("../util/magicComment"); +const walkCssTokens = require("./walkCssTokens"); + +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ +/** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */ + +/** @typedef {[number, number]} Range */ +/** @typedef {{ line: number, column: number }} Position */ +/** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */ + +const CC_COLON = ":".charCodeAt(0); +const CC_SLASH = "/".charCodeAt(0); +const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); +const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); +const CC_LOWER_F = "f".charCodeAt(0); +const CC_UPPER_F = "F".charCodeAt(0); + +// https://www.w3.org/TR/css-syntax-3/#newline +// We don't have `preprocessing` stage, so we need specify all of them +const STRING_MULTILINE = /\\[\n\r\f]/g; +// https://www.w3.org/TR/css-syntax-3/#whitespace +const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g; +const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g; +const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i; +const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/; +const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY = + /^(-\w+-)?animation(-name)?$/i; +const IS_MODULES = /\.module(s)?\.[^.]+$/i; +const CSS_COMMENT = /\/\*((?!\*\/).*?)\*\//g; + +/** + * @param {string} str url string + * @param {boolean} isString is url wrapped in quotes + * @returns {string} normalized url + */ +const normalizeUrl = (str, isString) => { + // Remove extra spaces and newlines: + // `url("im\ + // g.png")` + if (isString) { + str = str.replace(STRING_MULTILINE, ""); + } + + str = str + // Remove unnecessary spaces from `url(" img.png ")` + .replace(TRIM_WHITE_SPACES, "") + // Unescape + .replace(UNESCAPE, match => { + if (match.length > 2) { + return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16)); + } + return match[1]; + }); + + if (/^data:/i.test(str)) { + return str; + } + + if (str.includes("%")) { + // Convert `url('%2E/img.png')` -> `url('./img.png')` + try { + str = decodeURIComponent(str); + } catch (_err) { + // Ignore + } + } + + return str; +}; + +// eslint-disable-next-line no-useless-escape +const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/; +const regexExcessiveSpaces = + /(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g; + +/** + * @param {string} str string + * @returns {string} escaped identifier + */ +const escapeIdentifier = str => { + let output = ""; + let counter = 0; + + while (counter < str.length) { + const character = str.charAt(counter++); + + let value; + + if (/[\t\n\f\r\u000B]/.test(character)) { + const codePoint = character.charCodeAt(0); + + value = `\\${codePoint.toString(16).toUpperCase()} `; + } else if (character === "\\" || regexSingleEscape.test(character)) { + value = `\\${character}`; + } else { + value = character; + } + + output += value; + } + + const firstChar = str.charAt(0); + + if (/^-[-\d]/.test(output)) { + output = `\\-${output.slice(1)}`; + } else if (/\d/.test(firstChar)) { + output = `\\3${firstChar} ${output.slice(1)}`; + } + + // Remove spaces after `\HEX` escapes that are not followed by a hex digit, + // since they’re redundant. Note that this is only possible if the escape + // sequence isn’t preceded by an odd number of backslashes. + output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => { + if ($1 && $1.length % 2) { + // It’s not safe to remove the space, so don’t. + return $0; + } + + // Strip the space. + return ($1 || "") + $2; + }); + + return output; +}; + +const CONTAINS_ESCAPE = /\\/; + +/** + * @param {string} str string + * @returns {[string, number] | undefined} hex + */ +const gobbleHex = str => { + const lower = str.toLowerCase(); + let hex = ""; + let spaceTerminated = false; + + for (let i = 0; i < 6 && lower[i] !== undefined; i++) { + const code = lower.charCodeAt(i); + // check to see if we are dealing with a valid hex char [a-f|0-9] + const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57); + // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point + spaceTerminated = code === 32; + if (!valid) break; + hex += lower[i]; + } + + if (hex.length === 0) return undefined; + + const codePoint = Number.parseInt(hex, 16); + const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff; + + // Add special case for + // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point" + // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point + if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) { + return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)]; + } + + return [ + String.fromCodePoint(codePoint), + hex.length + (spaceTerminated ? 1 : 0) + ]; +}; + +/** + * @param {string} str string + * @returns {string} unescaped string + */ +const unescapeIdentifier = str => { + const needToProcess = CONTAINS_ESCAPE.test(str); + if (!needToProcess) return str; + let ret = ""; + for (let i = 0; i < str.length; i++) { + if (str[i] === "\\") { + const gobbled = gobbleHex(str.slice(i + 1, i + 7)); + if (gobbled !== undefined) { + ret += gobbled[0]; + i += gobbled[1]; + continue; + } + // Retain a pair of \\ if double escaped `\\\\` + // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e + if (str[i + 1] === "\\") { + ret += "\\"; + i += 1; + continue; + } + // if \\ is at the end of the string retain it + // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb + if (str.length === i + 1) { + ret += str[i]; + } + continue; + } + ret += str[i]; + } + + return ret; +}; + +class LocConverter { + /** + * @param {string} input input + */ + constructor(input) { + this._input = input; + this.line = 1; + this.column = 0; + this.pos = 0; + } + + /** + * @param {number} pos position + * @returns {LocConverter} location converter + */ + get(pos) { + if (this.pos !== pos) { + if (this.pos < pos) { + const str = this._input.slice(this.pos, pos); + let i = str.lastIndexOf("\n"); + if (i === -1) { + this.column += str.length; + } else { + this.column = str.length - i - 1; + this.line++; + while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) + this.line++; + } + } else { + let i = this._input.lastIndexOf("\n", this.pos); + while (i >= pos) { + this.line--; + i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1; + } + this.column = pos - i; + } + this.pos = pos; + } + return this; + } +} + +const EMPTY_COMMENT_OPTIONS = { + options: null, + errors: null +}; + +const CSS_MODE_TOP_LEVEL = 0; +const CSS_MODE_IN_BLOCK = 1; + +const eatUntilSemi = walkCssTokens.eatUntil(";"); +const eatUntilLeftCurly = walkCssTokens.eatUntil("{"); +const eatSemi = walkCssTokens.eatUntil(";"); + +class CssParser extends Parser { + /** + * @param {object} options options + * @param {boolean=} options.importOption need handle `@import` + * @param {boolean=} options.url need handle URLs + * @param {("pure" | "global" | "local" | "auto")=} options.defaultMode default mode + * @param {boolean=} options.namedExports is named exports + */ + constructor({ + defaultMode = "pure", + importOption = true, + url = true, + namedExports = true + } = {}) { + super(); + this.defaultMode = defaultMode; + this.import = importOption; + this.url = url; + this.namedExports = namedExports; + /** @type {Comment[] | undefined} */ + this.comments = undefined; + this.magicCommentContext = createMagicCommentContext(); + } + + /** + * @param {ParserState} state parser state + * @param {string} message warning message + * @param {LocConverter} locConverter location converter + * @param {number} start start offset + * @param {number} end end offset + */ + _emitWarning(state, message, locConverter, start, end) { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + + state.current.addWarning( + new ModuleDependencyWarning(state.module, new WebpackError(message), { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + }) + ); + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (Buffer.isBuffer(source)) { + source = source.toString("utf-8"); + } else if (typeof source === "object") { + throw new Error("webpackAst is unexpected for the CssParser"); + } + if (source[0] === "\uFEFF") { + source = source.slice(1); + } + + let mode = this.defaultMode; + + const module = state.module; + + if ( + mode === "auto" && + module.type === CSS_MODULE_TYPE_AUTO && + IS_MODULES.test( + parseResource(module.matchResource || module.resource).path + ) + ) { + mode = "local"; + } + + const isModules = mode === "global" || mode === "local"; + + const locConverter = new LocConverter(source); + + /** @type {number} */ + let scope = CSS_MODE_TOP_LEVEL; + /** @type {boolean} */ + let allowImportAtRule = true; + /** @type [string, number, number][] */ + const balanced = []; + let lastTokenEndForComments = 0; + + /** @type {boolean} */ + let isNextRulePrelude = isModules; + /** @type {number} */ + let blockNestingLevel = 0; + /** @type {"local" | "global" | undefined} */ + let modeData; + /** @type {boolean} */ + let inAnimationProperty = false; + /** @type {[number, number, boolean] | undefined} */ + let lastIdentifier; + /** @type {Set} */ + const declaredCssVariables = new Set(); + /** @type {Map} */ + const icssDefinitions = new Map(); + + /** + * @param {string} input input + * @param {number} pos position + * @returns {boolean} true, when next is nested syntax + */ + const isNextNestedSyntax = (input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + + if (input[pos] === "}") { + return false; + } + + // According spec only identifier can be used as a property name + const isIdentifier = walkCssTokens.isIdentStartCodePoint( + input.charCodeAt(pos) + ); + + return !isIdentifier; + }; + /** + * @returns {boolean} true, when in local scope + */ + const isLocalMode = () => + modeData === "local" || (mode === "local" && modeData === undefined); + + /** + * @param {string} input input + * @param {number} pos start position + * @param {(input: string, pos: number) => number} eater eater + * @returns {[number,string]} new position and text + */ + const eatText = (input, pos, eater) => { + let text = ""; + for (;;) { + if (input.charCodeAt(pos) === CC_SLASH) { + const newPos = walkCssTokens.eatComments(input, pos); + if (pos !== newPos) { + pos = newPos; + if (pos === input.length) break; + } else { + text += "/"; + pos++; + if (pos === input.length) break; + } + } + const newPos = eater(input, pos); + if (pos !== newPos) { + text += input.slice(pos, newPos); + pos = newPos; + } else { + break; + } + if (pos === input.length) break; + } + return [pos, text.trimEnd()]; + }; + + /** + * @param {0 | 1} type import or export + * @param {string} input input + * @param {number} pos start position + * @returns {number} position after parse + */ + const parseImportOrExport = (type, input, pos) => { + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + let importPath; + if (type === 0) { + let cc = input.charCodeAt(pos); + if (cc !== CC_LEFT_PARENTHESIS) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`, + locConverter, + pos, + pos + ); + return pos; + } + pos++; + const stringStart = pos; + const str = walkCssTokens.eatString(input, pos); + if (!str) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected string)`, + locConverter, + stringStart, + pos + ); + return pos; + } + importPath = input.slice(str[0] + 1, str[1] - 1); + pos = str[1]; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + cc = input.charCodeAt(pos); + if (cc !== CC_RIGHT_PARENTHESIS) { + this._emitWarning( + state, + `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`, + locConverter, + pos, + pos + ); + return pos; + } + pos++; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + } + + /** + * @param {string} name name + * @param {string} value value + * @param {number} start start of position + * @param {number} end end of position + */ + const createDep = (name, value, start, end) => { + if (type === 0) { + icssDefinitions.set(name, { + path: /** @type {string} */ (importPath), + value + }); + } else if (type === 1) { + const dep = new CssIcssExportDependency(name, value); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + }; + + let needTerminate = false; + let balanced = 0; + /** @type {undefined | 0 | 1 | 2} */ + let scope; + + /** @type {[number, number] | undefined} */ + let name; + /** @type {number | undefined} */ + let value; + + /** @type {CssTokenCallbacks} */ + const callbacks = { + leftCurlyBracket: (_input, _start, end) => { + balanced++; + + if (scope === undefined) { + scope = 0; + } + + return end; + }, + rightCurlyBracket: (_input, _start, end) => { + balanced--; + + if (scope === 2) { + createDep( + input.slice(name[0], name[1]), + input.slice(value, end - 1).trim(), + name[1], + end - 1 + ); + scope = 0; + } + + if (balanced === 0 && scope === 0) { + needTerminate = true; + } + + return end; + }, + identifier: (_input, start, end) => { + if (scope === 0) { + name = [start, end]; + scope = 1; + } + + return end; + }, + colon: (_input, _start, end) => { + if (scope === 1) { + scope = 2; + value = walkCssTokens.eatWhitespace(input, end); + return value; + } + + return end; + }, + semicolon: (input, _start, end) => { + if (scope === 2) { + createDep( + input.slice(name[0], name[1]), + input.slice(value, end - 1), + name[1], + end - 1 + ); + scope = 0; + } + + return end; + }, + needTerminate: () => needTerminate + }; + + pos = walkCssTokens(input, pos, callbacks); + pos = walkCssTokens.eatWhiteLine(input, pos); + + return pos; + }; + const eatPropertyName = walkCssTokens.eatUntil(":{};"); + /** + * @param {string} input input + * @param {number} pos name start position + * @param {number} end name end position + * @returns {number} position after handling + */ + const processLocalDeclaration = (input, pos, end) => { + modeData = undefined; + pos = walkCssTokens.eatWhitespaceAndComments(input, pos); + const propertyNameStart = pos; + const [propertyNameEnd, propertyName] = eatText( + input, + pos, + eatPropertyName + ); + if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end; + pos = propertyNameEnd + 1; + if (propertyName.startsWith("--") && propertyName.length >= 3) { + // CSS Variable + const { line: sl, column: sc } = locConverter.get(propertyNameStart); + const { line: el, column: ec } = locConverter.get(propertyNameEnd); + const name = unescapeIdentifier(propertyName.slice(2)); + const dep = new CssLocalIdentifierDependency( + name, + [propertyNameStart, propertyNameEnd], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + declaredCssVariables.add(name); + } else if ( + OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName) + ) { + inAnimationProperty = true; + } + return pos; + }; + /** + * @param {string} input input + */ + const processDeclarationValueDone = input => { + if (inAnimationProperty && lastIdentifier) { + const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]); + const { line: el, column: ec } = locConverter.get(lastIdentifier[1]); + const name = unescapeIdentifier( + lastIdentifier[2] + ? input.slice(lastIdentifier[0], lastIdentifier[1]) + : input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1) + ); + const dep = new CssSelfLocalIdentifierDependency(name, [ + lastIdentifier[0], + lastIdentifier[1] + ]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + lastIdentifier = undefined; + } + }; + + /** + * @param {string} input input + * @param {number} start start + * @param {number} end end + * @returns {number} end + */ + const comment = (input, start, end) => { + if (!this.comments) this.comments = []; + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + + /** @type {Comment} */ + const comment = { + value: input.slice(start + 2, end - 2), + range: [start, end], + loc: { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + } + }; + this.comments.push(comment); + return end; + }; + + walkCssTokens(source, 0, { + comment, + leftCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + allowImportAtRule = false; + scope = CSS_MODE_IN_BLOCK; + + if (isModules) { + blockNestingLevel = 1; + isNextRulePrelude = isNextNestedSyntax(input, end); + } + + break; + } + case CSS_MODE_IN_BLOCK: { + if (isModules) { + blockNestingLevel++; + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + rightCurlyBracket: (input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (--blockNestingLevel === 0) { + scope = CSS_MODE_TOP_LEVEL; + + if (isModules) { + isNextRulePrelude = true; + modeData = undefined; + } + } else if (isModules) { + if (isLocalMode()) { + processDeclarationValueDone(input); + inAnimationProperty = false; + } + + isNextRulePrelude = isNextNestedSyntax(input, end); + } + break; + } + } + return end; + }, + url: (input, start, end, contentStart, contentEnd) => { + if (!this.url) { + return end; + } + + const { options, errors: commentErrors } = this.parseCommentOptions([ + lastTokenEndForComments, + end + ]); + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + if (options && options.webpackIgnore !== undefined) { + if (typeof options.webpackIgnore !== "boolean") { + const { line: sl, column: sc } = locConverter.get( + lastTokenEndForComments + ); + const { line: el, column: ec } = locConverter.get(end); + + state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, + { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + } + ) + ); + } else if (options.webpackIgnore) { + return end; + } + } + const value = normalizeUrl( + input.slice(contentStart, contentEnd), + false + ); + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) return end; + const dep = new CssUrlDependency(value, [start, end], "url"); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + return end; + }, + string: (_input, start, end) => { + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (inAnimationProperty && balanced.length === 0) { + lastIdentifier = [start, end, false]; + } + } + } + return end; + }, + atKeyword: (input, start, end) => { + const name = input.slice(start, end).toLowerCase(); + + switch (name) { + case "@namespace": { + this._emitWarning( + state, + "'@namespace' is not supported in bundled CSS", + locConverter, + start, + end + ); + + return eatUntilSemi(input, start); + } + case "@import": { + if (!this.import) { + return eatSemi(input, end); + } + + if (!allowImportAtRule) { + this._emitWarning( + state, + "Any '@import' rules must precede all other rules", + locConverter, + start, + end + ); + return end; + } + + const tokens = walkCssTokens.eatImportTokens(input, end, { + comment + }); + if (!tokens[3]) return end; + const semi = tokens[3][1]; + if (!tokens[0]) { + this._emitWarning( + state, + `Expected URL in '${input.slice(start, semi)}'`, + locConverter, + start, + semi + ); + return end; + } + + const urlToken = tokens[0]; + const url = normalizeUrl( + input.slice(urlToken[2], urlToken[3]), + true + ); + const newline = walkCssTokens.eatWhiteLine(input, semi); + const { options, errors: commentErrors } = this.parseCommentOptions( + [end, urlToken[1]] + ); + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + if (options && options.webpackIgnore !== undefined) { + if (typeof options.webpackIgnore !== "boolean") { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(newline); + + state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, + { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + } + ) + ); + } else if (options.webpackIgnore) { + return newline; + } + } + if (url.length === 0) { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(newline); + const dep = new ConstDependency("", [start, newline]); + module.addPresentationalDependency(dep); + dep.setLoc(sl, sc, el, ec); + + return newline; + } + + let layer; + + if (tokens[1]) { + layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim(); + } + + let supports; + + if (tokens[2]) { + supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim(); + } + + const last = tokens[2] || tokens[1] || tokens[0]; + const mediaStart = walkCssTokens.eatWhitespaceAndComments( + input, + last[1] + ); + + let media; + + if (mediaStart !== semi - 1) { + media = input.slice(mediaStart, semi - 1).trim(); + } + + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(newline); + const dep = new CssImportDependency( + url, + [start, newline], + layer, + supports && supports.length > 0 ? supports : undefined, + media && media.length > 0 ? media : undefined + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + + return newline; + } + default: { + if (isModules) { + if (name === "@value") { + const semi = eatUntilSemi(input, end); + const atRuleEnd = semi + 1; + const params = input.slice(end, semi); + let [alias, from] = params.split(/\s*from\s*/); + + if (from) { + const aliases = alias + .replace(CSS_COMMENT, " ") + .trim() + .replace(/^\(|\)$/g, "") + .split(/\s*,\s*/); + + from = from.replace(CSS_COMMENT, "").trim(); + + const isExplicitImport = from[0] === "'" || from[0] === '"'; + + if (isExplicitImport) { + from = from.slice(1, -1); + } + + for (const alias of aliases) { + const [name, aliasName] = alias.split(/\s*as\s*/); + + icssDefinitions.set(aliasName || name, { + value: name, + path: from + }); + } + } else { + const ident = walkCssTokens.eatIdentSequence(alias, 0); + + if (!ident) { + this._emitWarning( + state, + `Broken '@value' at-rule: ${input.slice( + start, + atRuleEnd + )}'`, + locConverter, + start, + atRuleEnd + ); + + const dep = new ConstDependency("", [start, atRuleEnd]); + module.addPresentationalDependency(dep); + return atRuleEnd; + } + + const pos = walkCssTokens.eatWhitespaceAndComments( + alias, + ident[1] + ); + + const name = alias.slice(ident[0], ident[1]); + let value = + alias.charCodeAt(pos) === CC_COLON + ? alias.slice(pos + 1) + : alias.slice(ident[1]); + + if (value && !/^\s+$/.test(value)) { + value = value.trim(); + } + + if (icssDefinitions.has(value)) { + const def = icssDefinitions.get(value); + + value = def.value; + } + + icssDefinitions.set(name, { value }); + + const dep = new CssIcssExportDependency(name, value); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + const dep = new ConstDependency("", [start, atRuleEnd]); + module.addPresentationalDependency(dep); + return atRuleEnd; + } else if ( + OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) && + isLocalMode() + ) { + const ident = walkCssTokens.eatIdentSequenceOrString( + input, + end + ); + if (!ident) return end; + const name = unescapeIdentifier( + ident[2] === true + ? input.slice(ident[0], ident[1]) + : input.slice(ident[0] + 1, ident[1] - 1) + ); + const { line: sl, column: sc } = locConverter.get(ident[0]); + const { line: el, column: ec } = locConverter.get(ident[1]); + const dep = new CssLocalIdentifierDependency(name, [ + ident[0], + ident[1] + ]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return ident[1]; + } else if (name === "@property" && isLocalMode()) { + const ident = walkCssTokens.eatIdentSequence(input, end); + if (!ident) return end; + let name = input.slice(ident[0], ident[1]); + if (!name.startsWith("--") || name.length < 3) return end; + name = unescapeIdentifier(name.slice(2)); + declaredCssVariables.add(name); + const { line: sl, column: sc } = locConverter.get(ident[0]); + const { line: el, column: ec } = locConverter.get(ident[1]); + const dep = new CssLocalIdentifierDependency( + name, + [ident[0], ident[1]], + "--" + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return ident[1]; + } else if (name === "@scope") { + isNextRulePrelude = true; + return end; + } + + isNextRulePrelude = false; + } + } + } + + return end; + }, + semicolon: (input, start, end) => { + if (isModules && scope === CSS_MODE_IN_BLOCK) { + if (isLocalMode()) { + processDeclarationValueDone(input); + inAnimationProperty = false; + } + + isNextRulePrelude = isNextNestedSyntax(input, end); + } + return end; + }, + identifier: (input, start, end) => { + if (isModules) { + if (icssDefinitions.has(input.slice(start, end))) { + const name = input.slice(start, end); + let { path, value } = icssDefinitions.get(name); + + if (path) { + if (icssDefinitions.has(path)) { + const definition = icssDefinitions.get(path); + + path = definition.value.slice(1, -1); + } + + const dep = new CssIcssImportDependency(path, value, [ + start, + end - 1 + ]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end - 1); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } else { + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + const dep = new CssIcssSymbolDependency(name, value, [ + start, + end + ]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + return end; + } + + switch (scope) { + case CSS_MODE_IN_BLOCK: { + if (isLocalMode()) { + // Handle only top level values and not inside functions + if (inAnimationProperty && balanced.length === 0) { + lastIdentifier = [start, end, true]; + } else { + return processLocalDeclaration(input, start, end); + } + } + break; + } + } + } + + return end; + }, + delim: (input, start, end) => { + if (isNextRulePrelude && isLocalMode()) { + const ident = walkCssTokens.skipCommentsAndEatIdentSequence( + input, + end + ); + if (!ident) return end; + const name = unescapeIdentifier(input.slice(ident[0], ident[1])); + const dep = new CssLocalIdentifierDependency(name, [ + ident[0], + ident[1] + ]); + const { line: sl, column: sc } = locConverter.get(ident[0]); + const { line: el, column: ec } = locConverter.get(ident[1]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return ident[1]; + } + + return end; + }, + hash: (input, start, end, isID) => { + if (isNextRulePrelude && isLocalMode() && isID) { + const valueStart = start + 1; + const name = unescapeIdentifier(input.slice(valueStart, end)); + const dep = new CssLocalIdentifierDependency(name, [valueStart, end]); + const { line: sl, column: sc } = locConverter.get(start); + const { line: el, column: ec } = locConverter.get(end); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + + return end; + }, + colon: (input, start, end) => { + if (isModules) { + const ident = walkCssTokens.skipCommentsAndEatIdentSequence( + input, + end + ); + if (!ident) return end; + const name = input.slice(ident[0], ident[1]).toLowerCase(); + + switch (scope) { + case CSS_MODE_TOP_LEVEL: { + if (name === "import") { + const pos = parseImportOrExport(0, input, ident[1]); + const dep = new ConstDependency("", [start, pos]); + module.addPresentationalDependency(dep); + return pos; + } else if (name === "export") { + const pos = parseImportOrExport(1, input, ident[1]); + const dep = new ConstDependency("", [start, pos]); + module.addPresentationalDependency(dep); + return pos; + } + } + // falls through + default: { + if (isNextRulePrelude) { + const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS; + + if (isFn && name === "local") { + const end = ident[1] + 1; + modeData = "local"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + balanced.push([":local", start, end]); + return end; + } else if (name === "local") { + modeData = "local"; + // Eat extra whitespace + end = walkCssTokens.eatWhitespace(input, ident[1]); + + if (ident[1] === end) { + this._emitWarning( + state, + `Missing whitespace after ':local' in '${input.slice( + start, + eatUntilLeftCurly(input, end) + 1 + )}'`, + locConverter, + start, + end + ); + } + + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } else if (isFn && name === "global") { + const end = ident[1] + 1; + modeData = "global"; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + balanced.push([":global", start, end]); + return end; + } else if (name === "global") { + modeData = "global"; + // Eat extra whitespace + end = walkCssTokens.eatWhitespace(input, ident[1]); + + if (ident[1] === end) { + this._emitWarning( + state, + `Missing whitespace after ':global' in '${input.slice( + start, + eatUntilLeftCurly(input, end) + 1 + )}'`, + locConverter, + start, + end + ); + } + + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + return end; + } + } + } + } + } + + lastTokenEndForComments = end; + + return end; + }, + function: (input, start, end) => { + const name = input + .slice(start, end - 1) + .replace(/\\/g, "") + .toLowerCase(); + + balanced.push([name, start, end]); + + switch (name) { + case "src": + case "url": { + if (!this.url) { + return end; + } + + const string = walkCssTokens.eatString(input, end); + if (!string) return end; + const { options, errors: commentErrors } = this.parseCommentOptions( + [lastTokenEndForComments, end] + ); + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + if (options && options.webpackIgnore !== undefined) { + if (typeof options.webpackIgnore !== "boolean") { + const { line: sl, column: sc } = locConverter.get(string[0]); + const { line: el, column: ec } = locConverter.get(string[1]); + + state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, + { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + } + ) + ); + } else if (options.webpackIgnore) { + return end; + } + } + const value = normalizeUrl( + input.slice(string[0] + 1, string[1] - 1), + true + ); + // Ignore `url()`, `url('')` and `url("")`, they are valid by spec + if (value.length === 0) return end; + const isUrl = name === "url" || name === "src"; + const dep = new CssUrlDependency( + value, + [string[0], string[1]], + isUrl ? "string" : "url" + ); + const { line: sl, column: sc } = locConverter.get(string[0]); + const { line: el, column: ec } = locConverter.get(string[1]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + return string[1]; + } + default: { + if (this.url && IMAGE_SET_FUNCTION.test(name)) { + lastTokenEndForComments = end; + const values = walkCssTokens.eatImageSetStrings(input, end, { + comment + }); + if (values.length === 0) return end; + for (const [index, string] of values.entries()) { + const value = normalizeUrl( + input.slice(string[0] + 1, string[1] - 1), + true + ); + if (value.length === 0) return end; + const { options, errors: commentErrors } = + this.parseCommentOptions([ + index === 0 ? start : values[index - 1][1], + string[1] + ]); + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + comment.loc + ) + ); + } + } + if (options && options.webpackIgnore !== undefined) { + if (typeof options.webpackIgnore !== "boolean") { + const { line: sl, column: sc } = locConverter.get( + string[0] + ); + const { line: el, column: ec } = locConverter.get( + string[1] + ); + + state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, + { + start: { line: sl, column: sc }, + end: { line: el, column: ec } + } + ) + ); + } else if (options.webpackIgnore) { + continue; + } + } + const dep = new CssUrlDependency( + value, + [string[0], string[1]], + "url" + ); + const { line: sl, column: sc } = locConverter.get(string[0]); + const { line: el, column: ec } = locConverter.get(string[1]); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + module.addCodeGenerationDependency(dep); + } + // Can contain `url()` inside, so let's return end to allow parse them + return end; + } else if (isLocalMode()) { + // Don't rename animation name when we have `var()` function + if (inAnimationProperty && balanced.length === 1) { + lastIdentifier = undefined; + } + + if (name === "var") { + const customIdent = walkCssTokens.eatIdentSequence(input, end); + if (!customIdent) return end; + let name = input.slice(customIdent[0], customIdent[1]); + // A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo. + // The production corresponds to this: + // it’s defined as any (a valid identifier that starts with two dashes), + // except -- itself, which is reserved for future use by CSS. + if (!name.startsWith("--") || name.length < 3) return end; + name = unescapeIdentifier( + input.slice(customIdent[0] + 2, customIdent[1]) + ); + const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments( + input, + customIdent[1] + ); + if ( + input.charCodeAt(afterCustomIdent) === CC_LOWER_F || + input.charCodeAt(afterCustomIdent) === CC_UPPER_F + ) { + const fromWord = walkCssTokens.eatIdentSequence( + input, + afterCustomIdent + ); + if ( + !fromWord || + input.slice(fromWord[0], fromWord[1]).toLowerCase() !== + "from" + ) { + return end; + } + const from = walkCssTokens.eatIdentSequenceOrString( + input, + walkCssTokens.eatWhitespaceAndComments(input, fromWord[1]) + ); + if (!from) { + return end; + } + const path = input.slice(from[0], from[1]); + if (from[2] === true && path === "global") { + const dep = new ConstDependency("", [ + customIdent[1], + from[1] + ]); + module.addPresentationalDependency(dep); + return end; + } else if (from[2] === false) { + const dep = new CssIcssImportDependency( + path.slice(1, -1), + name, + [customIdent[0], from[1] - 1] + ); + const { line: sl, column: sc } = locConverter.get( + customIdent[0] + ); + const { line: el, column: ec } = locConverter.get( + from[1] - 1 + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + } + } else { + const { line: sl, column: sc } = locConverter.get( + customIdent[0] + ); + const { line: el, column: ec } = locConverter.get( + customIdent[1] + ); + const dep = new CssSelfLocalIdentifierDependency( + name, + [customIdent[0], customIdent[1]], + "--", + declaredCssVariables + ); + dep.setLoc(sl, sc, el, ec); + module.addDependency(dep); + return end; + } + } + } + } + } + + return end; + }, + leftParenthesis: (input, start, end) => { + balanced.push(["(", start, end]); + + return end; + }, + rightParenthesis: (input, start, end) => { + const popped = balanced.pop(); + + if ( + isModules && + popped && + (popped[0] === ":local" || popped[0] === ":global") + ) { + modeData = balanced[balanced.length - 1] + ? /** @type {"local" | "global"} */ + (balanced[balanced.length - 1][0]) + : undefined; + const dep = new ConstDependency("", [start, end]); + module.addPresentationalDependency(dep); + } + + return end; + }, + comma: (input, start, end) => { + if (isModules) { + // Reset stack for `:global .class :local .class-other` selector after + modeData = undefined; + + if (scope === CSS_MODE_IN_BLOCK && isLocalMode()) { + processDeclarationValueDone(input); + } + } + + lastTokenEndForComments = start; + + return end; + } + }); + + /** @type {BuildInfo} */ + (module.buildInfo).strict = true; + /** @type {BuildMeta} */ + (module.buildMeta).exportsType = this.namedExports + ? "namespace" + : "default"; + + if (!this.namedExports) { + /** @type {BuildMeta} */ + (module.buildMeta).defaultObject = "redirect"; + } + + module.addDependency(new StaticExportsDependency([], true)); + return state; + } + + /** + * @param {Range} range range + * @returns {Comment[]} comments in the range + */ + getComments(range) { + if (!this.comments) return []; + const [rangeStart, rangeEnd] = range; + /** + * @param {Comment} comment comment + * @param {number} needle needle + * @returns {number} compared + */ + const compare = (comment, needle) => + /** @type {Range} */ (comment.range)[0] - needle; + const comments = /** @type {Comment[]} */ (this.comments); + let idx = binarySearchBounds.ge(comments, rangeStart, compare); + /** @type {Comment[]} */ + const commentsInRange = []; + while ( + comments[idx] && + /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd + ) { + commentsInRange.push(comments[idx]); + idx++; + } + + return commentsInRange; + } + + /** + * @param {Range} range range of the comment + * @returns {{ options: Record | null, errors: (Error & { comment: Comment })[] | null }} result + */ + parseCommentOptions(range) { + const comments = this.getComments(range); + if (comments.length === 0) { + return EMPTY_COMMENT_OPTIONS; + } + /** @type {Record } */ + const options = {}; + /** @type {(Error & { comment: Comment })[]} */ + const errors = []; + for (const comment of comments) { + const { value } = comment; + if (value && webpackCommentRegExp.test(value)) { + // try compile only if webpack options comment is present + try { + for (let [key, val] of Object.entries( + vm.runInContext( + `(function(){return {${value}};})()`, + this.magicCommentContext + ) + )) { + if (typeof val === "object" && val !== null) { + val = + val.constructor.name === "RegExp" + ? new RegExp(val) + : JSON.parse(JSON.stringify(val)); + } + options[key] = val; + } + } catch (err) { + const newErr = new Error(String(/** @type {Error} */ (err).message)); + newErr.stack = String(/** @type {Error} */ (err).stack); + Object.assign(newErr, { comment }); + errors.push(/** @type (Error & { comment: Comment }) */ (newErr)); + } + } + } + return { options, errors }; + } +} + +module.exports = CssParser; +module.exports.escapeIdentifier = escapeIdentifier; +module.exports.unescapeIdentifier = unescapeIdentifier; diff --git a/webpack-lib/lib/css/walkCssTokens.js b/webpack-lib/lib/css/walkCssTokens.js new file mode 100644 index 00000000000..abef4f01e71 --- /dev/null +++ b/webpack-lib/lib/css/walkCssTokens.js @@ -0,0 +1,1627 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @typedef {object} CssTokenCallbacks + * @property {(function(string, number, number, number, number): number)=} url + * @property {(function(string, number, number): number)=} comment + * @property {(function(string, number, number): number)=} string + * @property {(function(string, number, number): number)=} leftParenthesis + * @property {(function(string, number, number): number)=} rightParenthesis + * @property {(function(string, number, number): number)=} function + * @property {(function(string, number, number): number)=} colon + * @property {(function(string, number, number): number)=} atKeyword + * @property {(function(string, number, number): number)=} delim + * @property {(function(string, number, number): number)=} identifier + * @property {(function(string, number, number, boolean): number)=} hash + * @property {(function(string, number, number): number)=} leftCurlyBracket + * @property {(function(string, number, number): number)=} rightCurlyBracket + * @property {(function(string, number, number): number)=} semicolon + * @property {(function(string, number, number): number)=} comma + * @property {(function(): boolean)=} needTerminate + */ + +/** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */ + +// spec: https://drafts.csswg.org/css-syntax/ + +const CC_LINE_FEED = "\n".charCodeAt(0); +const CC_CARRIAGE_RETURN = "\r".charCodeAt(0); +const CC_FORM_FEED = "\f".charCodeAt(0); + +const CC_TAB = "\t".charCodeAt(0); +const CC_SPACE = " ".charCodeAt(0); + +const CC_SOLIDUS = "/".charCodeAt(0); +const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0); +const CC_ASTERISK = "*".charCodeAt(0); + +const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); +const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); +const CC_LEFT_CURLY = "{".charCodeAt(0); +const CC_RIGHT_CURLY = "}".charCodeAt(0); +const CC_LEFT_SQUARE = "[".charCodeAt(0); +const CC_RIGHT_SQUARE = "]".charCodeAt(0); + +const CC_QUOTATION_MARK = '"'.charCodeAt(0); +const CC_APOSTROPHE = "'".charCodeAt(0); + +const CC_FULL_STOP = ".".charCodeAt(0); +const CC_COLON = ":".charCodeAt(0); +const CC_SEMICOLON = ";".charCodeAt(0); +const CC_COMMA = ",".charCodeAt(0); +const CC_PERCENTAGE = "%".charCodeAt(0); +const CC_AT_SIGN = "@".charCodeAt(0); + +const CC_LOW_LINE = "_".charCodeAt(0); +const CC_LOWER_A = "a".charCodeAt(0); +const CC_LOWER_F = "f".charCodeAt(0); +const CC_LOWER_E = "e".charCodeAt(0); +const CC_LOWER_U = "u".charCodeAt(0); +const CC_LOWER_Z = "z".charCodeAt(0); +const CC_UPPER_A = "A".charCodeAt(0); +const CC_UPPER_F = "F".charCodeAt(0); +const CC_UPPER_E = "E".charCodeAt(0); +const CC_UPPER_U = "E".charCodeAt(0); +const CC_UPPER_Z = "Z".charCodeAt(0); +const CC_0 = "0".charCodeAt(0); +const CC_9 = "9".charCodeAt(0); + +const CC_NUMBER_SIGN = "#".charCodeAt(0); +const CC_PLUS_SIGN = "+".charCodeAt(0); +const CC_HYPHEN_MINUS = "-".charCodeAt(0); + +const CC_LESS_THAN_SIGN = "<".charCodeAt(0); +const CC_GREATER_THAN_SIGN = ">".charCodeAt(0); + +/** @type {CharHandler} */ +const consumeSpace = (input, pos, _callbacks) => { + // Consume as much whitespace as possible. + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + + // Return a . + return pos; +}; + +// U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, +// as they are converted to U+000A LINE FEED during preprocessing. +// +// Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE FEED (LF) in input by a single U+000A LINE FEED (LF) code point. + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a newline + */ +const _isNewline = cc => + cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; + +/** + * @param {number} cc char code + * @param {string} input input + * @param {number} pos position + * @returns {number} position + */ +const consumeExtraNewline = (cc, input, pos) => { + if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) { + pos++; + } + + return pos; +}; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE) + */ +const _isSpace = cc => cc === CC_TAB || cc === CC_SPACE; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a whitespace + */ +const _isWhiteSpace = cc => _isNewline(cc) || _isSpace(cc); + +/** + * ident-start code point + * + * A letter, a non-ASCII code point, or U+005F LOW LINE (_). + * @param {number} cc char code + * @returns {boolean} true, if cc is a start code point of an identifier + */ +const isIdentStartCodePoint = cc => + (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || + (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) || + cc === CC_LOW_LINE || + cc >= 0x80; + +/** @type {CharHandler} */ +const consumeDelimToken = (input, pos, _callbacks) => + // Return a with its value set to the current input code point. + pos; + +/** @type {CharHandler} */ +const consumeComments = (input, pos, callbacks) => { + // This section describes how to consume comments from a stream of code points. It returns nothing. + // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*), + // consume them and all following code points up to and including the first U+002A ASTERISK (*) + // followed by a U+002F SOLIDUS (/), or up to an EOF code point. + // Return to the start of this step. + while ( + input.charCodeAt(pos) === CC_SOLIDUS && + input.charCodeAt(pos + 1) === CC_ASTERISK + ) { + const start = pos; + pos += 2; + + for (;;) { + if (pos === input.length) { + // If the preceding paragraph ended by consuming an EOF code point, this is a parse error. + return pos; + } + + if ( + input.charCodeAt(pos) === CC_ASTERISK && + input.charCodeAt(pos + 1) === CC_SOLIDUS + ) { + pos += 2; + + if (callbacks.comment) { + pos = callbacks.comment(input, start, pos); + } + + break; + } + + pos++; + } + } + + return pos; +}; + +/** + * @param {number} cc char code + * @returns {boolean} true, if cc is a hex digit + */ +const _isHexDigit = cc => + _isDigit(cc) || + (cc >= CC_UPPER_A && cc <= CC_UPPER_F) || + (cc >= CC_LOWER_A && cc <= CC_LOWER_F); + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position + */ +const _consumeAnEscapedCodePoint = (input, pos) => { + // This section describes how to consume an escaped code point. + // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and that the next input code point has already been verified to be part of a valid escape. + // It will return a code point. + + // Consume the next input code point. + const cc = input.charCodeAt(pos); + pos++; + + // EOF + // This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (�). + if (pos === input.length) { + return pos; + } + + // hex digit + // Consume as many hex digits as possible, but no more than 5. + // Note that this means 1-6 hex digits have been consumed in total. + // If the next input code point is whitespace, consume it as well. + // Interpret the hex digits as a hexadecimal number. + // If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point, return U+FFFD REPLACEMENT CHARACTER (�). + // Otherwise, return the code point with that value. + if (_isHexDigit(cc)) { + for (let i = 0; i < 5; i++) { + if (_isHexDigit(input.charCodeAt(pos))) { + pos++; + } + } + + const cc = input.charCodeAt(pos); + + if (_isWhiteSpace(cc)) { + pos++; + pos = consumeExtraNewline(cc, input, pos); + } + + return pos; + } + + // anything else + // Return the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeAStringToken = (input, pos, callbacks) => { + // This section describes how to consume a string token from a stream of code points. + // It returns either a or . + // + // This algorithm may be called with an ending code point, which denotes the code point that ends the string. + // If an ending code point is not specified, the current input code point is used. + const start = pos - 1; + const endingCodePoint = input.charCodeAt(pos - 1); + + // Initially create a with its value set to the empty string. + + // Repeatedly consume the next input code point from the stream: + for (;;) { + // EOF + // This is a parse error. Return the . + if (pos === input.length) { + if (callbacks.string !== undefined) { + return callbacks.string(input, start, pos); + } + + return pos; + } + + const cc = input.charCodeAt(pos); + pos++; + + // ending code point + // Return the . + if (cc === endingCodePoint) { + if (callbacks.string !== undefined) { + return callbacks.string(input, start, pos); + } + + return pos; + } + // newline + // This is a parse error. + // Reconsume the current input code point, create a , and return it. + else if (_isNewline(cc)) { + pos--; + // bad string + return pos; + } + // U+005C REVERSE SOLIDUS (\) + else if (cc === CC_REVERSE_SOLIDUS) { + // If the next input code point is EOF, do nothing. + if (pos === input.length) { + return pos; + } + // Otherwise, if the next input code point is a newline, consume it. + else if (_isNewline(input.charCodeAt(pos))) { + const cc = input.charCodeAt(pos); + pos++; + pos = consumeExtraNewline(cc, input, pos); + } + // Otherwise, (the stream starts with a valid escape) consume an escaped code point and append the returned code point to the ’s value. + else if (_ifTwoCodePointsAreValidEscape(input, pos)) { + pos = _consumeAnEscapedCodePoint(input, pos); + } + } + // anything else + // Append the current input code point to the ’s value. + else { + // Append + } + } +}; + +/** + * @param {number} cc char code + * @param {number} q char code + * @returns {boolean} is non-ASCII code point + */ +const isNonASCIICodePoint = (cc, q) => + // Simplify + cc > 0x80; +/** + * @param {number} cc char code + * @returns {boolean} is letter + */ +const isLetter = cc => + (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || + (cc >= CC_UPPER_A && cc <= CC_UPPER_Z); + +/** + * @param {number} cc char code + * @param {number} q char code + * @returns {boolean} is identifier start code + */ +const _isIdentStartCodePoint = (cc, q) => + isLetter(cc) || isNonASCIICodePoint(cc, q) || cc === CC_LOW_LINE; + +/** + * @param {number} cc char code + * @param {number} q char code + * @returns {boolean} is identifier code + */ +const _isIdentCodePoint = (cc, q) => + _isIdentStartCodePoint(cc, q) || _isDigit(cc) || cc === CC_HYPHEN_MINUS; +/** + * @param {number} cc char code + * @returns {boolean} is digit + */ +const _isDigit = cc => cc >= CC_0 && cc <= CC_9; + +/** + * @param {string} input input + * @param {number} pos position + * @param {number=} f first code point + * @param {number=} s second code point + * @returns {boolean} true if two code points are a valid escape + */ +const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => { + // This section describes how to check if two code points are a valid escape. + // The algorithm described here can be called explicitly with two code points, or can be called with the input stream itself. + // In the latter case, the two code points in question are the current input code point and the next input code point, in that order. + + // Note: This algorithm will not consume any additional code point. + const first = f || input.charCodeAt(pos - 1); + const second = s || input.charCodeAt(pos); + + // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. + if (first !== CC_REVERSE_SOLIDUS) return false; + // Otherwise, if the second code point is a newline, return false. + if (_isNewline(second)) return false; + // Otherwise, return true. + return true; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {number=} f first + * @param {number=} s second + * @param {number=} t third + * @returns {boolean} true, if input at pos starts an identifier + */ +const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => { + // This section describes how to check if three code points would start an ident sequence. + // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself. + // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order. + + // Note: This algorithm will not consume any additional code points. + + const first = f || input.charCodeAt(pos - 1); + const second = s || input.charCodeAt(pos); + const third = t || input.charCodeAt(pos + 1); + + // Look at the first code point: + + // U+002D HYPHEN-MINUS + if (first === CC_HYPHEN_MINUS) { + // If the second code point is an ident-start code point or a U+002D HYPHEN-MINUS + // or a U+002D HYPHEN-MINUS, or the second and third code points are a valid escape, return true. + if ( + _isIdentStartCodePoint(second, pos) || + second === CC_HYPHEN_MINUS || + _ifTwoCodePointsAreValidEscape(input, pos, second, third) + ) { + return true; + } + return false; + } + // ident-start code point + else if (_isIdentStartCodePoint(first, pos - 1)) { + return true; + } + // U+005C REVERSE SOLIDUS (\) + // If the first and second code points are a valid escape, return true. Otherwise, return false. + else if (first === CC_REVERSE_SOLIDUS) { + if (_ifTwoCodePointsAreValidEscape(input, pos, first, second)) { + return true; + } + + return false; + } + // anything else + // Return false. + return false; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {number=} f first + * @param {number=} s second + * @param {number=} t third + * @returns {boolean} true, if input at pos starts an identifier + */ +const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => { + // This section describes how to check if three code points would start a number. + // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself. + // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order. + + // Note: This algorithm will not consume any additional code points. + + const first = f || input.charCodeAt(pos - 1); + const second = s || input.charCodeAt(pos); + const third = t || input.charCodeAt(pos); + + // Look at the first code point: + + // U+002B PLUS SIGN (+) + // U+002D HYPHEN-MINUS (-) + // + // If the second code point is a digit, return true. + // Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code point is a digit, return true. + // Otherwise, return false. + if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) { + if (_isDigit(second)) { + return true; + } else if (second === CC_FULL_STOP && _isDigit(third)) { + return true; + } + + return false; + } + // U+002E FULL STOP (.) + // If the second code point is a digit, return true. Otherwise, return false. + else if (first === CC_FULL_STOP) { + if (_isDigit(second)) { + return true; + } + + return false; + } + // digit + // Return true. + else if (_isDigit(first)) { + return true; + } + + // anything else + // Return false. + return false; +}; + +/** @type {CharHandler} */ +const consumeNumberSign = (input, pos, callbacks) => { + // If the next input code point is an ident code point or the next two input code points are a valid escape, then: + // - Create a . + // - If the next 3 input code points would start an ident sequence, set the ’s type flag to "id". + // - Consume an ident sequence, and set the ’s value to the returned string. + // - Return the . + const start = pos - 1; + const first = input.charCodeAt(pos); + const second = input.charCodeAt(pos + 1); + + if ( + _isIdentCodePoint(first, pos - 1) || + _ifTwoCodePointsAreValidEscape(input, pos, first, second) + ) { + const third = input.charCodeAt(pos + 2); + let isId = false; + + if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + first, + second, + third + ) + ) { + isId = true; + } + + pos = _consumeAnIdentSequence(input, pos, callbacks); + + if (callbacks.hash !== undefined) { + return callbacks.hash(input, start, pos, isId); + } + + return pos; + } + + // Otherwise, return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeHyphenMinus = (input, pos, callbacks) => { + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (_ifThreeCodePointsWouldStartANumber(input, pos)) { + pos--; + return consumeANumericToken(input, pos, callbacks); + } + // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a . + else if ( + input.charCodeAt(pos) === CC_HYPHEN_MINUS && + input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN + ) { + return pos + 2; + } + // Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it. + else if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) { + pos--; + return consumeAnIdentLikeToken(input, pos, callbacks); + } + + // Otherwise, return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeFullStop = (input, pos, callbacks) => { + const start = pos - 1; + + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (_ifThreeCodePointsWouldStartANumber(input, pos)) { + pos--; + return consumeANumericToken(input, pos, callbacks); + } + + // Otherwise, return a with its value set to the current input code point. + if (callbacks.delim !== undefined) { + return callbacks.delim(input, start, pos); + } + + return pos; +}; + +/** @type {CharHandler} */ +const consumePlusSign = (input, pos, callbacks) => { + // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. + if (_ifThreeCodePointsWouldStartANumber(input, pos)) { + pos--; + return consumeANumericToken(input, pos, callbacks); + } + + // Otherwise, return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const _consumeANumber = (input, pos) => { + // This section describes how to consume a number from a stream of code points. + // It returns a numeric value, and a type which is either "integer" or "number". + + // Execute the following steps in order: + // Initially set type to "integer". Let repr be the empty string. + + // If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume it and append it to repr. + if ( + input.charCodeAt(pos) === CC_HYPHEN_MINUS || + input.charCodeAt(pos) === CC_PLUS_SIGN + ) { + pos++; + } + + // While the next input code point is a digit, consume it and append it to repr. + while (_isDigit(input.charCodeAt(pos))) { + pos++; + } + + // If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: + // 1. Consume the next input code point and append it to number part. + // 2. While the next input code point is a digit, consume it and append it to number part. + // 3. Set type to "number". + if ( + input.charCodeAt(pos) === CC_FULL_STOP && + _isDigit(input.charCodeAt(pos + 1)) + ) { + pos++; + + while (_isDigit(input.charCodeAt(pos))) { + pos++; + } + } + + // If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+), followed by a digit, then: + // 1. Consume the next input code point. + // 2. If the next input code point is "+" or "-", consume it and append it to exponent part. + // 3. While the next input code point is a digit, consume it and append it to exponent part. + // 4. Set type to "number". + if ( + (input.charCodeAt(pos) === CC_LOWER_E || + input.charCodeAt(pos) === CC_UPPER_E) && + (((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS || + input.charCodeAt(pos + 1) === CC_PLUS_SIGN) && + _isDigit(input.charCodeAt(pos + 2))) || + _isDigit(input.charCodeAt(pos + 1))) + ) { + pos++; + + if ( + input.charCodeAt(pos) === CC_PLUS_SIGN || + input.charCodeAt(pos) === CC_HYPHEN_MINUS + ) { + pos++; + } + + while (_isDigit(input.charCodeAt(pos))) { + pos++; + } + } + + // Let value be the result of interpreting number part as a base-10 number. + + // If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the power of the result, multiply it by value, and set value to that result. + + // Return value and type. + return pos; +}; + +/** @type {CharHandler} */ +const consumeANumericToken = (input, pos, callbacks) => { + // This section describes how to consume a numeric token from a stream of code points. + // It returns either a , , or . + + // Consume a number and let number be the result. + pos = _consumeANumber(input, pos, callbacks); + + // If the next 3 input code points would start an ident sequence, then: + // + // - Create a with the same value and type flag as number, and a unit set initially to the empty string. + // - Consume an ident sequence. Set the ’s unit to the returned value. + // - Return the . + + const first = input.charCodeAt(pos); + const second = input.charCodeAt(pos + 1); + const third = input.charCodeAt(pos + 2); + + if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + first, + second, + third + ) + ) { + return _consumeAnIdentSequence(input, pos, callbacks); + } + // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. + // Create a with the same value as number, and return it. + else if (first === CC_PERCENTAGE) { + return pos + 1; + } + + // Otherwise, create a with the same value and type flag as number, and return it. + return pos; +}; + +/** @type {CharHandler} */ +const consumeColon = (input, pos, callbacks) => { + // Return a . + if (callbacks.colon !== undefined) { + return callbacks.colon(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeLeftParenthesis = (input, pos, callbacks) => { + // Return a <(-token>. + if (callbacks.leftParenthesis !== undefined) { + return callbacks.leftParenthesis(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeRightParenthesis = (input, pos, callbacks) => { + // Return a <)-token>. + if (callbacks.rightParenthesis !== undefined) { + return callbacks.rightParenthesis(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeLeftSquareBracket = (input, pos, callbacks) => + // Return a <]-token>. + pos; + +/** @type {CharHandler} */ +const consumeRightSquareBracket = (input, pos, callbacks) => + // Return a <]-token>. + pos; + +/** @type {CharHandler} */ +const consumeLeftCurlyBracket = (input, pos, callbacks) => { + // Return a <{-token>. + if (callbacks.leftCurlyBracket !== undefined) { + return callbacks.leftCurlyBracket(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeRightCurlyBracket = (input, pos, callbacks) => { + // Return a <}-token>. + if (callbacks.rightCurlyBracket !== undefined) { + return callbacks.rightCurlyBracket(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeSemicolon = (input, pos, callbacks) => { + // Return a . + if (callbacks.semicolon !== undefined) { + return callbacks.semicolon(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const consumeComma = (input, pos, callbacks) => { + // Return a . + if (callbacks.comma !== undefined) { + return callbacks.comma(input, pos - 1, pos); + } + return pos; +}; + +/** @type {CharHandler} */ +const _consumeAnIdentSequence = (input, pos) => { + // This section describes how to consume an ident sequence from a stream of code points. + // It returns a string containing the largest name that can be formed from adjacent code points in the stream, starting from the first. + + // Note: This algorithm does not do the verification of the first few code points that are necessary to ensure the returned code points would constitute an . + // If that is the intended use, ensure that the stream starts with an ident sequence before calling this algorithm. + + // Let result initially be an empty string. + + // Repeatedly consume the next input code point from the stream: + for (;;) { + const cc = input.charCodeAt(pos); + pos++; + + // ident code point + // Append the code point to result. + if (_isIdentCodePoint(cc, pos - 1)) { + // Nothing + } + // the stream starts with a valid escape + // Consume an escaped code point. Append the returned code point to result. + else if (_ifTwoCodePointsAreValidEscape(input, pos)) { + pos = _consumeAnEscapedCodePoint(input, pos); + } + // anything else + // Reconsume the current input code point. Return result. + else { + return pos - 1; + } + } +}; + +/** + * @param {number} cc char code + * @returns {boolean} true, when cc is the non-printable code point, otherwise false + */ +const _isNonPrintableCodePoint = cc => + (cc >= 0x00 && cc <= 0x08) || + cc === 0x0b || + (cc >= 0x0e && cc <= 0x1f) || + cc === 0x7f; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position + */ +const consumeTheRemnantsOfABadUrl = (input, pos) => { + // This section describes how to consume the remnants of a bad url from a stream of code points, + // "cleaning up" after the tokenizer realizes that it’s in the middle of a rather than a . + // It returns nothing; its sole use is to consume enough of the input stream to reach a recovery point where normal tokenizing can resume. + + // Repeatedly consume the next input code point from the stream: + for (;;) { + // EOF + // Return. + if (pos === input.length) { + return pos; + } + + const cc = input.charCodeAt(pos); + pos++; + + // U+0029 RIGHT PARENTHESIS ()) + // Return. + if (cc === CC_RIGHT_PARENTHESIS) { + return pos; + } + // the input stream starts with a valid escape + // Consume an escaped code point. + // This allows an escaped right parenthesis ("\)") to be encountered without ending the . + // This is otherwise identical to the "anything else" clause. + else if (_ifTwoCodePointsAreValidEscape(input, pos)) { + pos = _consumeAnEscapedCodePoint(input, pos); + } + // anything else + // Do nothing. + else { + // Do nothing. + } + } +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {number} fnStart start + * @param {CssTokenCallbacks} callbacks callbacks + * @returns {pos} pos + */ +const consumeAUrlToken = (input, pos, fnStart, callbacks) => { + // This section describes how to consume a url token from a stream of code points. + // It returns either a or a . + + // Note: This algorithm assumes that the initial "url(" has already been consumed. + // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo). + // A quoted value, like url("foo"), is parsed as a . + // Consume an ident-like token automatically handles this distinction; this algorithm shouldn’t be called directly otherwise. + + // Initially create a with its value set to the empty string. + + // Consume as much whitespace as possible. + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + + const contentStart = pos; + + // Repeatedly consume the next input code point from the stream: + for (;;) { + // EOF + // This is a parse error. Return the . + if (pos === input.length) { + if (callbacks.url !== undefined) { + return callbacks.url(input, fnStart, pos, contentStart, pos - 1); + } + + return pos; + } + + const cc = input.charCodeAt(pos); + pos++; + + // U+0029 RIGHT PARENTHESIS ()) + // Return the . + if (cc === CC_RIGHT_PARENTHESIS) { + if (callbacks.url !== undefined) { + return callbacks.url(input, fnStart, pos, contentStart, pos - 1); + } + + return pos; + } + // whitespace + // Consume as much whitespace as possible. + // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, consume it and return the + // (if EOF was encountered, this is a parse error); otherwise, consume the remnants of a bad url, create a , and return it. + else if (_isWhiteSpace(cc)) { + const end = pos - 1; + + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + + if (pos === input.length) { + if (callbacks.url !== undefined) { + return callbacks.url(input, fnStart, pos, contentStart, end); + } + + return pos; + } + + if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) { + pos++; + + if (callbacks.url !== undefined) { + return callbacks.url(input, fnStart, pos, contentStart, end); + } + + return pos; + } + + // Don't handle bad urls + return consumeTheRemnantsOfABadUrl(input, pos); + } + // U+0022 QUOTATION MARK (") + // U+0027 APOSTROPHE (') + // U+0028 LEFT PARENTHESIS (() + // non-printable code point + // This is a parse error. Consume the remnants of a bad url, create a , and return it. + else if ( + cc === CC_QUOTATION_MARK || + cc === CC_APOSTROPHE || + cc === CC_LEFT_PARENTHESIS || + _isNonPrintableCodePoint(cc) + ) { + // Don't handle bad urls + return consumeTheRemnantsOfABadUrl(input, pos); + } + // // U+005C REVERSE SOLIDUS (\) + // // If the stream starts with a valid escape, consume an escaped code point and append the returned code point to the ’s value. + // // Otherwise, this is a parse error. Consume the remnants of a bad url, create a , and return it. + else if (cc === CC_REVERSE_SOLIDUS) { + if (_ifTwoCodePointsAreValidEscape(input, pos)) { + pos = _consumeAnEscapedCodePoint(input, pos); + } else { + // Don't handle bad urls + return consumeTheRemnantsOfABadUrl(input, pos); + } + } + // anything else + // Append the current input code point to the ’s value. + else { + // Nothing + } + } +}; + +/** @type {CharHandler} */ +const consumeAnIdentLikeToken = (input, pos, callbacks) => { + const start = pos; + // This section describes how to consume an ident-like token from a stream of code points. + // It returns an , , , or . + pos = _consumeAnIdentSequence(input, pos, callbacks); + + // If string’s value is an ASCII case-insensitive match for "url", and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + // While the next two input code points are whitespace, consume the next input code point. + // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), then create a with its value set to string and return it. + // Otherwise, consume a url token, and return it. + if ( + input.slice(start, pos).toLowerCase() === "url" && + input.charCodeAt(pos) === CC_LEFT_PARENTHESIS + ) { + pos++; + const end = pos; + + while ( + _isWhiteSpace(input.charCodeAt(pos)) && + _isWhiteSpace(input.charCodeAt(pos + 1)) + ) { + pos++; + } + + if ( + input.charCodeAt(pos) === CC_QUOTATION_MARK || + input.charCodeAt(pos) === CC_APOSTROPHE || + (_isWhiteSpace(input.charCodeAt(pos)) && + (input.charCodeAt(pos + 1) === CC_QUOTATION_MARK || + input.charCodeAt(pos + 1) === CC_APOSTROPHE)) + ) { + if (callbacks.function !== undefined) { + return callbacks.function(input, start, end); + } + + return pos; + } + + return consumeAUrlToken(input, pos, start, callbacks); + } + + // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. + // Create a with its value set to string and return it. + if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) { + pos++; + + if (callbacks.function !== undefined) { + return callbacks.function(input, start, pos); + } + + return pos; + } + + // Otherwise, create an with its value set to string and return it. + if (callbacks.identifier !== undefined) { + return callbacks.identifier(input, start, pos); + } + + return pos; +}; + +/** @type {CharHandler} */ +const consumeLessThan = (input, pos, _callbacks) => { + // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a . + if (input.slice(pos, pos + 3) === "!--") { + return pos + 3; + } + + // Otherwise, return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeCommercialAt = (input, pos, callbacks) => { + const start = pos - 1; + + // If the next 3 input code points would start an ident sequence, consume an ident sequence, create an with its value set to the returned value, and return it. + if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + input.charCodeAt(pos), + input.charCodeAt(pos + 1), + input.charCodeAt(pos + 2) + ) + ) { + pos = _consumeAnIdentSequence(input, pos, callbacks); + + if (callbacks.atKeyword !== undefined) { + pos = callbacks.atKeyword(input, start, pos); + } + + return pos; + } + + // Otherwise, return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeReverseSolidus = (input, pos, callbacks) => { + // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it. + if (_ifTwoCodePointsAreValidEscape(input, pos)) { + pos--; + return consumeAnIdentLikeToken(input, pos, callbacks); + } + + // Otherwise, this is a parse error. Return a with its value set to the current input code point. + return pos; +}; + +/** @type {CharHandler} */ +const consumeAToken = (input, pos, callbacks) => { + const cc = input.charCodeAt(pos - 1); + + // https://drafts.csswg.org/css-syntax/#consume-token + switch (cc) { + // whitespace + case CC_LINE_FEED: + case CC_CARRIAGE_RETURN: + case CC_FORM_FEED: + case CC_TAB: + case CC_SPACE: + return consumeSpace(input, pos, callbacks); + // U+0022 QUOTATION MARK (") + case CC_QUOTATION_MARK: + return consumeAStringToken(input, pos, callbacks); + // U+0023 NUMBER SIGN (#) + case CC_NUMBER_SIGN: + return consumeNumberSign(input, pos, callbacks); + // U+0027 APOSTROPHE (') + case CC_APOSTROPHE: + return consumeAStringToken(input, pos, callbacks); + // U+0028 LEFT PARENTHESIS (() + case CC_LEFT_PARENTHESIS: + return consumeLeftParenthesis(input, pos, callbacks); + // U+0029 RIGHT PARENTHESIS ()) + case CC_RIGHT_PARENTHESIS: + return consumeRightParenthesis(input, pos, callbacks); + // U+002B PLUS SIGN (+) + case CC_PLUS_SIGN: + return consumePlusSign(input, pos, callbacks); + // U+002C COMMA (,) + case CC_COMMA: + return consumeComma(input, pos, callbacks); + // U+002D HYPHEN-MINUS (-) + case CC_HYPHEN_MINUS: + return consumeHyphenMinus(input, pos, callbacks); + // U+002E FULL STOP (.) + case CC_FULL_STOP: + return consumeFullStop(input, pos, callbacks); + // U+003A COLON (:) + case CC_COLON: + return consumeColon(input, pos, callbacks); + // U+003B SEMICOLON (;) + case CC_SEMICOLON: + return consumeSemicolon(input, pos, callbacks); + // U+003C LESS-THAN SIGN (<) + case CC_LESS_THAN_SIGN: + return consumeLessThan(input, pos, callbacks); + // U+0040 COMMERCIAL AT (@) + case CC_AT_SIGN: + return consumeCommercialAt(input, pos, callbacks); + // U+005B LEFT SQUARE BRACKET ([) + case CC_LEFT_SQUARE: + return consumeLeftSquareBracket(input, pos, callbacks); + // U+005C REVERSE SOLIDUS (\) + case CC_REVERSE_SOLIDUS: + return consumeReverseSolidus(input, pos, callbacks); + // U+005D RIGHT SQUARE BRACKET (]) + case CC_RIGHT_SQUARE: + return consumeRightSquareBracket(input, pos, callbacks); + // U+007B LEFT CURLY BRACKET ({) + case CC_LEFT_CURLY: + return consumeLeftCurlyBracket(input, pos, callbacks); + // U+007D RIGHT CURLY BRACKET (}) + case CC_RIGHT_CURLY: + return consumeRightCurlyBracket(input, pos, callbacks); + default: + // digit + // Reconsume the current input code point, consume a numeric token, and return it. + if (_isDigit(cc)) { + pos--; + return consumeANumericToken(input, pos, callbacks); + } else if (cc === CC_LOWER_U || cc === CC_UPPER_U) { + // If unicode ranges allowed is true and the input stream would start a unicode-range, + // reconsume the current input code point, consume a unicode-range token, and return it. + // Skip now + // if (_ifThreeCodePointsWouldStartAUnicodeRange(input, pos)) { + // pos--; + // return consumeAUnicodeRangeToken(input, pos, callbacks); + // } + + // Otherwise, reconsume the current input code point, consume an ident-like token, and return it. + pos--; + return consumeAnIdentLikeToken(input, pos, callbacks); + } + // ident-start code point + // Reconsume the current input code point, consume an ident-like token, and return it. + else if (isIdentStartCodePoint(cc)) { + pos--; + return consumeAnIdentLikeToken(input, pos, callbacks); + } + + // EOF, but we don't have it + + // anything else + // Return a with its value set to the current input code point. + return consumeDelimToken(input, pos, callbacks); + } +}; + +/** + * @param {string} input input css + * @param {number=} pos pos + * @param {CssTokenCallbacks=} callbacks callbacks + * @returns {number} pos + */ +module.exports = (input, pos = 0, callbacks = {}) => { + // This section describes how to consume a token from a stream of code points. It will return a single token of any type. + while (pos < input.length) { + // Consume comments. + pos = consumeComments(input, pos, callbacks); + + // Consume the next input code point. + pos++; + pos = consumeAToken(input, pos, callbacks); + + if (callbacks.needTerminate && callbacks.needTerminate()) { + break; + } + } + + return pos; +}; + +module.exports.isIdentStartCodePoint = isIdentStartCodePoint; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after comments + */ +module.exports.eatComments = (input, pos) => { + for (;;) { + const originalPos = pos; + pos = consumeComments(input, pos, {}); + if (originalPos === pos) { + break; + } + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace + */ +module.exports.eatWhitespace = (input, pos) => { + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace and comments + */ +module.exports.eatWhitespaceAndComments = (input, pos) => { + for (;;) { + const originalPos = pos; + pos = consumeComments(input, pos, {}); + while (_isWhiteSpace(input.charCodeAt(pos))) { + pos++; + } + if (originalPos === pos) { + break; + } + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace and comments + */ +module.exports.eatComments = (input, pos) => { + for (;;) { + const originalPos = pos; + pos = consumeComments(input, pos, {}); + if (originalPos === pos) { + break; + } + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {number} position after whitespace + */ +module.exports.eatWhiteLine = (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (_isSpace(cc)) { + pos++; + continue; + } + if (_isNewline(cc)) pos++; + pos = consumeExtraNewline(cc, input, pos); + break; + } + + return pos; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {[number, number] | undefined} positions of ident sequence + */ +module.exports.skipCommentsAndEatIdentSequence = (input, pos) => { + pos = module.exports.eatComments(input, pos); + + const start = pos; + + if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + input.charCodeAt(pos), + input.charCodeAt(pos + 1), + input.charCodeAt(pos + 2) + ) + ) { + return [start, _consumeAnIdentSequence(input, pos, {})]; + } + + return undefined; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {[number, number] | undefined} positions of ident sequence + */ +module.exports.eatString = (input, pos) => { + pos = module.exports.eatWhitespaceAndComments(input, pos); + + const start = pos; + + if ( + input.charCodeAt(pos) === CC_QUOTATION_MARK || + input.charCodeAt(pos) === CC_APOSTROPHE + ) { + return [start, consumeAStringToken(input, pos + 1, {})]; + } + + return undefined; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {CssTokenCallbacks} cbs callbacks + * @returns {[number, number][]} positions of ident sequence + */ +module.exports.eatImageSetStrings = (input, pos, cbs) => { + /** @type {[number, number][]} */ + const result = []; + + let isFirst = true; + let needStop = false; + // We already in `func(` token + let balanced = 1; + + /** @type {CssTokenCallbacks} */ + const callbacks = { + ...cbs, + string: (_input, start, end) => { + if (isFirst && balanced === 1) { + result.push([start, end]); + isFirst = false; + } + + return end; + }, + comma: (_input, _start, end) => { + if (balanced === 1) { + isFirst = true; + } + + return end; + }, + leftParenthesis: (input, start, end) => { + balanced++; + + return end; + }, + function: (_input, start, end) => { + balanced++; + + return end; + }, + rightParenthesis: (_input, _start, end) => { + balanced--; + + if (balanced === 0) { + needStop = true; + } + + return end; + } + }; + + while (pos < input.length) { + // Consume comments. + pos = consumeComments(input, pos, callbacks); + + // Consume the next input code point. + pos++; + pos = consumeAToken(input, pos, callbacks); + + if (needStop) { + break; + } + } + + return result; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @param {CssTokenCallbacks} cbs callbacks + * @returns {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} positions of top level tokens + */ +module.exports.eatImportTokens = (input, pos, cbs) => { + const result = + /** @type {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} */ + (new Array(4)); + + /** @type {0 | 1 | 2 | undefined} */ + let scope; + let needStop = false; + let balanced = 0; + + /** @type {CssTokenCallbacks} */ + const callbacks = { + ...cbs, + url: (_input, start, end, contentStart, contentEnd) => { + if ( + result[0] === undefined && + balanced === 0 && + result[1] === undefined && + result[2] === undefined && + result[3] === undefined + ) { + result[0] = [start, end, contentStart, contentEnd]; + scope = undefined; + } + + return end; + }, + string: (_input, start, end) => { + if ( + balanced === 0 && + result[0] === undefined && + result[1] === undefined && + result[2] === undefined && + result[3] === undefined + ) { + result[0] = [start, end, start + 1, end - 1]; + scope = undefined; + } else if (result[0] !== undefined && scope === 0) { + result[0][2] = start + 1; + result[0][3] = end - 1; + } + + return end; + }, + leftParenthesis: (_input, _start, end) => { + balanced++; + + return end; + }, + rightParenthesis: (_input, _start, end) => { + balanced--; + + if (balanced === 0 && scope !== undefined) { + /** @type {[number, number]} */ + (result[scope])[1] = end; + scope = undefined; + } + + return end; + }, + function: (input, start, end) => { + if (balanced === 0) { + const name = input + .slice(start, end - 1) + .replace(/\\/g, "") + .toLowerCase(); + + if ( + name === "url" && + result[0] === undefined && + result[1] === undefined && + result[2] === undefined && + result[3] === undefined + ) { + scope = 0; + result[scope] = [start, end + 1, end + 1, end + 1]; + } else if ( + name === "layer" && + result[1] === undefined && + result[2] === undefined + ) { + scope = 1; + result[scope] = [start, end]; + } else if (name === "supports" && result[2] === undefined) { + scope = 2; + result[scope] = [start, end]; + } else { + scope = undefined; + } + } + + balanced++; + + return end; + }, + identifier: (input, start, end) => { + if ( + balanced === 0 && + result[1] === undefined && + result[2] === undefined + ) { + const name = input.slice(start, end).replace(/\\/g, "").toLowerCase(); + + if (name === "layer") { + result[1] = [start, end]; + scope = undefined; + } + } + + return end; + }, + semicolon: (_input, start, end) => { + if (balanced === 0) { + needStop = true; + result[3] = [start, end]; + } + + return end; + } + }; + + while (pos < input.length) { + // Consume comments. + pos = consumeComments(input, pos, callbacks); + + // Consume the next input code point. + pos++; + pos = consumeAToken(input, pos, callbacks); + + if (needStop) { + break; + } + } + + return result; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {[number, number] | undefined} positions of ident sequence + */ +module.exports.eatIdentSequence = (input, pos) => { + pos = module.exports.eatWhitespaceAndComments(input, pos); + + const start = pos; + + if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + input.charCodeAt(pos), + input.charCodeAt(pos + 1), + input.charCodeAt(pos + 2) + ) + ) { + return [start, _consumeAnIdentSequence(input, pos, {})]; + } + + return undefined; +}; + +/** + * @param {string} input input + * @param {number} pos position + * @returns {[number, number, boolean] | undefined} positions of ident sequence or string + */ +module.exports.eatIdentSequenceOrString = (input, pos) => { + pos = module.exports.eatWhitespaceAndComments(input, pos); + + const start = pos; + + if ( + input.charCodeAt(pos) === CC_QUOTATION_MARK || + input.charCodeAt(pos) === CC_APOSTROPHE + ) { + return [start, consumeAStringToken(input, pos + 1, {}), false]; + } else if ( + _ifThreeCodePointsWouldStartAnIdentSequence( + input, + pos, + input.charCodeAt(pos), + input.charCodeAt(pos + 1), + input.charCodeAt(pos + 2) + ) + ) { + return [start, _consumeAnIdentSequence(input, pos, {}), true]; + } + + return undefined; +}; + +/** + * @param {string} chars characters + * @returns {(input: string, pos: number) => number} function to eat characters + */ +module.exports.eatUntil = chars => { + const charCodes = Array.from({ length: chars.length }, (_, i) => + chars.charCodeAt(i) + ); + const arr = Array.from( + { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 }, + () => false + ); + for (const cc of charCodes) { + arr[cc] = true; + } + + return (input, pos) => { + for (;;) { + const cc = input.charCodeAt(pos); + if (cc < arr.length && arr[cc]) { + return pos; + } + pos++; + if (pos === input.length) return pos; + } + }; +}; diff --git a/webpack-lib/lib/debug/ProfilingPlugin.js b/webpack-lib/lib/debug/ProfilingPlugin.js new file mode 100644 index 00000000000..9f2d445a0d0 --- /dev/null +++ b/webpack-lib/lib/debug/ProfilingPlugin.js @@ -0,0 +1,504 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { Tracer } = require("chrome-trace-event"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM, + WEBASSEMBLY_MODULE_TYPE_ASYNC, + WEBASSEMBLY_MODULE_TYPE_SYNC, + JSON_MODULE_TYPE +} = require("../ModuleTypeConstants"); +const createSchemaValidation = require("../util/create-schema-validation"); +const { dirname, mkdirpSync } = require("../util/fs"); + +/** @typedef {import("../../declarations/plugins/debug/ProfilingPlugin").ProfilingPluginOptions} ProfilingPluginOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../ContextModuleFactory")} ContextModuleFactory */ +/** @typedef {import("../ModuleFactory")} ModuleFactory */ +/** @typedef {import("../NormalModuleFactory")} NormalModuleFactory */ +/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ + +/** @typedef {TODO} Inspector */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/debug/ProfilingPlugin.check.js"), + () => require("../../schemas/plugins/debug/ProfilingPlugin.json"), + { + name: "Profiling Plugin", + baseDataPath: "options" + } +); + +/** @type {Inspector | undefined} */ +let inspector; + +try { + // eslint-disable-next-line n/no-unsupported-features/node-builtins + inspector = require("inspector"); +} catch (_err) { + console.log("Unable to CPU profile in < node 8.0"); +} + +class Profiler { + /** + * @param {Inspector} inspector inspector + */ + constructor(inspector) { + this.session = undefined; + this.inspector = inspector; + this._startTime = 0; + } + + hasSession() { + return this.session !== undefined; + } + + startProfiling() { + if (this.inspector === undefined) { + return Promise.resolve(); + } + + try { + this.session = new inspector.Session(); + this.session.connect(); + } catch (_) { + this.session = undefined; + return Promise.resolve(); + } + + const hrtime = process.hrtime(); + this._startTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000); + + return Promise.all([ + this.sendCommand("Profiler.setSamplingInterval", { + interval: 100 + }), + this.sendCommand("Profiler.enable"), + this.sendCommand("Profiler.start") + ]); + } + + /** + * @param {string} method method name + * @param {object} [params] params + * @returns {Promise} Promise for the result + */ + sendCommand(method, params) { + if (this.hasSession()) { + return new Promise((res, rej) => { + this.session.post(method, params, (err, params) => { + if (err !== null) { + rej(err); + } else { + res(params); + } + }); + }); + } + return Promise.resolve(); + } + + destroy() { + if (this.hasSession()) { + this.session.disconnect(); + } + + return Promise.resolve(); + } + + stopProfiling() { + return this.sendCommand("Profiler.stop").then(({ profile }) => { + const hrtime = process.hrtime(); + const endTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000); + // Avoid coverage problems due indirect changes + /* istanbul ignore next */ + if (profile.startTime < this._startTime || profile.endTime > endTime) { + // In some cases timestamps mismatch and we need to adjust them + // Both process.hrtime and the inspector timestamps claim to be relative + // to a unknown point in time. But they do not guarantee that this is the + // same point in time. + const duration = profile.endTime - profile.startTime; + const ownDuration = endTime - this._startTime; + const untracked = Math.max(0, ownDuration - duration); + profile.startTime = this._startTime + untracked / 2; + profile.endTime = endTime - untracked / 2; + } + return { profile }; + }); + } +} + +/** + * an object that wraps Tracer and Profiler with a counter + * @typedef {object} Trace + * @property {Tracer} trace instance of Tracer + * @property {number} counter Counter + * @property {Profiler} profiler instance of Profiler + * @property {Function} end the end function + */ + +/** + * @param {IntermediateFileSystem} fs filesystem used for output + * @param {string} outputPath The location where to write the log. + * @returns {Trace} The trace object + */ +const createTrace = (fs, outputPath) => { + const trace = new Tracer(); + const profiler = new Profiler(/** @type {Inspector} */ (inspector)); + if (/\/|\\/.test(outputPath)) { + const dirPath = dirname(fs, outputPath); + mkdirpSync(fs, dirPath); + } + const fsStream = fs.createWriteStream(outputPath); + + let counter = 0; + + trace.pipe(fsStream); + // These are critical events that need to be inserted so that tools like + // chrome dev tools can load the profile. + trace.instantEvent({ + name: "TracingStartedInPage", + id: ++counter, + cat: ["disabled-by-default-devtools.timeline"], + args: { + data: { + sessionId: "-1", + page: "0xfff", + frames: [ + { + frame: "0xfff", + url: "webpack", + name: "" + } + ] + } + } + }); + + trace.instantEvent({ + name: "TracingStartedInBrowser", + id: ++counter, + cat: ["disabled-by-default-devtools.timeline"], + args: { + data: { + sessionId: "-1" + } + } + }); + + return { + trace, + counter, + profiler, + end: callback => { + trace.push("]"); + // Wait until the write stream finishes. + fsStream.on("close", () => { + callback(); + }); + // Tear down the readable trace stream. + trace.push(null); + } + }; +}; + +const PLUGIN_NAME = "ProfilingPlugin"; + +class ProfilingPlugin { + /** + * @param {ProfilingPluginOptions=} options options object + */ + constructor(options = {}) { + validate(options); + this.outputPath = options.outputPath || "events.json"; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const tracer = createTrace( + /** @type {IntermediateFileSystem} */ + (compiler.intermediateFileSystem), + this.outputPath + ); + tracer.profiler.startProfiling(); + + // Compiler Hooks + for (const hookName of Object.keys(compiler.hooks)) { + const hook = + compiler.hooks[/** @type {keyof Compiler["hooks"]} */ (hookName)]; + if (hook) { + hook.intercept(makeInterceptorFor("Compiler", tracer)(hookName)); + } + } + + for (const hookName of Object.keys(compiler.resolverFactory.hooks)) { + const hook = compiler.resolverFactory.hooks[hookName]; + if (hook) { + hook.intercept(makeInterceptorFor("Resolver", tracer)(hookName)); + } + } + + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory, contextModuleFactory }) => { + interceptAllHooksFor(compilation, tracer, "Compilation"); + interceptAllHooksFor( + normalModuleFactory, + tracer, + "Normal Module Factory" + ); + interceptAllHooksFor( + contextModuleFactory, + tracer, + "Context Module Factory" + ); + interceptAllParserHooks(normalModuleFactory, tracer); + interceptAllJavascriptModulesPluginHooks(compilation, tracer); + } + ); + + // We need to write out the CPU profile when we are all done. + compiler.hooks.done.tapAsync( + { + name: PLUGIN_NAME, + stage: Infinity + }, + (stats, callback) => { + if (compiler.watchMode) return callback(); + tracer.profiler.stopProfiling().then(parsedResults => { + if (parsedResults === undefined) { + tracer.profiler.destroy(); + tracer.end(callback); + return; + } + + const cpuStartTime = parsedResults.profile.startTime; + const cpuEndTime = parsedResults.profile.endTime; + + tracer.trace.completeEvent({ + name: "TaskQueueManager::ProcessTaskFromWorkQueue", + id: ++tracer.counter, + cat: ["toplevel"], + ts: cpuStartTime, + args: { + // eslint-disable-next-line camelcase + src_file: "../../ipc/ipc_moji_bootstrap.cc", + // eslint-disable-next-line camelcase + src_func: "Accept" + } + }); + + tracer.trace.completeEvent({ + name: "EvaluateScript", + id: ++tracer.counter, + cat: ["devtools.timeline"], + ts: cpuStartTime, + dur: cpuEndTime - cpuStartTime, + args: { + data: { + url: "webpack", + lineNumber: 1, + columnNumber: 1, + frame: "0xFFF" + } + } + }); + + tracer.trace.instantEvent({ + name: "CpuProfile", + id: ++tracer.counter, + cat: ["disabled-by-default-devtools.timeline"], + ts: cpuEndTime, + args: { + data: { + cpuProfile: parsedResults.profile + } + } + }); + + tracer.profiler.destroy(); + tracer.end(callback); + }); + } + ); + } +} + +/** + * @param {any} instance instance + * @param {Trace} tracer tracer + * @param {string} logLabel log label + */ +const interceptAllHooksFor = (instance, tracer, logLabel) => { + if (Reflect.has(instance, "hooks")) { + for (const hookName of Object.keys(instance.hooks)) { + const hook = instance.hooks[hookName]; + if (hook && !hook._fakeHook) { + hook.intercept(makeInterceptorFor(logLabel, tracer)(hookName)); + } + } + } +}; + +/** + * @param {NormalModuleFactory} moduleFactory normal module factory + * @param {Trace} tracer tracer + */ +const interceptAllParserHooks = (moduleFactory, tracer) => { + const moduleTypes = [ + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM, + JSON_MODULE_TYPE, + WEBASSEMBLY_MODULE_TYPE_ASYNC, + WEBASSEMBLY_MODULE_TYPE_SYNC + ]; + + for (const moduleType of moduleTypes) { + moduleFactory.hooks.parser + .for(moduleType) + .tap(PLUGIN_NAME, (parser, parserOpts) => { + interceptAllHooksFor(parser, tracer, "Parser"); + }); + } +}; + +/** + * @param {Compilation} compilation compilation + * @param {Trace} tracer tracer + */ +const interceptAllJavascriptModulesPluginHooks = (compilation, tracer) => { + interceptAllHooksFor( + { + hooks: + require("../javascript/JavascriptModulesPlugin").getCompilationHooks( + compilation + ) + }, + tracer, + "JavascriptModulesPlugin" + ); +}; + +/** + * @param {string} instance instance + * @param {Trace} tracer tracer + * @returns {TODO} interceptor + */ +const makeInterceptorFor = (instance, tracer) => hookName => ({ + register: tapInfo => { + const { name, type, fn } = tapInfo; + const newFn = + // Don't tap our own hooks to ensure stream can close cleanly + name === PLUGIN_NAME + ? fn + : makeNewProfiledTapFn(hookName, tracer, { + name, + type, + fn + }); + return { ...tapInfo, fn: newFn }; + } +}); + +// TODO improve typing +/** @typedef {(...args: TODO[]) => void | Promise} PluginFunction */ + +/** + * @param {string} hookName Name of the hook to profile. + * @param {Trace} tracer The trace object. + * @param {object} options Options for the profiled fn. + * @param {string} options.name Plugin name + * @param {string} options.type Plugin type (sync | async | promise) + * @param {PluginFunction} options.fn Plugin function + * @returns {PluginFunction} Chainable hooked function. + */ +const makeNewProfiledTapFn = (hookName, tracer, { name, type, fn }) => { + const defaultCategory = ["blink.user_timing"]; + + switch (type) { + case "promise": + return (...args) => { + const id = ++tracer.counter; + tracer.trace.begin({ + name, + id, + cat: defaultCategory + }); + const promise = /** @type {Promise<*>} */ (fn(...args)); + return promise.then(r => { + tracer.trace.end({ + name, + id, + cat: defaultCategory + }); + return r; + }); + }; + case "async": + return (...args) => { + const id = ++tracer.counter; + tracer.trace.begin({ + name, + id, + cat: defaultCategory + }); + const callback = args.pop(); + fn(...args, (...r) => { + tracer.trace.end({ + name, + id, + cat: defaultCategory + }); + callback(...r); + }); + }; + case "sync": + return (...args) => { + const id = ++tracer.counter; + // Do not instrument ourself due to the CPU + // profile needing to be the last event in the trace. + if (name === PLUGIN_NAME) { + return fn(...args); + } + + tracer.trace.begin({ + name, + id, + cat: defaultCategory + }); + let r; + try { + r = fn(...args); + } catch (err) { + tracer.trace.end({ + name, + id, + cat: defaultCategory + }); + throw err; + } + tracer.trace.end({ + name, + id, + cat: defaultCategory + }); + return r; + }; + default: + break; + } +}; + +module.exports = ProfilingPlugin; +module.exports.Profiler = Profiler; diff --git a/webpack-lib/lib/dependencies/AMDDefineDependency.js b/webpack-lib/lib/dependencies/AMDDefineDependency.js new file mode 100644 index 00000000000..4acb1525271 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDDefineDependency.js @@ -0,0 +1,265 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +/** @type {Record} */ +const DEFINITIONS = { + f: { + definition: "var __WEBPACK_AMD_DEFINE_RESULT__;", + content: `!(__WEBPACK_AMD_DEFINE_RESULT__ = (#).call(exports, ${RuntimeGlobals.require}, exports, module), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, + requests: [ + RuntimeGlobals.require, + RuntimeGlobals.exports, + RuntimeGlobals.module + ] + }, + o: { + definition: "", + content: "!(module.exports = #)", + requests: [RuntimeGlobals.module] + }, + of: { + definition: + "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;", + content: `!(__WEBPACK_AMD_DEFINE_FACTORY__ = (#), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, ${RuntimeGlobals.require}, exports, module)) : + __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, + requests: [ + RuntimeGlobals.require, + RuntimeGlobals.exports, + RuntimeGlobals.module + ] + }, + af: { + definition: + "var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", + content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_RESULT__ = (#).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, + requests: [RuntimeGlobals.exports, RuntimeGlobals.module] + }, + ao: { + definition: "", + content: "!(#, module.exports = #)", + requests: [RuntimeGlobals.module] + }, + aof: { + definition: + "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", + content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_FACTORY__ = (#), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, + requests: [RuntimeGlobals.exports, RuntimeGlobals.module] + }, + lf: { + definition: "var XXX, XXXmodule;", + content: `!(XXXmodule = { id: YYY, exports: {}, loaded: false }, XXX = (#).call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule), XXXmodule.loaded = true, XXX === undefined && (XXX = XXXmodule.exports))`, + requests: [RuntimeGlobals.require, RuntimeGlobals.module] + }, + lo: { + definition: "var XXX;", + content: "!(XXX = #)", + requests: [] + }, + lof: { + definition: "var XXX, XXXfactory, XXXmodule;", + content: `!(XXXfactory = (#), (typeof XXXfactory === 'function' ? ((XXXmodule = { id: YYY, exports: {}, loaded: false }), (XXX = XXXfactory.call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule)), (XXXmodule.loaded = true), XXX === undefined && (XXX = XXXmodule.exports)) : XXX = XXXfactory))`, + requests: [RuntimeGlobals.require, RuntimeGlobals.module] + }, + laf: { + definition: "var __WEBPACK_AMD_DEFINE_ARRAY__, XXX, XXXexports;", + content: + "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, XXX = (#).apply(XXXexports = {}, __WEBPACK_AMD_DEFINE_ARRAY__), XXX === undefined && (XXX = XXXexports))", + requests: [] + }, + lao: { + definition: "var XXX;", + content: "!(#, XXX = #)", + requests: [] + }, + laof: { + definition: "var XXXarray, XXXfactory, XXXexports, XXX;", + content: `!(XXXarray = #, XXXfactory = (#), + (typeof XXXfactory === 'function' ? + ((XXX = XXXfactory.apply(XXXexports = {}, XXXarray)), XXX === undefined && (XXX = XXXexports)) : + (XXX = XXXfactory) + ))`, + requests: [] + } +}; + +class AMDDefineDependency extends NullDependency { + /** + * @param {Range} range range + * @param {Range | null} arrayRange array range + * @param {Range | null} functionRange function range + * @param {Range | null} objectRange object range + * @param {string | null} namedModule true, when define is called with a name + */ + constructor(range, arrayRange, functionRange, objectRange, namedModule) { + super(); + this.range = range; + this.arrayRange = arrayRange; + this.functionRange = functionRange; + this.objectRange = objectRange; + this.namedModule = namedModule; + this.localModule = null; + } + + get type() { + return "amd define"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.arrayRange); + write(this.functionRange); + write(this.objectRange); + write(this.namedModule); + write(this.localModule); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.arrayRange = read(); + this.functionRange = read(); + this.objectRange = read(); + this.namedModule = read(); + this.localModule = read(); + super.deserialize(context); + } +} + +makeSerializable( + AMDDefineDependency, + "webpack/lib/dependencies/AMDDefineDependency" +); + +AMDDefineDependency.Template = class AMDDefineDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { runtimeRequirements }) { + const dep = /** @type {AMDDefineDependency} */ (dependency); + const branch = this.branch(dep); + const { definition, content, requests } = DEFINITIONS[branch]; + for (const req of requests) { + runtimeRequirements.add(req); + } + this.replace(dep, source, definition, content); + } + + /** + * @param {AMDDefineDependency} dependency dependency + * @returns {string} variable name + */ + localModuleVar(dependency) { + return ( + dependency.localModule && + dependency.localModule.used && + dependency.localModule.variableName() + ); + } + + /** + * @param {AMDDefineDependency} dependency dependency + * @returns {string} branch + */ + branch(dependency) { + const localModuleVar = this.localModuleVar(dependency) ? "l" : ""; + const arrayRange = dependency.arrayRange ? "a" : ""; + const objectRange = dependency.objectRange ? "o" : ""; + const functionRange = dependency.functionRange ? "f" : ""; + return localModuleVar + arrayRange + objectRange + functionRange; + } + + /** + * @param {AMDDefineDependency} dependency dependency + * @param {ReplaceSource} source source + * @param {string} definition definition + * @param {string} text text + */ + replace(dependency, source, definition, text) { + const localModuleVar = this.localModuleVar(dependency); + if (localModuleVar) { + text = text.replace(/XXX/g, localModuleVar.replace(/\$/g, "$$$$")); + definition = definition.replace( + /XXX/g, + localModuleVar.replace(/\$/g, "$$$$") + ); + } + + if (dependency.namedModule) { + text = text.replace(/YYY/g, JSON.stringify(dependency.namedModule)); + } + + const texts = text.split("#"); + + if (definition) source.insert(0, definition); + + let current = dependency.range[0]; + if (dependency.arrayRange) { + source.replace( + current, + dependency.arrayRange[0] - 1, + /** @type {string} */ (texts.shift()) + ); + current = dependency.arrayRange[1]; + } + + if (dependency.objectRange) { + source.replace( + current, + dependency.objectRange[0] - 1, + /** @type {string} */ (texts.shift()) + ); + current = dependency.objectRange[1]; + } else if (dependency.functionRange) { + source.replace( + current, + dependency.functionRange[0] - 1, + /** @type {string} */ (texts.shift()) + ); + current = dependency.functionRange[1]; + } + source.replace( + current, + dependency.range[1] - 1, + /** @type {string} */ (texts.shift()) + ); + if (texts.length > 0) throw new Error("Implementation error"); + } +}; + +module.exports = AMDDefineDependency; diff --git a/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js b/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js new file mode 100644 index 00000000000..14fbe4af218 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js @@ -0,0 +1,497 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const AMDDefineDependency = require("./AMDDefineDependency"); +const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); +const AMDRequireContextDependency = require("./AMDRequireContextDependency"); +const AMDRequireItemDependency = require("./AMDRequireItemDependency"); +const ConstDependency = require("./ConstDependency"); +const ContextDependencyHelpers = require("./ContextDependencyHelpers"); +const DynamicExports = require("./DynamicExports"); +const LocalModuleDependency = require("./LocalModuleDependency"); +const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers"); + +/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").FunctionExpression} FunctionExpression */ +/** @typedef {import("estree").Identifier} Identifier */ +/** @typedef {import("estree").Literal} Literal */ +/** @typedef {import("estree").MemberExpression} MemberExpression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").SimpleCallExpression} SimpleCallExpression */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +/** + * @param {Expression | SpreadElement} expr expression + * @returns {expr is CallExpression} true if it's a bound function expression + */ +const isBoundFunctionExpression = expr => { + if (expr.type !== "CallExpression") return false; + if (expr.callee.type !== "MemberExpression") return false; + if (expr.callee.computed) return false; + if (expr.callee.object.type !== "FunctionExpression") return false; + if (expr.callee.property.type !== "Identifier") return false; + if (expr.callee.property.name !== "bind") return false; + return true; +}; + +/** @typedef {FunctionExpression | ArrowFunctionExpression} UnboundFunctionExpression */ + +/** + * @param {Expression | SpreadElement} expr expression + * @returns {expr is FunctionExpression | ArrowFunctionExpression} true when unbound function expression + */ +const isUnboundFunctionExpression = expr => { + if (expr.type === "FunctionExpression") return true; + if (expr.type === "ArrowFunctionExpression") return true; + return false; +}; + +/** + * @param {Expression | SpreadElement} expr expression + * @returns {expr is FunctionExpression | ArrowFunctionExpression | CallExpression} true when callable + */ +const isCallable = expr => { + if (isUnboundFunctionExpression(expr)) return true; + if (isBoundFunctionExpression(expr)) return true; + return false; +}; + +class AMDDefineDependencyParserPlugin { + /** + * @param {JavascriptParserOptions} options parserOptions + */ + constructor(options) { + this.options = options; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.call + .for("define") + .tap( + "AMDDefineDependencyParserPlugin", + this.processCallDefine.bind(this, parser) + ); + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {Record} identifiers identifiers + * @param {string=} namedModule named module + * @returns {boolean | undefined} result + */ + processArray(parser, expr, param, identifiers, namedModule) { + if (param.isArray()) { + const items = /** @type {BasicEvaluatedExpression[]} */ (param.items); + for (const [idx, item] of items.entries()) { + if ( + item.isString() && + ["require", "module", "exports"].includes( + /** @type {string} */ (item.string) + ) + ) + identifiers[/** @type {number} */ (idx)] = /** @type {string} */ ( + item.string + ); + const result = this.processItem(parser, expr, item, namedModule); + if (result === undefined) { + this.processContext(parser, expr, item); + } + } + return true; + } else if (param.isConstArray()) { + /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */ + const deps = []; + const array = /** @type {string[]} */ (param.array); + for (const [idx, request] of array.entries()) { + let dep; + let localModule; + if (request === "require") { + identifiers[idx] = request; + dep = RuntimeGlobals.require; + } else if (["exports", "module"].includes(request)) { + identifiers[idx] = request; + dep = request; + } else if ((localModule = getLocalModule(parser.state, request))) { + localModule.flagUsed(); + dep = new LocalModuleDependency(localModule, undefined, false); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } else { + dep = this.newRequireItemDependency(request); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + } + deps.push(dep); + } + const dep = this.newRequireArrayDependency( + deps, + /** @type {Range} */ (param.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {string=} namedModule named module + * @returns {boolean | undefined} result + */ + processItem(parser, expr, param, namedModule) { + if (param.isConditional()) { + const options = /** @type {BasicEvaluatedExpression[]} */ (param.options); + for (const item of options) { + const result = this.processItem(parser, expr, item); + if (result === undefined) { + this.processContext(parser, expr, item); + } + } + + return true; + } else if (param.isString()) { + let dep; + let localModule; + + if (param.string === "require") { + dep = new ConstDependency( + RuntimeGlobals.require, + /** @type {Range} */ (param.range), + [RuntimeGlobals.require] + ); + } else if (param.string === "exports") { + dep = new ConstDependency( + "exports", + /** @type {Range} */ (param.range), + [RuntimeGlobals.exports] + ); + } else if (param.string === "module") { + dep = new ConstDependency( + "module", + /** @type {Range} */ (param.range), + [RuntimeGlobals.module] + ); + } else if ( + (localModule = getLocalModule( + parser.state, + /** @type {string} */ (param.string), + namedModule + )) + ) { + localModule.flagUsed(); + dep = new LocalModuleDependency(localModule, param.range, false); + } else { + dep = this.newRequireItemDependency( + /** @type {string} */ (param.string), + param.range + ); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | undefined} result + */ + processContext(parser, expr, param) { + const dep = ContextDependencyHelpers.create( + AMDRequireContextDependency, + /** @type {Range} */ (param.range), + param, + expr, + this.options, + { + category: "amd" + }, + parser + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @returns {boolean | undefined} result + */ + processCallDefine(parser, expr) { + /** @type {TODO} */ + let array; + /** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | Identifier | undefined} */ + let fn; + /** @type {ObjectExpression | Identifier | undefined} */ + let obj; + /** @type {string | undefined} */ + let namedModule; + switch (expr.arguments.length) { + case 1: + if (isCallable(expr.arguments[0])) { + // define(f() {…}) + fn = expr.arguments[0]; + } else if (expr.arguments[0].type === "ObjectExpression") { + // define({…}) + obj = expr.arguments[0]; + } else { + // define(expr) + // unclear if function or object + obj = fn = /** @type {Identifier} */ (expr.arguments[0]); + } + break; + case 2: + if (expr.arguments[0].type === "Literal") { + namedModule = /** @type {string} */ (expr.arguments[0].value); + // define("…", …) + if (isCallable(expr.arguments[1])) { + // define("…", f() {…}) + fn = expr.arguments[1]; + } else if (expr.arguments[1].type === "ObjectExpression") { + // define("…", {…}) + obj = expr.arguments[1]; + } else { + // define("…", expr) + // unclear if function or object + obj = fn = /** @type {Identifier} */ (expr.arguments[1]); + } + } else { + array = expr.arguments[0]; + if (isCallable(expr.arguments[1])) { + // define([…], f() {}) + fn = expr.arguments[1]; + } else if (expr.arguments[1].type === "ObjectExpression") { + // define([…], {…}) + obj = expr.arguments[1]; + } else { + // define([…], expr) + // unclear if function or object + obj = fn = /** @type {Identifier} */ (expr.arguments[1]); + } + } + break; + case 3: + // define("…", […], f() {…}) + namedModule = + /** @type {string} */ + ( + /** @type {Literal} */ + (expr.arguments[0]).value + ); + array = expr.arguments[1]; + if (isCallable(expr.arguments[2])) { + // define("…", […], f() {}) + fn = expr.arguments[2]; + } else if (expr.arguments[2].type === "ObjectExpression") { + // define("…", […], {…}) + obj = expr.arguments[2]; + } else { + // define("…", […], expr) + // unclear if function or object + obj = fn = /** @type {Identifier} */ (expr.arguments[2]); + } + break; + default: + return; + } + DynamicExports.bailout(parser.state); + /** @type {Identifier[] | null} */ + let fnParams = null; + let fnParamsOffset = 0; + if (fn) { + if (isUnboundFunctionExpression(fn)) { + fnParams = + /** @type {Identifier[]} */ + (fn.params); + } else if (isBoundFunctionExpression(fn)) { + const object = + /** @type {FunctionExpression} */ + (/** @type {MemberExpression} */ (fn.callee).object); + + fnParams = + /** @type {Identifier[]} */ + (object.params); + fnParamsOffset = fn.arguments.length - 1; + if (fnParamsOffset < 0) { + fnParamsOffset = 0; + } + } + } + const fnRenames = new Map(); + if (array) { + /** @type {Record} */ + const identifiers = {}; + const param = parser.evaluateExpression(array); + const result = this.processArray( + parser, + expr, + param, + identifiers, + namedModule + ); + if (!result) return; + if (fnParams) { + fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { + if (identifiers[idx]) { + fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); + return false; + } + return true; + }); + } + } else { + const identifiers = ["require", "exports", "module"]; + if (fnParams) { + fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { + if (identifiers[idx]) { + fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); + return false; + } + return true; + }); + } + } + /** @type {boolean | undefined} */ + let inTry; + if (fn && isUnboundFunctionExpression(fn)) { + inTry = parser.scope.inTry; + parser.inScope(fnParams, () => { + for (const [name, varInfo] of fnRenames) { + parser.setVariable(name, varInfo); + } + parser.scope.inTry = /** @type {boolean} */ (inTry); + if (fn.body.type === "BlockStatement") { + parser.detectMode(fn.body.body); + const prev = parser.prevStatement; + parser.preWalkStatement(fn.body); + parser.prevStatement = prev; + parser.walkStatement(fn.body); + } else { + parser.walkExpression(fn.body); + } + }); + } else if (fn && isBoundFunctionExpression(fn)) { + inTry = parser.scope.inTry; + + const object = + /** @type {FunctionExpression} */ + (/** @type {MemberExpression} */ (fn.callee).object); + + parser.inScope( + /** @type {Identifier[]} */ + (object.params).filter( + i => !["require", "module", "exports"].includes(i.name) + ), + () => { + for (const [name, varInfo] of fnRenames) { + parser.setVariable(name, varInfo); + } + parser.scope.inTry = /** @type {boolean} */ (inTry); + + if (object.body.type === "BlockStatement") { + parser.detectMode(object.body.body); + const prev = parser.prevStatement; + parser.preWalkStatement(object.body); + parser.prevStatement = prev; + parser.walkStatement(object.body); + } else { + parser.walkExpression(object.body); + } + } + ); + if (fn.arguments) { + parser.walkExpressions(fn.arguments); + } + } else if (fn || obj) { + parser.walkExpression(fn || obj); + } + + const dep = this.newDefineDependency( + /** @type {Range} */ (expr.range), + array ? /** @type {Range} */ (array.range) : null, + fn ? /** @type {Range} */ (fn.range) : null, + obj ? /** @type {Range} */ (obj.range) : null, + namedModule || null + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + if (namedModule) { + dep.localModule = addLocalModule(parser.state, namedModule); + } + parser.state.module.addPresentationalDependency(dep); + return true; + } + + /** + * @param {Range} range range + * @param {Range | null} arrayRange array range + * @param {Range | null} functionRange function range + * @param {Range | null} objectRange object range + * @param {string | null} namedModule true, when define is called with a name + * @returns {AMDDefineDependency} AMDDefineDependency + */ + newDefineDependency( + range, + arrayRange, + functionRange, + objectRange, + namedModule + ) { + return new AMDDefineDependency( + range, + arrayRange, + functionRange, + objectRange, + namedModule + ); + } + + /** + * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array + * @param {Range} range range + * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency + */ + newRequireArrayDependency(depsArray, range) { + return new AMDRequireArrayDependency(depsArray, range); + } + + /** + * @param {string} request request + * @param {Range=} range range + * @returns {AMDRequireItemDependency} AMDRequireItemDependency + */ + newRequireItemDependency(request, range) { + return new AMDRequireItemDependency(request, range); + } +} + +module.exports = AMDDefineDependencyParserPlugin; diff --git a/webpack-lib/lib/dependencies/AMDPlugin.js b/webpack-lib/lib/dependencies/AMDPlugin.js new file mode 100644 index 00000000000..2ae03b78bc7 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDPlugin.js @@ -0,0 +1,236 @@ +/* + 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 +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const { + approve, + evaluateToIdentifier, + evaluateToString, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); + +const AMDDefineDependency = require("./AMDDefineDependency"); +const AMDDefineDependencyParserPlugin = require("./AMDDefineDependencyParserPlugin"); +const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); +const AMDRequireContextDependency = require("./AMDRequireContextDependency"); +const AMDRequireDependenciesBlockParserPlugin = require("./AMDRequireDependenciesBlockParserPlugin"); +const AMDRequireDependency = require("./AMDRequireDependency"); +const AMDRequireItemDependency = require("./AMDRequireItemDependency"); +const { + AMDDefineRuntimeModule, + AMDOptionsRuntimeModule +} = require("./AMDRuntimeModules"); +const ConstDependency = require("./ConstDependency"); +const LocalModuleDependency = require("./LocalModuleDependency"); +const UnsupportedDependency = require("./UnsupportedDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const PLUGIN_NAME = "AMDPlugin"; + +class AMDPlugin { + /** + * @param {Record} amdOptions the AMD options + */ + constructor(amdOptions) { + this.amdOptions = amdOptions; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const amdOptions = this.amdOptions; + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyTemplates.set( + AMDRequireDependency, + new AMDRequireDependency.Template() + ); + + compilation.dependencyFactories.set( + AMDRequireItemDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + AMDRequireItemDependency, + new AMDRequireItemDependency.Template() + ); + + compilation.dependencyTemplates.set( + AMDRequireArrayDependency, + new AMDRequireArrayDependency.Template() + ); + + compilation.dependencyFactories.set( + AMDRequireContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + AMDRequireContextDependency, + new AMDRequireContextDependency.Template() + ); + + compilation.dependencyTemplates.set( + AMDDefineDependency, + new AMDDefineDependency.Template() + ); + + compilation.dependencyTemplates.set( + UnsupportedDependency, + new UnsupportedDependency.Template() + ); + + compilation.dependencyTemplates.set( + LocalModuleDependency, + new LocalModuleDependency.Template() + ); + + compilation.hooks.runtimeRequirementInModule + .for(RuntimeGlobals.amdDefine) + .tap(PLUGIN_NAME, (module, set) => { + set.add(RuntimeGlobals.require); + }); + + compilation.hooks.runtimeRequirementInModule + .for(RuntimeGlobals.amdOptions) + .tap(PLUGIN_NAME, (module, set) => { + set.add(RuntimeGlobals.requireScope); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.amdDefine) + .tap(PLUGIN_NAME, (chunk, set) => { + compilation.addRuntimeModule(chunk, new AMDDefineRuntimeModule()); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.amdOptions) + .tap(PLUGIN_NAME, (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new AMDOptionsRuntimeModule(amdOptions) + ); + }); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.amd !== undefined && !parserOptions.amd) return; + + /** + * @param {string} optionExpr option expression + * @param {string} rootName root name + * @param {function(): TODO} getMembers callback + */ + const tapOptionsHooks = (optionExpr, rootName, getMembers) => { + parser.hooks.expression + .for(optionExpr) + .tap( + PLUGIN_NAME, + toConstantDependency(parser, RuntimeGlobals.amdOptions, [ + RuntimeGlobals.amdOptions + ]) + ); + parser.hooks.evaluateIdentifier + .for(optionExpr) + .tap( + PLUGIN_NAME, + evaluateToIdentifier(optionExpr, rootName, getMembers, true) + ); + parser.hooks.evaluateTypeof + .for(optionExpr) + .tap(PLUGIN_NAME, evaluateToString("object")); + parser.hooks.typeof + .for(optionExpr) + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("object")) + ); + }; + + new AMDRequireDependenciesBlockParserPlugin(parserOptions).apply( + parser + ); + new AMDDefineDependencyParserPlugin(parserOptions).apply(parser); + + tapOptionsHooks("define.amd", "define", () => "amd"); + tapOptionsHooks("require.amd", "require", () => ["amd"]); + tapOptionsHooks( + "__webpack_amd_options__", + "__webpack_amd_options__", + () => [] + ); + + parser.hooks.expression.for("define").tap(PLUGIN_NAME, expr => { + const dep = new ConstDependency( + RuntimeGlobals.amdDefine, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.amdDefine] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + parser.hooks.typeof + .for("define") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for("define") + .tap(PLUGIN_NAME, evaluateToString("function")); + parser.hooks.canRename.for("define").tap(PLUGIN_NAME, approve); + parser.hooks.rename.for("define").tap(PLUGIN_NAME, expr => { + const dep = new ConstDependency( + RuntimeGlobals.amdDefine, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.amdDefine] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return false; + }); + parser.hooks.typeof + .for("require") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for("require") + .tap(PLUGIN_NAME, evaluateToString("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); + } + ); + } +} + +module.exports = AMDPlugin; diff --git a/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js b/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js new file mode 100644 index 00000000000..a182f6c230f --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js @@ -0,0 +1,123 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const DependencyTemplate = require("../DependencyTemplate"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./AMDRequireItemDependency")} AMDRequireItemDependency */ +/** @typedef {import("./LocalModuleDependency")} LocalModuleDependency */ + +class AMDRequireArrayDependency extends NullDependency { + /** + * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array + * @param {Range} range range + */ + constructor(depsArray, range) { + super(); + + this.depsArray = depsArray; + this.range = range; + } + + get type() { + return "amd require array"; + } + + get category() { + return "amd"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.depsArray); + write(this.range); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.depsArray = read(); + this.range = read(); + + super.deserialize(context); + } +} + +makeSerializable( + AMDRequireArrayDependency, + "webpack/lib/dependencies/AMDRequireArrayDependency" +); + +AMDRequireArrayDependency.Template = class AMDRequireArrayDependencyTemplate extends ( + DependencyTemplate +) { + /** + * @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 dep = /** @type {AMDRequireArrayDependency} */ (dependency); + const content = this.getContent(dep, templateContext); + source.replace(dep.range[0], dep.range[1] - 1, content); + } + + /** + * @param {AMDRequireArrayDependency} dep the dependency for which the template should be applied + * @param {DependencyTemplateContext} templateContext the context object + * @returns {string} content + */ + getContent(dep, templateContext) { + const requires = dep.depsArray.map(dependency => + this.contentForDependency(dependency, templateContext) + ); + return `[${requires.join(", ")}]`; + } + + /** + * @param {TODO} dep the dependency for which the template should be applied + * @param {DependencyTemplateContext} templateContext the context object + * @returns {string} content + */ + contentForDependency( + dep, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + if (typeof dep === "string") { + return dep; + } + + if (dep.localModule) { + return dep.localModule.variableName(); + } + return runtimeTemplate.moduleExports({ + module: moduleGraph.getModule(dep), + chunkGraph, + request: dep.request, + runtimeRequirements + }); + } +}; + +module.exports = AMDRequireArrayDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireContextDependency.js b/webpack-lib/lib/dependencies/AMDRequireContextDependency.js new file mode 100644 index 00000000000..f040cb6e861 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireContextDependency.js @@ -0,0 +1,68 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class AMDRequireContextDependency extends ContextDependency { + /** + * @param {TODO} options options + * @param {Range} range range + * @param {Range} valueRange value range + */ + constructor(options, range, valueRange) { + super(options); + + this.range = range; + this.valueRange = valueRange; + } + + get type() { + return "amd require context"; + } + + get category() { + return "amd"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.range); + write(this.valueRange); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.range = read(); + this.valueRange = read(); + + super.deserialize(context); + } +} + +makeSerializable( + AMDRequireContextDependency, + "webpack/lib/dependencies/AMDRequireContextDependency" +); + +AMDRequireContextDependency.Template = require("./ContextDependencyTemplateAsRequireCall"); + +module.exports = AMDRequireContextDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js new file mode 100644 index 00000000000..615660c3c9e --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js @@ -0,0 +1,28 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ + +class AMDRequireDependenciesBlock extends AsyncDependenciesBlock { + /** + * @param {DependencyLocation} loc location info + * @param {string=} request request + */ + constructor(loc, request) { + super(null, loc, request); + } +} + +makeSerializable( + AMDRequireDependenciesBlock, + "webpack/lib/dependencies/AMDRequireDependenciesBlock" +); + +module.exports = AMDRequireDependenciesBlock; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js new file mode 100644 index 00000000000..803ce398bee --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js @@ -0,0 +1,410 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); +const AMDRequireContextDependency = require("./AMDRequireContextDependency"); +const AMDRequireDependenciesBlock = require("./AMDRequireDependenciesBlock"); +const AMDRequireDependency = require("./AMDRequireDependency"); +const AMDRequireItemDependency = require("./AMDRequireItemDependency"); +const ConstDependency = require("./ConstDependency"); +const ContextDependencyHelpers = require("./ContextDependencyHelpers"); +const LocalModuleDependency = require("./LocalModuleDependency"); +const { getLocalModule } = require("./LocalModulesHelpers"); +const UnsupportedDependency = require("./UnsupportedDependency"); +const getFunctionExpression = require("./getFunctionExpression"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").Identifier} Identifier */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class AMDRequireDependenciesBlockParserPlugin { + /** + * @param {JavascriptParserOptions} options parserOptions + */ + constructor(options) { + this.options = options; + } + + /** + * @param {JavascriptParser} parser the parser + * @param {Expression | SpreadElement} expression expression + * @returns {boolean} need bind this + */ + processFunctionArgument(parser, expression) { + let bindThis = true; + const fnData = getFunctionExpression(expression); + if (fnData) { + parser.inScope( + fnData.fn.params.filter( + i => + !["require", "module", "exports"].includes( + /** @type {Identifier} */ (i).name + ) + ), + () => { + if (fnData.fn.body.type === "BlockStatement") { + parser.walkStatement(fnData.fn.body); + } else { + parser.walkExpression(fnData.fn.body); + } + } + ); + parser.walkExpressions(fnData.expressions); + if (fnData.needThis === false) { + bindThis = false; + } + } else { + parser.walkExpression(expression); + } + return bindThis; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.call + .for("require") + .tap( + "AMDRequireDependenciesBlockParserPlugin", + this.processCallRequire.bind(this, parser) + ); + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | undefined} result + */ + processArray(parser, expr, param) { + if (param.isArray()) { + for (const p of /** @type {BasicEvaluatedExpression[]} */ (param.items)) { + const result = this.processItem(parser, expr, p); + if (result === undefined) { + this.processContext(parser, expr, p); + } + } + return true; + } else if (param.isConstArray()) { + /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */ + const deps = []; + for (const request of /** @type {any[]} */ (param.array)) { + let dep; + let localModule; + if (request === "require") { + dep = RuntimeGlobals.require; + } else if (["exports", "module"].includes(request)) { + dep = request; + } else if ((localModule = getLocalModule(parser.state, request))) { + localModule.flagUsed(); + dep = new LocalModuleDependency(localModule, undefined, false); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } else { + dep = this.newRequireItemDependency(request); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + } + deps.push(dep); + } + const dep = this.newRequireArrayDependency( + deps, + /** @type {Range} */ (param.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | undefined} result + */ + processItem(parser, expr, param) { + if (param.isConditional()) { + for (const p of /** @type {BasicEvaluatedExpression[]} */ ( + param.options + )) { + const result = this.processItem(parser, expr, p); + if (result === undefined) { + this.processContext(parser, expr, p); + } + } + return true; + } else if (param.isString()) { + let dep; + let localModule; + if (param.string === "require") { + dep = new ConstDependency( + RuntimeGlobals.require, + /** @type {TODO} */ (param.string), + [RuntimeGlobals.require] + ); + } else if (param.string === "module") { + dep = new ConstDependency( + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).moduleArgument, + /** @type {Range} */ (param.range), + [RuntimeGlobals.module] + ); + } else if (param.string === "exports") { + dep = new ConstDependency( + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).exportsArgument, + /** @type {Range} */ (param.range), + [RuntimeGlobals.exports] + ); + } else if ( + (localModule = getLocalModule( + parser.state, + /** @type {string} */ (param.string) + )) + ) { + localModule.flagUsed(); + dep = new LocalModuleDependency(localModule, param.range, false); + } else { + dep = this.newRequireItemDependency( + /** @type {string} */ (param.string), + param.range + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | undefined} result + */ + processContext(parser, expr, param) { + const dep = ContextDependencyHelpers.create( + AMDRequireContextDependency, + /** @type {Range} */ (param.range), + param, + expr, + this.options, + { + category: "amd" + }, + parser + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + + /** + * @param {BasicEvaluatedExpression} param param + * @returns {string | undefined} result + */ + processArrayForRequestString(param) { + if (param.isArray()) { + const result = + /** @type {BasicEvaluatedExpression[]} */ + (param.items).map(item => this.processItemForRequestString(item)); + if (result.every(Boolean)) return result.join(" "); + } else if (param.isConstArray()) { + return /** @type {string[]} */ (param.array).join(" "); + } + } + + /** + * @param {BasicEvaluatedExpression} param param + * @returns {string | undefined} result + */ + processItemForRequestString(param) { + if (param.isConditional()) { + const result = + /** @type {BasicEvaluatedExpression[]} */ + (param.options).map(item => this.processItemForRequestString(item)); + if (result.every(Boolean)) return result.join("|"); + } else if (param.isString()) { + return param.string; + } + } + + /** + * @param {JavascriptParser} parser the parser + * @param {CallExpression} expr call expression + * @returns {boolean | undefined} result + */ + processCallRequire(parser, expr) { + /** @type {BasicEvaluatedExpression | undefined} */ + let param; + /** @type {AMDRequireDependenciesBlock | undefined | null} */ + let depBlock; + /** @type {AMDRequireDependency | undefined} */ + let dep; + /** @type {boolean | undefined} */ + let result; + + const old = parser.state.current; + + if (expr.arguments.length >= 1) { + param = parser.evaluateExpression( + /** @type {Expression} */ (expr.arguments[0]) + ); + depBlock = this.newRequireDependenciesBlock( + /** @type {DependencyLocation} */ (expr.loc), + this.processArrayForRequestString(param) + ); + dep = this.newRequireDependency( + /** @type {Range} */ (expr.range), + /** @type {Range} */ (param.range), + expr.arguments.length > 1 + ? /** @type {Range} */ (expr.arguments[1].range) + : null, + expr.arguments.length > 2 + ? /** @type {Range} */ (expr.arguments[2].range) + : null + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + depBlock.addDependency(dep); + + parser.state.current = /** @type {TODO} */ (depBlock); + } + + if (expr.arguments.length === 1) { + parser.inScope([], () => { + result = this.processArray( + parser, + expr, + /** @type {BasicEvaluatedExpression} */ (param) + ); + }); + parser.state.current = old; + if (!result) return; + parser.state.current.addBlock( + /** @type {AMDRequireDependenciesBlock} */ (depBlock) + ); + return true; + } + + if (expr.arguments.length === 2 || expr.arguments.length === 3) { + try { + parser.inScope([], () => { + result = this.processArray( + parser, + expr, + /** @type {BasicEvaluatedExpression} */ (param) + ); + }); + if (!result) { + const dep = new UnsupportedDependency( + "unsupported", + /** @type {Range} */ (expr.range) + ); + old.addPresentationalDependency(dep); + if (parser.state.module) { + parser.state.module.addError( + new UnsupportedFeatureWarning( + `Cannot statically analyse 'require(…, …)' in line ${ + /** @type {SourceLocation} */ (expr.loc).start.line + }`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + depBlock = null; + return true; + } + /** @type {AMDRequireDependency} */ + (dep).functionBindThis = this.processFunctionArgument( + parser, + expr.arguments[1] + ); + if (expr.arguments.length === 3) { + /** @type {AMDRequireDependency} */ + (dep).errorCallbackBindThis = this.processFunctionArgument( + parser, + expr.arguments[2] + ); + } + } finally { + parser.state.current = old; + if (depBlock) parser.state.current.addBlock(depBlock); + } + return true; + } + } + + /** + * @param {DependencyLocation} loc location + * @param {string=} request request + * @returns {AMDRequireDependenciesBlock} AMDRequireDependenciesBlock + */ + newRequireDependenciesBlock(loc, request) { + return new AMDRequireDependenciesBlock(loc, request); + } + + /** + * @param {Range} outerRange outer range + * @param {Range} arrayRange array range + * @param {Range | null} functionRange function range + * @param {Range | null} errorCallbackRange error callback range + * @returns {AMDRequireDependency} dependency + */ + newRequireDependency( + outerRange, + arrayRange, + functionRange, + errorCallbackRange + ) { + return new AMDRequireDependency( + outerRange, + arrayRange, + functionRange, + errorCallbackRange + ); + } + + /** + * @param {string} request request + * @param {Range=} range range + * @returns {AMDRequireItemDependency} AMDRequireItemDependency + */ + newRequireItemDependency(request, range) { + return new AMDRequireItemDependency(request, range); + } + + /** + * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array + * @param {Range} range range + * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency + */ + newRequireArrayDependency(depsArray, range) { + return new AMDRequireArrayDependency(depsArray, range); + } +} +module.exports = AMDRequireDependenciesBlockParserPlugin; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependency.js b/webpack-lib/lib/dependencies/AMDRequireDependency.js new file mode 100644 index 00000000000..930348fc948 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireDependency.js @@ -0,0 +1,189 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class AMDRequireDependency extends NullDependency { + /** + * @param {Range} outerRange outer range + * @param {Range} arrayRange array range + * @param {Range | null} functionRange function range + * @param {Range | null} errorCallbackRange error callback range + */ + constructor(outerRange, arrayRange, functionRange, errorCallbackRange) { + super(); + + this.outerRange = outerRange; + this.arrayRange = arrayRange; + this.functionRange = functionRange; + this.errorCallbackRange = errorCallbackRange; + this.functionBindThis = false; + this.errorCallbackBindThis = false; + } + + get category() { + return "amd"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.outerRange); + write(this.arrayRange); + write(this.functionRange); + write(this.errorCallbackRange); + write(this.functionBindThis); + write(this.errorCallbackBindThis); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.outerRange = read(); + this.arrayRange = read(); + this.functionRange = read(); + this.errorCallbackRange = read(); + this.functionBindThis = read(); + this.errorCallbackBindThis = read(); + + super.deserialize(context); + } +} + +makeSerializable( + AMDRequireDependency, + "webpack/lib/dependencies/AMDRequireDependency" +); + +AMDRequireDependency.Template = class AMDRequireDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {AMDRequireDependency} */ (dependency); + const depBlock = /** @type {AsyncDependenciesBlock} */ ( + moduleGraph.getParentBlock(dep) + ); + const promise = runtimeTemplate.blockPromise({ + chunkGraph, + block: depBlock, + message: "AMD require", + runtimeRequirements + }); + + // has array range but no function range + if (dep.arrayRange && !dep.functionRange) { + const startBlock = `${promise}.then(function() {`; + const endBlock = `;})['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; + runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); + + source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); + + source.replace(dep.arrayRange[1], dep.outerRange[1] - 1, endBlock); + + return; + } + + // has function range but no array range + if (dep.functionRange && !dep.arrayRange) { + const startBlock = `${promise}.then((`; + const endBlock = `).bind(exports, ${RuntimeGlobals.require}, exports, module))['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; + runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); + + source.replace(dep.outerRange[0], dep.functionRange[0] - 1, startBlock); + + source.replace(dep.functionRange[1], dep.outerRange[1] - 1, endBlock); + + return; + } + + // has array range, function range, and errorCallbackRange + if (dep.arrayRange && dep.functionRange && dep.errorCallbackRange) { + const startBlock = `${promise}.then(function() { `; + const errorRangeBlock = `}${ + dep.functionBindThis ? ".bind(this)" : "" + })['catch'](`; + const endBlock = `${dep.errorCallbackBindThis ? ".bind(this)" : ""})`; + + source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); + + source.insert(dep.arrayRange[0], "var __WEBPACK_AMD_REQUIRE_ARRAY__ = "); + + source.replace(dep.arrayRange[1], dep.functionRange[0] - 1, "; ("); + + source.insert( + dep.functionRange[1], + ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);" + ); + + source.replace( + dep.functionRange[1], + dep.errorCallbackRange[0] - 1, + errorRangeBlock + ); + + source.replace( + dep.errorCallbackRange[1], + dep.outerRange[1] - 1, + endBlock + ); + + return; + } + + // has array range, function range, but no errorCallbackRange + if (dep.arrayRange && dep.functionRange) { + const startBlock = `${promise}.then(function() { `; + const endBlock = `}${ + dep.functionBindThis ? ".bind(this)" : "" + })['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; + runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); + + source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); + + source.insert(dep.arrayRange[0], "var __WEBPACK_AMD_REQUIRE_ARRAY__ = "); + + source.replace(dep.arrayRange[1], dep.functionRange[0] - 1, "; ("); + + source.insert( + dep.functionRange[1], + ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);" + ); + + source.replace(dep.functionRange[1], dep.outerRange[1] - 1, endBlock); + } + } +}; + +module.exports = AMDRequireDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireItemDependency.js b/webpack-lib/lib/dependencies/AMDRequireItemDependency.js new file mode 100644 index 00000000000..614633ad324 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRequireItemDependency.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class AMDRequireItemDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range=} range location in source code + */ + constructor(request, range) { + super(request); + + this.range = range; + } + + get type() { + return "amd require"; + } + + get category() { + return "amd"; + } +} + +makeSerializable( + AMDRequireItemDependency, + "webpack/lib/dependencies/AMDRequireItemDependency" +); + +AMDRequireItemDependency.Template = ModuleDependencyTemplateAsRequireId; + +module.exports = AMDRequireItemDependency; diff --git a/webpack-lib/lib/dependencies/AMDRuntimeModules.js b/webpack-lib/lib/dependencies/AMDRuntimeModules.js new file mode 100644 index 00000000000..cec00bb8412 --- /dev/null +++ b/webpack-lib/lib/dependencies/AMDRuntimeModules.js @@ -0,0 +1,48 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +class AMDDefineRuntimeModule extends RuntimeModule { + constructor() { + super("amd define"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return Template.asString([ + `${RuntimeGlobals.amdDefine} = function () {`, + Template.indent("throw new Error('define cannot be used indirect');"), + "};" + ]); + } +} + +class AMDOptionsRuntimeModule extends RuntimeModule { + /** + * @param {Record} options the AMD options + */ + constructor(options) { + super("amd options"); + this.options = options; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return Template.asString([ + `${RuntimeGlobals.amdOptions} = ${JSON.stringify(this.options)};` + ]); + } +} + +module.exports.AMDDefineRuntimeModule = AMDDefineRuntimeModule; +module.exports.AMDOptionsRuntimeModule = AMDOptionsRuntimeModule; diff --git a/webpack-lib/lib/dependencies/CachedConstDependency.js b/webpack-lib/lib/dependencies/CachedConstDependency.js new file mode 100644 index 00000000000..913904abc94 --- /dev/null +++ b/webpack-lib/lib/dependencies/CachedConstDependency.js @@ -0,0 +1,128 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const DependencyTemplate = require("../DependencyTemplate"); +const InitFragment = require("../InitFragment"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class CachedConstDependency extends NullDependency { + /** + * @param {string} expression expression + * @param {Range} range range + * @param {string} identifier identifier + */ + constructor(expression, range, identifier) { + super(); + + this.expression = expression; + this.range = range; + this.identifier = identifier; + this._hashUpdate = undefined; + } + + /** + * @returns {string} hash update + */ + _createHashUpdate() { + return `${this.identifier}${this.range}${this.expression}`; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = this._createHashUpdate(); + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.expression); + write(this.range); + write(this.identifier); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.expression = read(); + this.range = read(); + this.identifier = read(); + + super.deserialize(context); + } +} + +makeSerializable( + CachedConstDependency, + "webpack/lib/dependencies/CachedConstDependency" +); + +CachedConstDependency.Template = class CachedConstDependencyTemplate extends ( + DependencyTemplate +) { + /** + * @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, + { runtimeTemplate, dependencyTemplates, initFragments } + ) { + const dep = /** @type {CachedConstDependency} */ (dependency); + + initFragments.push( + new InitFragment( + `var ${dep.identifier} = ${dep.expression};\n`, + InitFragment.STAGE_CONSTANTS, + 0, + `const ${dep.identifier}` + ) + ); + + if (typeof dep.range === "number") { + source.insert(dep.range, dep.identifier); + + return; + } + + source.replace(dep.range[0], dep.range[1] - 1, dep.identifier); + } +}; + +module.exports = CachedConstDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js b/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js new file mode 100644 index 00000000000..0cd457ee73a --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js @@ -0,0 +1,63 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ +/** @typedef {"exports" | "module.exports" | "this" | "Object.defineProperty(exports)" | "Object.defineProperty(module.exports)" | "Object.defineProperty(this)"} CommonJSDependencyBaseKeywords */ + +/** + * @param {CommonJSDependencyBaseKeywords} depBase commonjs dependency base + * @param {Module} module module + * @param {RuntimeRequirements} runtimeRequirements runtime requirements + * @returns {[string, string]} type and base + */ +module.exports.handleDependencyBase = ( + depBase, + module, + runtimeRequirements +) => { + let base; + let type; + switch (depBase) { + case "exports": + runtimeRequirements.add(RuntimeGlobals.exports); + base = module.exportsArgument; + type = "expression"; + break; + case "module.exports": + runtimeRequirements.add(RuntimeGlobals.module); + base = `${module.moduleArgument}.exports`; + type = "expression"; + break; + case "this": + runtimeRequirements.add(RuntimeGlobals.thisAsExports); + base = "this"; + type = "expression"; + break; + case "Object.defineProperty(exports)": + runtimeRequirements.add(RuntimeGlobals.exports); + base = module.exportsArgument; + type = "Object.defineProperty"; + break; + case "Object.defineProperty(module.exports)": + runtimeRequirements.add(RuntimeGlobals.module); + base = `${module.moduleArgument}.exports`; + type = "Object.defineProperty"; + break; + case "Object.defineProperty(this)": + runtimeRequirements.add(RuntimeGlobals.thisAsExports); + base = "this"; + type = "Object.defineProperty"; + break; + default: + throw new Error(`Unsupported base ${depBase}`); + } + + return [type, base]; +}; diff --git a/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js new file mode 100644 index 00000000000..d4f7a73c8fe --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js @@ -0,0 +1,404 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const { UsageState } = require("../ExportsInfo"); +const Template = require("../Template"); +const { equals } = require("../util/ArrayHelpers"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const { handleDependencyBase } = require("./CommonJsDependencyHelpers"); +const ModuleDependency = require("./ModuleDependency"); +const processExportInfo = require("./processExportInfo"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ExportsInfo")} ExportsInfo */ +/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ + +const idsSymbol = Symbol("CommonJsExportRequireDependency.ids"); + +const EMPTY_OBJECT = {}; + +class CommonJsExportRequireDependency extends ModuleDependency { + /** + * @param {Range} range range + * @param {Range | null} valueRange value range + * @param {CommonJSDependencyBaseKeywords} base base + * @param {string[]} names names + * @param {string} request request + * @param {string[]} ids ids + * @param {boolean} resultUsed true, when the result is used + */ + constructor(range, valueRange, base, names, request, ids, resultUsed) { + super(request); + this.range = range; + this.valueRange = valueRange; + this.base = base; + this.names = names; + this.ids = ids; + this.resultUsed = resultUsed; + this.asiSafe = undefined; + } + + get type() { + return "cjs export require"; + } + + /** + * @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 Dependency.TRANSITIVE; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {string[]} the imported id + */ + getIds(moduleGraph) { + return ( + /** @type {TODO} */ (moduleGraph.getMeta(this))[idsSymbol] || this.ids + ); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {string[]} ids the imported ids + * @returns {void} + */ + setIds(moduleGraph, ids) { + /** @type {TODO} */ (moduleGraph.getMeta(this))[idsSymbol] = ids; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + const ids = this.getIds(moduleGraph); + const getFullResult = () => { + if (ids.length === 0) { + return Dependency.EXPORTS_OBJECT_REFERENCED; + } + return [ + { + name: ids, + canMangle: false + } + ]; + }; + if (this.resultUsed) return getFullResult(); + /** @type {ExportsInfo | undefined} */ + let exportsInfo = moduleGraph.getExportsInfo( + /** @type {Module} */ (moduleGraph.getParentModule(this)) + ); + for (const name of this.names) { + const exportInfo = /** @type {ExportInfo} */ ( + exportsInfo.getReadOnlyExportInfo(name) + ); + const used = exportInfo.getUsed(runtime); + if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED; + if (used !== UsageState.OnlyPropertiesUsed) return getFullResult(); + exportsInfo = exportInfo.exportsInfo; + if (!exportsInfo) return getFullResult(); + } + if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) { + return getFullResult(); + } + /** @type {string[][]} */ + const referencedExports = []; + for (const exportInfo of exportsInfo.orderedExports) { + processExportInfo( + runtime, + referencedExports, + ids.concat(exportInfo.name), + exportInfo, + false + ); + } + return referencedExports.map(name => ({ + name, + canMangle: false + })); + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + if (this.names.length === 1) { + const ids = this.getIds(moduleGraph); + const name = this.names[0]; + const from = moduleGraph.getConnection(this); + if (!from) return; + return { + exports: [ + { + name, + from, + export: ids.length === 0 ? null : ids, + // we can't mangle names that are in an empty object + // because one could access the prototype property + // when export isn't set yet + canMangle: !(name in EMPTY_OBJECT) && false + } + ], + dependencies: [from.module] + }; + } else if (this.names.length > 0) { + const name = this.names[0]; + return { + exports: [ + { + name, + // we can't mangle names that are in an empty object + // because one could access the prototype property + // when export isn't set yet + canMangle: !(name in EMPTY_OBJECT) && false + } + ], + dependencies: undefined + }; + } + const from = moduleGraph.getConnection(this); + if (!from) return; + const reexportInfo = this.getStarReexports( + moduleGraph, + undefined, + from.module + ); + const ids = this.getIds(moduleGraph); + if (reexportInfo) { + return { + exports: Array.from( + /** @type {TODO} */ (reexportInfo).exports, + name => ({ + name, + from, + export: ids.concat(name), + canMangle: !(name in EMPTY_OBJECT) && false + }) + ), + // TODO handle deep reexports + dependencies: [from.module] + }; + } + return { + exports: true, + from: ids.length === 0 ? from : undefined, + canMangle: false, + dependencies: [from.module] + }; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {RuntimeSpec} runtime the runtime + * @param {Module} importedModule the imported module (optional) + * @returns {{exports?: Set, checked?: Set} | undefined} information + */ + getStarReexports( + moduleGraph, + runtime, + importedModule = /** @type {Module} */ (moduleGraph.getModule(this)) + ) { + /** @type {ExportsInfo | undefined} */ + let importedExportsInfo = moduleGraph.getExportsInfo(importedModule); + const ids = this.getIds(moduleGraph); + if (ids.length > 0) + importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids); + /** @type {ExportsInfo | undefined} */ + let exportsInfo = moduleGraph.getExportsInfo( + /** @type {Module} */ (moduleGraph.getParentModule(this)) + ); + if (this.names.length > 0) + exportsInfo = exportsInfo.getNestedExportsInfo(this.names); + + const noExtraExports = + importedExportsInfo && + importedExportsInfo.otherExportsInfo.provided === false; + const noExtraImports = + exportsInfo && + exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused; + + if (!noExtraExports && !noExtraImports) { + return; + } + + const isNamespaceImport = + importedModule.getExportsType(moduleGraph, false) === "namespace"; + + /** @type {Set} */ + const exports = new Set(); + /** @type {Set} */ + const checked = new Set(); + + if (noExtraImports) { + for (const exportInfo of /** @type {ExportsInfo} */ (exportsInfo) + .orderedExports) { + const name = exportInfo.name; + if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; + if (name === "__esModule" && isNamespaceImport) { + exports.add(name); + } else if (importedExportsInfo) { + const importedExportInfo = + importedExportsInfo.getReadOnlyExportInfo(name); + if (importedExportInfo.provided === false) continue; + exports.add(name); + if (importedExportInfo.provided === true) continue; + checked.add(name); + } else { + exports.add(name); + checked.add(name); + } + } + } else if (noExtraExports) { + for (const importedExportInfo of /** @type {ExportsInfo} */ ( + importedExportsInfo + ).orderedExports) { + const name = importedExportInfo.name; + if (importedExportInfo.provided === false) continue; + if (exportsInfo) { + const exportInfo = exportsInfo.getReadOnlyExportInfo(name); + if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; + } + exports.add(name); + if (importedExportInfo.provided === true) continue; + checked.add(name); + } + if (isNamespaceImport) { + exports.add("__esModule"); + checked.delete("__esModule"); + } + } + + return { exports, checked }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.asiSafe); + write(this.range); + write(this.valueRange); + write(this.base); + write(this.names); + write(this.ids); + write(this.resultUsed); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.asiSafe = read(); + this.range = read(); + this.valueRange = read(); + this.base = read(); + this.names = read(); + this.ids = read(); + this.resultUsed = read(); + super.deserialize(context); + } +} + +makeSerializable( + CommonJsExportRequireDependency, + "webpack/lib/dependencies/CommonJsExportRequireDependency" +); + +CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, + { + module, + runtimeTemplate, + chunkGraph, + moduleGraph, + runtimeRequirements, + runtime + } + ) { + const dep = /** @type {CommonJsExportRequireDependency} */ (dependency); + const used = moduleGraph + .getExportsInfo(module) + .getUsedName(dep.names, runtime); + + const [type, base] = handleDependencyBase( + dep.base, + module, + runtimeRequirements + ); + + const importedModule = moduleGraph.getModule(dep); + let requireExpr = runtimeTemplate.moduleExports({ + module: importedModule, + chunkGraph, + request: dep.request, + weak: dep.weak, + runtimeRequirements + }); + if (importedModule) { + const ids = dep.getIds(moduleGraph); + const usedImported = moduleGraph + .getExportsInfo(importedModule) + .getUsedName(ids, runtime); + if (usedImported) { + const comment = equals(usedImported, ids) + ? "" + : `${Template.toNormalComment(propertyAccess(ids))} `; + requireExpr += `${comment}${propertyAccess(usedImported)}`; + } + } + + switch (type) { + case "expression": + source.replace( + dep.range[0], + dep.range[1] - 1, + used + ? `${base}${propertyAccess(used)} = ${requireExpr}` + : `/* unused reexport */ ${requireExpr}` + ); + return; + case "Object.defineProperty": + throw new Error("TODO"); + default: + throw new Error("Unexpected type"); + } + } +}; + +module.exports = CommonJsExportRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsExportsDependency.js b/webpack-lib/lib/dependencies/CommonJsExportsDependency.js new file mode 100644 index 00000000000..93c831b5dfd --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsExportsDependency.js @@ -0,0 +1,183 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const InitFragment = require("../InitFragment"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const { handleDependencyBase } = require("./CommonJsDependencyHelpers"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ + +const EMPTY_OBJECT = {}; + +class CommonJsExportsDependency extends NullDependency { + /** + * @param {Range} range range + * @param {Range | null} valueRange value range + * @param {CommonJSDependencyBaseKeywords} base base + * @param {string[]} names names + */ + constructor(range, valueRange, base, names) { + super(); + this.range = range; + this.valueRange = valueRange; + this.base = base; + this.names = names; + } + + get type() { + return "cjs exports"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + const name = this.names[0]; + return { + exports: [ + { + name, + // we can't mangle names that are in an empty object + // because one could access the prototype property + // when export isn't set yet + canMangle: !(name in EMPTY_OBJECT) + } + ], + dependencies: undefined + }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.valueRange); + write(this.base); + write(this.names); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.valueRange = read(); + this.base = read(); + this.names = read(); + super.deserialize(context); + } +} + +makeSerializable( + CommonJsExportsDependency, + "webpack/lib/dependencies/CommonJsExportsDependency" +); + +CommonJsExportsDependency.Template = class CommonJsExportsDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { module, moduleGraph, initFragments, runtimeRequirements, runtime } + ) { + const dep = /** @type {CommonJsExportsDependency} */ (dependency); + const used = moduleGraph + .getExportsInfo(module) + .getUsedName(dep.names, runtime); + + const [type, base] = handleDependencyBase( + dep.base, + module, + runtimeRequirements + ); + + switch (type) { + case "expression": + if (!used) { + initFragments.push( + new InitFragment( + "var __webpack_unused_export__;\n", + InitFragment.STAGE_CONSTANTS, + 0, + "__webpack_unused_export__" + ) + ); + source.replace( + dep.range[0], + dep.range[1] - 1, + "__webpack_unused_export__" + ); + return; + } + source.replace( + dep.range[0], + dep.range[1] - 1, + `${base}${propertyAccess(used)}` + ); + return; + case "Object.defineProperty": + if (!used) { + initFragments.push( + new InitFragment( + "var __webpack_unused_export__;\n", + InitFragment.STAGE_CONSTANTS, + 0, + "__webpack_unused_export__" + ) + ); + source.replace( + dep.range[0], + /** @type {Range} */ (dep.valueRange)[0] - 1, + "__webpack_unused_export__ = (" + ); + source.replace( + /** @type {Range} */ (dep.valueRange)[1], + dep.range[1] - 1, + ")" + ); + return; + } + source.replace( + dep.range[0], + /** @type {Range} */ (dep.valueRange)[0] - 1, + `Object.defineProperty(${base}${propertyAccess( + used.slice(0, -1) + )}, ${JSON.stringify(used[used.length - 1])}, (` + ); + source.replace( + /** @type {Range} */ (dep.valueRange)[1], + dep.range[1] - 1, + "))" + ); + } + } +}; + +module.exports = CommonJsExportsDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js b/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js new file mode 100644 index 00000000000..a37e0521288 --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js @@ -0,0 +1,411 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const formatLocation = require("../formatLocation"); +const { evaluateToString } = require("../javascript/JavascriptParserHelpers"); +const propertyAccess = require("../util/propertyAccess"); +const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency"); +const CommonJsExportsDependency = require("./CommonJsExportsDependency"); +const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency"); +const DynamicExports = require("./DynamicExports"); +const HarmonyExports = require("./HarmonyExports"); +const ModuleDecoratorDependency = require("./ModuleDecoratorDependency"); + +/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").Super} Super */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../javascript/JavascriptParser").StatementPath} StatementPath */ +/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ + +/** + * This function takes a generic expression and detects whether it is an ObjectExpression. + * This is used in the context of parsing CommonJS exports to get the value of the property descriptor + * when the `exports` object is assigned to `Object.defineProperty`. + * + * In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore + * webpack has to detect this case and get the value key of the property descriptor. See the following example + * for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57 + * + * This would be an example of a CommonJS module that exports an object with a property descriptor: + * ```js + * Object.defineProperty(exports, "__esModule", { value: true }); + * exports.foo = void 0; + * exports.foo = "bar"; + * ``` + * @param {TODO} expr expression + * @returns {Expression | undefined} returns the value of property descriptor + */ +const getValueOfPropertyDescription = expr => { + if (expr.type !== "ObjectExpression") return; + for (const property of expr.properties) { + if (property.computed) continue; + const key = property.key; + if (key.type !== "Identifier" || key.name !== "value") continue; + return property.value; + } +}; + +/** + * The purpose of this function is to check whether an expression is a truthy literal or not. This is + * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy + * values like `null` and `false`. However, exports should only be created if the exported value is truthy. + * @param {Expression} expr expression being checked + * @returns {boolean} true, when the expression is a truthy literal + */ +const isTruthyLiteral = expr => { + switch (expr.type) { + case "Literal": + return Boolean(expr.value); + case "UnaryExpression": + if (expr.operator === "!") return isFalsyLiteral(expr.argument); + } + return false; +}; + +/** + * The purpose of this function is to check whether an expression is a falsy literal or not. This is + * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy + * values like `null` and `false`. However, exports should only be created if the exported value is truthy. + * @param {Expression} expr expression being checked + * @returns {boolean} true, when the expression is a falsy literal + */ +const isFalsyLiteral = expr => { + switch (expr.type) { + case "Literal": + return !expr.value; + case "UnaryExpression": + if (expr.operator === "!") return isTruthyLiteral(expr.argument); + } + return false; +}; + +/** + * @param {JavascriptParser} parser the parser + * @param {Expression} expr expression + * @returns {{ argument: BasicEvaluatedExpression, ids: string[] } | undefined} parsed call + */ +const parseRequireCall = (parser, expr) => { + const ids = []; + while (expr.type === "MemberExpression") { + if (expr.object.type === "Super") return; + if (!expr.property) return; + const prop = expr.property; + if (expr.computed) { + if (prop.type !== "Literal") return; + ids.push(`${prop.value}`); + } else { + if (prop.type !== "Identifier") return; + ids.push(prop.name); + } + expr = expr.object; + } + if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return; + const callee = expr.callee; + if ( + callee.type !== "Identifier" || + parser.getVariableInfo(callee.name) !== "require" + ) { + return; + } + const arg = expr.arguments[0]; + if (arg.type === "SpreadElement") return; + const argValue = parser.evaluateExpression(arg); + return { argument: argValue, ids: ids.reverse() }; +}; + +class CommonJsExportsParserPlugin { + /** + * @param {ModuleGraph} moduleGraph module graph + */ + constructor(moduleGraph) { + this.moduleGraph = moduleGraph; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const enableStructuredExports = () => { + DynamicExports.enable(parser.state); + }; + + /** + * @param {boolean} topLevel true, when the export is on top level + * @param {string[]} members members of the export + * @param {Expression | undefined} valueExpr expression for the value + * @returns {void} + */ + const checkNamespace = (topLevel, members, valueExpr) => { + if (!DynamicExports.isEnabled(parser.state)) return; + if (members.length > 0 && members[0] === "__esModule") { + if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) { + DynamicExports.setFlagged(parser.state); + } else { + DynamicExports.setDynamic(parser.state); + } + } + }; + /** + * @param {string=} reason reason + */ + const bailout = reason => { + DynamicExports.bailout(parser.state); + if (reason) bailoutHint(reason); + }; + /** + * @param {string} reason reason + */ + const bailoutHint = reason => { + this.moduleGraph + .getOptimizationBailout(parser.state.module) + .push(`CommonJS bailout: ${reason}`); + }; + + // metadata // + parser.hooks.evaluateTypeof + .for("module") + .tap("CommonJsExportsParserPlugin", evaluateToString("object")); + parser.hooks.evaluateTypeof + .for("exports") + .tap("CommonJsPlugin", evaluateToString("object")); + + // exporting // + + /** + * @param {AssignmentExpression} expr expression + * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords + * @param {string[]} members members of the export + * @returns {boolean | undefined} true, when the expression was handled + */ + const handleAssignExport = (expr, base, members) => { + if (HarmonyExports.isEnabled(parser.state)) return; + // Handle reexporting + const requireCall = parseRequireCall(parser, expr.right); + if ( + requireCall && + requireCall.argument.isString() && + (members.length === 0 || members[0] !== "__esModule") + ) { + enableStructuredExports(); + // It's possible to reexport __esModule, so we must convert to a dynamic module + if (members.length === 0) DynamicExports.setDynamic(parser.state); + const dep = new CommonJsExportRequireDependency( + /** @type {Range} */ (expr.range), + null, + base, + members, + /** @type {string} */ (requireCall.argument.string), + requireCall.ids, + !parser.isStatementLevelExpression(expr) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.module.addDependency(dep); + return true; + } + if (members.length === 0) return; + enableStructuredExports(); + const remainingMembers = members; + checkNamespace( + /** @type {StatementPath} */ + (parser.statementPath).length === 1 && + parser.isStatementLevelExpression(expr), + remainingMembers, + expr.right + ); + const dep = new CommonJsExportsDependency( + /** @type {Range} */ (expr.left.range), + null, + base, + remainingMembers + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + parser.walkExpression(expr.right); + return true; + }; + parser.hooks.assignMemberChain + .for("exports") + .tap("CommonJsExportsParserPlugin", (expr, members) => + handleAssignExport(expr, "exports", members) + ); + parser.hooks.assignMemberChain + .for("this") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (!parser.scope.topLevelScope) return; + return handleAssignExport(expr, "this", members); + }); + parser.hooks.assignMemberChain + .for("module") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (members[0] !== "exports") return; + return handleAssignExport(expr, "module.exports", members.slice(1)); + }); + parser.hooks.call + .for("Object.defineProperty") + .tap("CommonJsExportsParserPlugin", expression => { + const expr = /** @type {CallExpression} */ (expression); + if (!parser.isStatementLevelExpression(expr)) return; + if (expr.arguments.length !== 3) return; + if (expr.arguments[0].type === "SpreadElement") return; + if (expr.arguments[1].type === "SpreadElement") return; + if (expr.arguments[2].type === "SpreadElement") return; + const exportsArg = parser.evaluateExpression(expr.arguments[0]); + if (!exportsArg.isIdentifier()) return; + if ( + exportsArg.identifier !== "exports" && + exportsArg.identifier !== "module.exports" && + (exportsArg.identifier !== "this" || !parser.scope.topLevelScope) + ) { + return; + } + const propertyArg = parser.evaluateExpression(expr.arguments[1]); + const property = propertyArg.asString(); + if (typeof property !== "string") return; + enableStructuredExports(); + const descArg = expr.arguments[2]; + checkNamespace( + /** @type {StatementPath} */ + (parser.statementPath).length === 1, + [property], + getValueOfPropertyDescription(descArg) + ); + const dep = new CommonJsExportsDependency( + /** @type {Range} */ (expr.range), + /** @type {Range} */ (expr.arguments[2].range), + `Object.defineProperty(${exportsArg.identifier})`, + [property] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + + parser.walkExpression(expr.arguments[2]); + return true; + }); + + // Self reference // + + /** + * @param {Expression | Super} expr expression + * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords + * @param {string[]} members members of the export + * @param {CallExpression=} call call expression + * @returns {boolean | void} true, when the expression was handled + */ + const handleAccessExport = (expr, base, members, call) => { + if (HarmonyExports.isEnabled(parser.state)) return; + if (members.length === 0) { + bailout( + `${base} is used directly at ${formatLocation( + /** @type {DependencyLocation} */ (expr.loc) + )}` + ); + } + if (call && members.length === 1) { + bailoutHint( + `${base}${propertyAccess( + members + )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation( + /** @type {DependencyLocation} */ (expr.loc) + )}` + ); + } + const dep = new CommonJsSelfReferenceDependency( + /** @type {Range} */ (expr.range), + base, + members, + Boolean(call) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + if (call) { + parser.walkExpressions(call.arguments); + } + return true; + }; + parser.hooks.callMemberChain + .for("exports") + .tap("CommonJsExportsParserPlugin", (expr, members) => + handleAccessExport(expr.callee, "exports", members, expr) + ); + parser.hooks.expressionMemberChain + .for("exports") + .tap("CommonJsExportsParserPlugin", (expr, members) => + handleAccessExport(expr, "exports", members) + ); + parser.hooks.expression + .for("exports") + .tap("CommonJsExportsParserPlugin", expr => + handleAccessExport(expr, "exports", []) + ); + parser.hooks.callMemberChain + .for("module") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (members[0] !== "exports") return; + return handleAccessExport( + expr.callee, + "module.exports", + members.slice(1), + expr + ); + }); + parser.hooks.expressionMemberChain + .for("module") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (members[0] !== "exports") return; + return handleAccessExport(expr, "module.exports", members.slice(1)); + }); + parser.hooks.expression + .for("module.exports") + .tap("CommonJsExportsParserPlugin", expr => + handleAccessExport(expr, "module.exports", []) + ); + parser.hooks.callMemberChain + .for("this") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (!parser.scope.topLevelScope) return; + return handleAccessExport(expr.callee, "this", members, expr); + }); + parser.hooks.expressionMemberChain + .for("this") + .tap("CommonJsExportsParserPlugin", (expr, members) => { + if (!parser.scope.topLevelScope) return; + return handleAccessExport(expr, "this", members); + }); + parser.hooks.expression + .for("this") + .tap("CommonJsExportsParserPlugin", expr => { + if (!parser.scope.topLevelScope) return; + return handleAccessExport(expr, "this", []); + }); + + // Bailouts // + parser.hooks.expression.for("module").tap("CommonJsPlugin", expr => { + bailout(); + const isHarmony = HarmonyExports.isEnabled(parser.state); + const dep = new ModuleDecoratorDependency( + isHarmony + ? RuntimeGlobals.harmonyModuleDecorator + : RuntimeGlobals.nodeModuleDecorator, + !isHarmony + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + return true; + }); + } +} +module.exports = CommonJsExportsParserPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js new file mode 100644 index 00000000000..1164eee150e --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js @@ -0,0 +1,166 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Template = require("../Template"); +const { equals } = require("../util/ArrayHelpers"); +const { getTrimmedIdsAndRange } = require("../util/chainedImports"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class CommonJsFullRequireDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + * @param {string[]} names accessed properties on module + * @param {Range[]=} idRanges ranges for members of ids; the two arrays are right-aligned + */ + constructor( + request, + range, + names, + idRanges /* TODO webpack 6 make this non-optional. It must always be set to properly trim ids. */ + ) { + super(request); + this.range = range; + this.names = names; + this.idRanges = idRanges; + this.call = false; + this.asiSafe = undefined; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + if (this.call) { + const importedModule = moduleGraph.getModule(this); + if ( + !importedModule || + importedModule.getExportsType(moduleGraph, false) !== "namespace" + ) { + return [this.names.slice(0, -1)]; + } + } + return [this.names]; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.names); + write(this.idRanges); + write(this.call); + write(this.asiSafe); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.names = read(); + this.idRanges = read(); + this.call = read(); + this.asiSafe = read(); + super.deserialize(context); + } + + get type() { + return "cjs full require"; + } + + get category() { + return "commonjs"; + } +} + +CommonJsFullRequireDependency.Template = class CommonJsFullRequireDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, + { + module, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtimeRequirements, + runtime, + initFragments + } + ) { + const dep = /** @type {CommonJsFullRequireDependency} */ (dependency); + if (!dep.range) return; + const importedModule = moduleGraph.getModule(dep); + let requireExpr = runtimeTemplate.moduleExports({ + module: importedModule, + chunkGraph, + request: dep.request, + weak: dep.weak, + runtimeRequirements + }); + + const { + trimmedRange: [trimmedRangeStart, trimmedRangeEnd], + trimmedIds + } = getTrimmedIdsAndRange( + dep.names, + dep.range, + dep.idRanges, + moduleGraph, + dep + ); + + if (importedModule) { + const usedImported = moduleGraph + .getExportsInfo(importedModule) + .getUsedName(trimmedIds, runtime); + if (usedImported) { + const comment = equals(usedImported, trimmedIds) + ? "" + : `${Template.toNormalComment(propertyAccess(trimmedIds))} `; + const access = `${comment}${propertyAccess(usedImported)}`; + requireExpr = + dep.asiSafe === true + ? `(${requireExpr}${access})` + : `${requireExpr}${access}`; + } + } + source.replace(trimmedRangeStart, trimmedRangeEnd - 1, requireExpr); + } +}; + +makeSerializable( + CommonJsFullRequireDependency, + "webpack/lib/dependencies/CommonJsFullRequireDependency" +); + +module.exports = CommonJsFullRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js b/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js new file mode 100644 index 00000000000..15e87b90817 --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js @@ -0,0 +1,783 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { fileURLToPath } = require("url"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const WebpackError = require("../WebpackError"); +const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); +const { + evaluateToIdentifier, + evaluateToString, + expressionIsUnsupported, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); +const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); +const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); +const CommonJsRequireDependency = require("./CommonJsRequireDependency"); +const ConstDependency = require("./ConstDependency"); +const ContextDependencyHelpers = require("./ContextDependencyHelpers"); +const LocalModuleDependency = require("./LocalModuleDependency"); +const { getLocalModule } = require("./LocalModulesHelpers"); +const RequireHeaderDependency = require("./RequireHeaderDependency"); +const RequireResolveContextDependency = require("./RequireResolveContextDependency"); +const RequireResolveDependency = require("./RequireResolveDependency"); +const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").NewExpression} NewExpression */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const createRequireSpecifierTag = Symbol("createRequire"); +const createdRequireIdentifierTag = Symbol("createRequire()"); + +class CommonJsImportsParserPlugin { + /** + * @param {JavascriptParserOptions} options parser options + */ + constructor(options) { + this.options = options; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const options = this.options; + + const getContext = () => { + if (parser.currentTagData) { + const { context } = parser.currentTagData; + return context; + } + }; + + // #region metadata + /** + * @param {string} expression expression + * @param {() => string[]} getMembers get members + */ + const tapRequireExpression = (expression, getMembers) => { + parser.hooks.typeof + .for(expression) + .tap( + "CommonJsImportsParserPlugin", + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for(expression) + .tap("CommonJsImportsParserPlugin", evaluateToString("function")); + parser.hooks.evaluateIdentifier + .for(expression) + .tap( + "CommonJsImportsParserPlugin", + evaluateToIdentifier(expression, "require", getMembers, true) + ); + }; + /** + * @param {string | symbol} tag tag + */ + const tapRequireExpressionTag = tag => { + parser.hooks.typeof + .for(tag) + .tap( + "CommonJsImportsParserPlugin", + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for(tag) + .tap("CommonJsImportsParserPlugin", evaluateToString("function")); + }; + tapRequireExpression("require", () => []); + tapRequireExpression("require.resolve", () => ["resolve"]); + tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]); + // #endregion + + // Weird stuff // + parser.hooks.assign + .for("require") + .tap("CommonJsImportsParserPlugin", expr => { + // to not leak to global "require", we need to define a local require here. + const dep = new ConstDependency("var require;", 0); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + // #region Unsupported + parser.hooks.expression + .for("require.main") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "require.main is not supported by webpack." + ) + ); + parser.hooks.call + .for("require.main.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "require.main.require is not supported by webpack." + ) + ); + parser.hooks.expression + .for("module.parent.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "module.parent.require is not supported by webpack." + ) + ); + parser.hooks.call + .for("module.parent.require") + .tap( + "CommonJsImportsParserPlugin", + expressionIsUnsupported( + parser, + "module.parent.require is not supported by webpack." + ) + ); + // #endregion + + // #region Renaming + /** + * @param {Expression} expr expression + * @returns {boolean} true when set undefined + */ + const defineUndefined = expr => { + // To avoid "not defined" error, replace the value with undefined + const dep = new ConstDependency( + "undefined", + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return false; + }; + parser.hooks.canRename + .for("require") + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.rename + .for("require") + .tap("CommonJsImportsParserPlugin", defineUndefined); + // #endregion + + // #region Inspection + const requireCache = toConstantDependency( + parser, + RuntimeGlobals.moduleCache, + [ + RuntimeGlobals.moduleCache, + RuntimeGlobals.moduleId, + RuntimeGlobals.moduleLoaded + ] + ); + + parser.hooks.expression + .for("require.cache") + .tap("CommonJsImportsParserPlugin", requireCache); + // #endregion + + // #region Require as expression + /** + * @param {Expression} expr expression + * @returns {boolean} true when handled + */ + const requireAsExpressionHandler = expr => { + const dep = new CommonJsRequireContextDependency( + { + request: options.unknownContextRequest, + recursive: options.unknownContextRecursive, + regExp: options.unknownContextRegExp, + mode: "sync" + }, + /** @type {Range} */ (expr.range), + undefined, + parser.scope.inShorthand, + getContext() + ); + dep.critical = + options.unknownContextCritical && + "require function is used in a way in which dependencies cannot be statically extracted"; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + }; + parser.hooks.expression + .for("require") + .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); + // #endregion + + // #region Require + /** + * @param {CallExpression | NewExpression} expr expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | void} true when handled + */ + const processRequireItem = (expr, param) => { + if (param.isString()) { + const dep = new CommonJsRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (param.range), + getContext() + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression | NewExpression} expr expression + * @param {BasicEvaluatedExpression} param param + * @returns {boolean | void} true when handled + */ + const processRequireContext = (expr, param) => { + const dep = ContextDependencyHelpers.create( + CommonJsRequireContextDependency, + /** @type {Range} */ (expr.range), + param, + expr, + options, + { + category: "commonjs" + }, + parser, + undefined, + getContext() + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + }; + /** + * @param {boolean} callNew true, when require is called with new + * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler + */ + const createRequireHandler = callNew => expr => { + if (options.commonjsMagicComments) { + const { options: requireOptions, errors: commentErrors } = + parser.parseCommentOptions(/** @type {Range} */ (expr.range)); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + /** @type {DependencyLocation} */ (comment.loc) + ) + ); + } + } + if (requireOptions && requireOptions.webpackIgnore !== undefined) { + if (typeof requireOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else if (requireOptions.webpackIgnore) { + // Do not instrument `require()` if `webpackIgnore` is `true` + return true; + } + } + } + + if (expr.arguments.length !== 1) return; + let localModule; + const param = parser.evaluateExpression(expr.arguments[0]); + if (param.isConditional()) { + let isExpression = false; + for (const p of /** @type {BasicEvaluatedExpression[]} */ ( + param.options + )) { + const result = processRequireItem(expr, p); + if (result === undefined) { + isExpression = true; + } + } + if (!isExpression) { + const dep = new RequireHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + } + if ( + param.isString() && + (localModule = getLocalModule( + parser.state, + /** @type {string} */ (param.string) + )) + ) { + localModule.flagUsed(); + const dep = new LocalModuleDependency( + localModule, + /** @type {Range} */ (expr.range), + callNew + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } else { + const result = processRequireItem(expr, param); + if (result === undefined) { + processRequireContext(expr, param); + } else { + const dep = new RequireHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } + } + return true; + }; + parser.hooks.call + .for("require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + parser.hooks.new + .for("require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); + parser.hooks.call + .for("module.require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + parser.hooks.new + .for("module.require") + .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); + // #endregion + + // #region Require with property access + /** + * @param {Expression} expr expression + * @param {string[]} calleeMembers callee members + * @param {CallExpression} callExpr call expression + * @param {string[]} members members + * @param {Range[]} memberRanges member ranges + * @returns {boolean | void} true when handled + */ + const chainHandler = ( + expr, + calleeMembers, + callExpr, + members, + memberRanges + ) => { + if (callExpr.arguments.length !== 1) return; + const param = parser.evaluateExpression(callExpr.arguments[0]); + if ( + param.isString() && + !getLocalModule(parser.state, /** @type {string} */ (param.string)) + ) { + const dep = new CommonJsFullRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range), + members, + /** @type {Range[]} */ memberRanges + ); + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.optional = Boolean(parser.scope.inTry); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression} expr expression + * @param {string[]} calleeMembers callee members + * @param {CallExpression} callExpr call expression + * @param {string[]} members members + * @param {Range[]} memberRanges member ranges + * @returns {boolean | void} true when handled + */ + const callChainHandler = ( + expr, + calleeMembers, + callExpr, + members, + memberRanges + ) => { + if (callExpr.arguments.length !== 1) return; + const param = parser.evaluateExpression(callExpr.arguments[0]); + if ( + param.isString() && + !getLocalModule(parser.state, /** @type {string} */ (param.string)) + ) { + const dep = new CommonJsFullRequireDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.callee.range), + members, + /** @type {Range[]} */ memberRanges + ); + dep.call = true; + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.optional = Boolean(parser.scope.inTry); + dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc); + parser.state.current.addDependency(dep); + parser.walkExpressions(expr.arguments); + return true; + } + }; + parser.hooks.memberChainOfCallMemberChain + .for("require") + .tap("CommonJsImportsParserPlugin", chainHandler); + parser.hooks.memberChainOfCallMemberChain + .for("module.require") + .tap("CommonJsImportsParserPlugin", chainHandler); + parser.hooks.callMemberChainOfCallMemberChain + .for("require") + .tap("CommonJsImportsParserPlugin", callChainHandler); + parser.hooks.callMemberChainOfCallMemberChain + .for("module.require") + .tap("CommonJsImportsParserPlugin", callChainHandler); + // #endregion + + // #region Require.resolve + /** + * @param {CallExpression} expr call expression + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolve = (expr, weak) => { + if (expr.arguments.length !== 1) return; + const param = parser.evaluateExpression(expr.arguments[0]); + if (param.isConditional()) { + for (const option of /** @type {BasicEvaluatedExpression[]} */ ( + param.options + )) { + const result = processResolveItem(expr, option, weak); + if (result === undefined) { + processResolveContext(expr, option, weak); + } + } + const dep = new RequireResolveHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + const result = processResolveItem(expr, param, weak); + if (result === undefined) { + processResolveContext(expr, param, weak); + } + const dep = new RequireResolveHeaderDependency( + /** @type {Range} */ (expr.callee.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }; + /** + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolveItem = (expr, param, weak) => { + if (param.isString()) { + const dep = new RequireResolveDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (param.range), + getContext() + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + dep.weak = weak; + parser.state.current.addDependency(dep); + return true; + } + }; + /** + * @param {CallExpression} expr call expression + * @param {BasicEvaluatedExpression} param param + * @param {boolean} weak weak + * @returns {boolean | void} true when handled + */ + const processResolveContext = (expr, param, weak) => { + const dep = ContextDependencyHelpers.create( + RequireResolveContextDependency, + /** @type {Range} */ (param.range), + param, + expr, + options, + { + category: "commonjs", + mode: weak ? "weak" : "sync" + }, + parser, + getContext() + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + }; + + parser.hooks.call + .for("require.resolve") + .tap("CommonJsImportsParserPlugin", expr => processResolve(expr, false)); + parser.hooks.call + .for("require.resolveWeak") + .tap("CommonJsImportsParserPlugin", expr => processResolve(expr, true)); + // #endregion + + // #region Create require + + if (!options.createRequire) return; + + /** @type {ImportSource[]} */ + let moduleName = []; + /** @type {string | undefined} */ + let specifierName; + + if (options.createRequire === true) { + moduleName = ["module", "node:module"]; + specifierName = "createRequire"; + } else { + let moduleName; + const match = /^(.*) from (.*)$/.exec(options.createRequire); + if (match) { + [, specifierName, moduleName] = match; + } + if (!specifierName || !moduleName) { + const err = new WebpackError( + `Parsing javascript parser option "createRequire" failed, got ${JSON.stringify( + options.createRequire + )}` + ); + err.details = + 'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module'; + throw err; + } + } + + tapRequireExpressionTag(createdRequireIdentifierTag); + tapRequireExpressionTag(createRequireSpecifierTag); + parser.hooks.evaluateCallExpression + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", expr => { + const context = parseCreateRequireArguments(expr); + if (context === undefined) return; + const ident = parser.evaluatedVariable({ + tag: createdRequireIdentifierTag, + data: { context }, + next: undefined + }); + + return new BasicEvaluatedExpression() + .setIdentifier(ident, ident, () => []) + .setSideEffects(false) + .setRange(/** @type {Range} */ (expr.range)); + }); + parser.hooks.unhandledExpressionMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => + expressionIsUnsupported( + parser, + `createRequire().${members.join(".")} is not supported by webpack.` + )(expr) + ); + parser.hooks.canRename + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.canRename + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", () => true); + parser.hooks.rename + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", defineUndefined); + parser.hooks.expression + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); + parser.hooks.call + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); + /** + * @param {CallExpression} expr call expression + * @returns {string | void} context + */ + const parseCreateRequireArguments = expr => { + const args = expr.arguments; + if (args.length !== 1) { + const err = new WebpackError( + "module.createRequire supports only one argument." + ); + err.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addWarning(err); + return; + } + const arg = args[0]; + const evaluated = parser.evaluateExpression(arg); + if (!evaluated.isString()) { + const err = new WebpackError( + "module.createRequire failed parsing argument." + ); + err.loc = /** @type {DependencyLocation} */ (arg.loc); + parser.state.module.addWarning(err); + return; + } + const ctx = /** @type {string} */ (evaluated.string).startsWith("file://") + ? fileURLToPath(/** @type {string} */ (evaluated.string)) + : /** @type {string} */ (evaluated.string); + // argument always should be a filename + return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\")); + }; + + parser.hooks.import.tap( + { + name: "CommonJsImportsParserPlugin", + stage: -10 + }, + (statement, source) => { + if ( + !moduleName.includes(source) || + statement.specifiers.length !== 1 || + statement.specifiers[0].type !== "ImportSpecifier" || + statement.specifiers[0].imported.type !== "Identifier" || + statement.specifiers[0].imported.name !== specifierName + ) + return; + // clear for 'import { createRequire as x } from "module"' + // if any other specifier was used import module + const clearDep = new ConstDependency( + parser.isAsiPosition(/** @type {Range} */ (statement.range)[0]) + ? ";" + : "", + /** @type {Range} */ (statement.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); + parser.state.module.addPresentationalDependency(clearDep); + parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]); + return true; + } + ); + parser.hooks.importSpecifier.tap( + { + name: "CommonJsImportsParserPlugin", + stage: -10 + }, + (statement, source, id, name) => { + if (!moduleName.includes(source) || id !== specifierName) return; + parser.tagVariable(name, createRequireSpecifierTag); + return true; + } + ); + parser.hooks.preDeclarator.tap( + "CommonJsImportsParserPlugin", + declarator => { + if ( + declarator.id.type !== "Identifier" || + !declarator.init || + declarator.init.type !== "CallExpression" || + declarator.init.callee.type !== "Identifier" + ) + return; + const variableInfo = + /** @type {TODO} */ + (parser.getVariableInfo(declarator.init.callee.name)); + if ( + variableInfo && + variableInfo.tagInfo && + variableInfo.tagInfo.tag === createRequireSpecifierTag + ) { + const context = parseCreateRequireArguments(declarator.init); + if (context === undefined) return; + parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, { + name: declarator.id.name, + context + }); + return true; + } + } + ); + + parser.hooks.memberChainOfCallMemberChain + .for(createRequireSpecifierTag) + .tap( + "CommonJsImportsParserPlugin", + (expr, calleeMembers, callExpr, members) => { + if ( + calleeMembers.length !== 0 || + members.length !== 1 || + members[0] !== "cache" + ) + return; + // createRequire().cache + const context = parseCreateRequireArguments(callExpr); + if (context === undefined) return; + return requireCache(expr); + } + ); + parser.hooks.callMemberChainOfCallMemberChain + .for(createRequireSpecifierTag) + .tap( + "CommonJsImportsParserPlugin", + (expr, calleeMembers, innerCallExpression, members) => { + if ( + calleeMembers.length !== 0 || + members.length !== 1 || + members[0] !== "resolve" + ) + return; + // createRequire().resolve() + return processResolve(expr, false); + } + ); + parser.hooks.expressionMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => { + // require.cache + if (members.length === 1 && members[0] === "cache") { + return requireCache(expr); + } + }); + parser.hooks.callMemberChain + .for(createdRequireIdentifierTag) + .tap("CommonJsImportsParserPlugin", (expr, members) => { + // require.resolve() + if (members.length === 1 && members[0] === "resolve") { + return processResolve(expr, false); + } + }); + parser.hooks.call + .for(createRequireSpecifierTag) + .tap("CommonJsImportsParserPlugin", expr => { + const clearDep = new ConstDependency( + "/* createRequire() */ undefined", + /** @type {Range} */ (expr.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(clearDep); + return true; + }); + // #endregion + } +} +module.exports = CommonJsImportsParserPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsPlugin.js b/webpack-lib/lib/dependencies/CommonJsPlugin.js new file mode 100644 index 00000000000..b148b17b0b9 --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsPlugin.js @@ -0,0 +1,305 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const SelfModuleFactory = require("../SelfModuleFactory"); +const Template = require("../Template"); +const CommonJsExportsDependency = require("./CommonJsExportsDependency"); +const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); +const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); +const CommonJsRequireDependency = require("./CommonJsRequireDependency"); +const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency"); +const ModuleDecoratorDependency = require("./ModuleDecoratorDependency"); +const RequireHeaderDependency = require("./RequireHeaderDependency"); +const RequireResolveContextDependency = require("./RequireResolveContextDependency"); +const RequireResolveDependency = require("./RequireResolveDependency"); +const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency"); +const RuntimeRequirementsDependency = require("./RuntimeRequirementsDependency"); + +const CommonJsExportsParserPlugin = require("./CommonJsExportsParserPlugin"); +const CommonJsImportsParserPlugin = require("./CommonJsImportsParserPlugin"); + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC +} = require("../ModuleTypeConstants"); +const { + evaluateToIdentifier, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); +const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "CommonJsPlugin"; + +class CommonJsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyFactories.set( + CommonJsRequireDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CommonJsRequireDependency, + new CommonJsRequireDependency.Template() + ); + + compilation.dependencyFactories.set( + CommonJsFullRequireDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CommonJsFullRequireDependency, + new CommonJsFullRequireDependency.Template() + ); + + compilation.dependencyFactories.set( + CommonJsRequireContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + CommonJsRequireContextDependency, + new CommonJsRequireContextDependency.Template() + ); + + compilation.dependencyFactories.set( + RequireResolveDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + RequireResolveDependency, + new RequireResolveDependency.Template() + ); + + compilation.dependencyFactories.set( + RequireResolveContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + RequireResolveContextDependency, + new RequireResolveContextDependency.Template() + ); + + compilation.dependencyTemplates.set( + RequireResolveHeaderDependency, + new RequireResolveHeaderDependency.Template() + ); + + compilation.dependencyTemplates.set( + RequireHeaderDependency, + new RequireHeaderDependency.Template() + ); + + compilation.dependencyTemplates.set( + CommonJsExportsDependency, + new CommonJsExportsDependency.Template() + ); + + compilation.dependencyFactories.set( + CommonJsExportRequireDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + CommonJsExportRequireDependency, + new CommonJsExportRequireDependency.Template() + ); + + const selfFactory = new SelfModuleFactory(compilation.moduleGraph); + + compilation.dependencyFactories.set( + CommonJsSelfReferenceDependency, + selfFactory + ); + compilation.dependencyTemplates.set( + CommonJsSelfReferenceDependency, + new CommonJsSelfReferenceDependency.Template() + ); + + compilation.dependencyFactories.set( + ModuleDecoratorDependency, + selfFactory + ); + compilation.dependencyTemplates.set( + ModuleDecoratorDependency, + new ModuleDecoratorDependency.Template() + ); + + compilation.hooks.runtimeRequirementInModule + .for(RuntimeGlobals.harmonyModuleDecorator) + .tap(PLUGIN_NAME, (module, set) => { + set.add(RuntimeGlobals.module); + set.add(RuntimeGlobals.requireScope); + }); + + compilation.hooks.runtimeRequirementInModule + .for(RuntimeGlobals.nodeModuleDecorator) + .tap(PLUGIN_NAME, (module, set) => { + set.add(RuntimeGlobals.module); + set.add(RuntimeGlobals.requireScope); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.harmonyModuleDecorator) + .tap(PLUGIN_NAME, (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new HarmonyModuleDecoratorRuntimeModule() + ); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.nodeModuleDecorator) + .tap(PLUGIN_NAME, (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new NodeModuleDecoratorRuntimeModule() + ); + }); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.commonjs !== undefined && !parserOptions.commonjs) + return; + parser.hooks.typeof + .for("module") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("object")) + ); + + parser.hooks.expression + .for("require.main") + .tap( + PLUGIN_NAME, + toConstantDependency( + parser, + `${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}]`, + [RuntimeGlobals.moduleCache, RuntimeGlobals.entryModuleId] + ) + ); + parser.hooks.expression + .for(RuntimeGlobals.moduleLoaded) + .tap(PLUGIN_NAME, expr => { + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).moduleConcatenationBailout = + RuntimeGlobals.moduleLoaded; + const dep = new RuntimeRequirementsDependency([ + RuntimeGlobals.moduleLoaded + ]); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + parser.hooks.expression + .for(RuntimeGlobals.moduleId) + .tap(PLUGIN_NAME, expr => { + /** @type {BuildInfo} */ + (parser.state.module.buildInfo).moduleConcatenationBailout = + RuntimeGlobals.moduleId; + const dep = new RuntimeRequirementsDependency([ + RuntimeGlobals.moduleId + ]); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + parser.hooks.evaluateIdentifier.for("module.hot").tap( + PLUGIN_NAME, + evaluateToIdentifier("module.hot", "module", () => ["hot"], null) + ); + + new CommonJsImportsParserPlugin(parserOptions).apply(parser); + new CommonJsExportsParserPlugin(compilation.moduleGraph).apply( + parser + ); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + } + ); + } +} + +class HarmonyModuleDecoratorRuntimeModule extends RuntimeModule { + constructor() { + super("harmony module decorator"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { runtimeTemplate } = /** @type {Compilation} */ (this.compilation); + return Template.asString([ + `${ + RuntimeGlobals.harmonyModuleDecorator + } = ${runtimeTemplate.basicFunction("module", [ + "module = Object.create(module);", + "if (!module.children) module.children = [];", + "Object.defineProperty(module, 'exports', {", + Template.indent([ + "enumerable: true,", + `set: ${runtimeTemplate.basicFunction("", [ + "throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id);" + ])}` + ]), + "});", + "return module;" + ])};` + ]); + } +} + +class NodeModuleDecoratorRuntimeModule extends RuntimeModule { + constructor() { + super("node module decorator"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { runtimeTemplate } = /** @type {Compilation} */ (this.compilation); + return Template.asString([ + `${RuntimeGlobals.nodeModuleDecorator} = ${runtimeTemplate.basicFunction( + "module", + [ + "module.paths = [];", + "if (!module.children) module.children = [];", + "return module;" + ] + )};` + ]); + } +} + +module.exports = CommonJsPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js b/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js new file mode 100644 index 00000000000..14e64285d0d --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js @@ -0,0 +1,72 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class CommonJsRequireContextDependency extends ContextDependency { + /** + * @param {TODO} options options for the context module + * @param {Range} range location in source code + * @param {Range | undefined} valueRange location of the require call + * @param {boolean | string } inShorthand true or name + * @param {string} context context + */ + constructor(options, range, valueRange, inShorthand, context) { + super(options, context); + + this.range = range; + this.valueRange = valueRange; + // inShorthand must be serialized by subclasses that use it + this.inShorthand = inShorthand; + } + + get type() { + return "cjs require context"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.range); + write(this.valueRange); + write(this.inShorthand); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.range = read(); + this.valueRange = read(); + this.inShorthand = read(); + + super.deserialize(context); + } +} + +makeSerializable( + CommonJsRequireContextDependency, + "webpack/lib/dependencies/CommonJsRequireContextDependency" +); + +CommonJsRequireContextDependency.Template = + ContextDependencyTemplateAsRequireCall; + +module.exports = CommonJsRequireContextDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsRequireDependency.js new file mode 100644 index 00000000000..09545a86e5e --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsRequireDependency.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class CommonJsRequireDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {Range=} range location in source code + * @param {string=} context request context + */ + constructor(request, range, context) { + super(request); + this.range = range; + this._context = context; + } + + get type() { + return "cjs require"; + } + + get category() { + return "commonjs"; + } +} + +CommonJsRequireDependency.Template = ModuleDependencyTemplateAsId; + +makeSerializable( + CommonJsRequireDependency, + "webpack/lib/dependencies/CommonJsRequireDependency" +); + +module.exports = CommonJsRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js b/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js new file mode 100644 index 00000000000..b1b368ead67 --- /dev/null +++ b/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js @@ -0,0 +1,155 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const { equals } = require("../util/ArrayHelpers"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ + +class CommonJsSelfReferenceDependency extends NullDependency { + /** + * @param {Range} range range + * @param {CommonJSDependencyBaseKeywords} base base + * @param {string[]} names names + * @param {boolean} call is a call + */ + constructor(range, base, names, call) { + super(); + this.range = range; + this.base = base; + this.names = names; + this.call = call; + } + + get type() { + return "cjs self exports reference"; + } + + get category() { + return "self"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return "self"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return [this.call ? this.names.slice(0, -1) : this.names]; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.base); + write(this.names); + write(this.call); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.base = read(); + this.names = read(); + this.call = read(); + super.deserialize(context); + } +} + +makeSerializable( + CommonJsSelfReferenceDependency, + "webpack/lib/dependencies/CommonJsSelfReferenceDependency" +); + +CommonJsSelfReferenceDependency.Template = class CommonJsSelfReferenceDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { module, moduleGraph, runtime, runtimeRequirements } + ) { + const dep = /** @type {CommonJsSelfReferenceDependency} */ (dependency); + const used = + dep.names.length === 0 + ? dep.names + : moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime); + if (!used) { + throw new Error( + "Self-reference dependency has unused export name: This should not happen" + ); + } + + let base; + switch (dep.base) { + case "exports": + runtimeRequirements.add(RuntimeGlobals.exports); + base = module.exportsArgument; + break; + case "module.exports": + runtimeRequirements.add(RuntimeGlobals.module); + base = `${module.moduleArgument}.exports`; + break; + case "this": + runtimeRequirements.add(RuntimeGlobals.thisAsExports); + base = "this"; + break; + default: + throw new Error(`Unsupported base ${dep.base}`); + } + + if (base === dep.base && equals(used, dep.names)) { + // Nothing has to be changed + // We don't use a replacement for compat reasons + // for plugins that update `module._source` which they + // shouldn't do! + return; + } + + source.replace( + dep.range[0], + dep.range[1] - 1, + `${base}${propertyAccess(used)}` + ); + } +}; + +module.exports = CommonJsSelfReferenceDependency; diff --git a/webpack-lib/lib/dependencies/ConstDependency.js b/webpack-lib/lib/dependencies/ConstDependency.js new file mode 100644 index 00000000000..e41acef3acc --- /dev/null +++ b/webpack-lib/lib/dependencies/ConstDependency.js @@ -0,0 +1,117 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class ConstDependency extends NullDependency { + /** + * @param {string} expression the expression + * @param {number | Range} range the source range + * @param {(string[] | null)=} runtimeRequirements runtime requirements + */ + constructor(expression, range, runtimeRequirements) { + super(); + this.expression = expression; + this.range = range; + this.runtimeRequirements = runtimeRequirements + ? new Set(runtimeRequirements) + : null; + this._hashUpdate = undefined; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + let hashUpdate = `${this.range}|${this.expression}`; + if (this.runtimeRequirements) { + for (const item of this.runtimeRequirements) { + hashUpdate += "|"; + hashUpdate += item; + } + } + this._hashUpdate = hashUpdate; + } + hash.update(this._hashUpdate); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return false; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.expression); + write(this.range); + write(this.runtimeRequirements); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.expression = read(); + this.range = read(); + this.runtimeRequirements = read(); + super.deserialize(context); + } +} + +makeSerializable(ConstDependency, "webpack/lib/dependencies/ConstDependency"); + +ConstDependency.Template = class ConstDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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 dep = /** @type {ConstDependency} */ (dependency); + if (dep.runtimeRequirements) { + for (const req of dep.runtimeRequirements) { + templateContext.runtimeRequirements.add(req); + } + } + if (typeof dep.range === "number") { + source.insert(dep.range, dep.expression); + return; + } + + source.replace(dep.range[0], dep.range[1] - 1, dep.expression); + } +}; + +module.exports = ConstDependency; diff --git a/webpack-lib/lib/dependencies/ContextDependency.js b/webpack-lib/lib/dependencies/ContextDependency.js new file mode 100644 index 00000000000..e1d94b5ece7 --- /dev/null +++ b/webpack-lib/lib/dependencies/ContextDependency.js @@ -0,0 +1,178 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const DependencyTemplate = require("../DependencyTemplate"); +const makeSerializable = require("../util/makeSerializable"); +const memoize = require("../util/memoize"); + +/** @typedef {import("../ContextModule").ContextOptions} ContextOptions */ +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +const getCriticalDependencyWarning = memoize(() => + require("./CriticalDependencyWarning") +); + +/** @typedef {ContextOptions & { request: string }} ContextDependencyOptions */ + +/** + * @param {RegExp | null | undefined} r regexp + * @returns {string} stringified regexp + */ +const regExpToString = r => (r ? String(r) : ""); + +class ContextDependency extends Dependency { + /** + * @param {ContextDependencyOptions} options options for the context module + * @param {string=} context request context + */ + constructor(options, context) { + super(); + + this.options = options; + this.userRequest = this.options && this.options.request; + /** @type {false | undefined | string} */ + this.critical = false; + this.hadGlobalOrStickyRegExp = false; + + if ( + this.options && + (this.options.regExp.global || this.options.regExp.sticky) + ) { + this.options = { ...this.options, regExp: null }; + this.hadGlobalOrStickyRegExp = true; + } + + this.request = undefined; + this.range = undefined; + this.valueRange = undefined; + /** @type {boolean | string | undefined} */ + this.inShorthand = undefined; + // TODO refactor this + this.replaces = undefined; + this._requestContext = context; + } + + /** + * @returns {string | undefined} a request context + */ + getContext() { + return this._requestContext; + } + + get category() { + return "commonjs"; + } + + /** + * @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 true; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return ( + `context${this._requestContext || ""}|ctx request${ + this.options.request + } ${this.options.recursive} ` + + `${regExpToString(this.options.regExp)} ${regExpToString( + this.options.include + )} ${regExpToString(this.options.exclude)} ` + + `${this.options.mode} ${this.options.chunkName} ` + + `${JSON.stringify(this.options.groupOptions)}` + + `${ + this.options.referencedExports + ? ` ${JSON.stringify(this.options.referencedExports)}` + : "" + }` + ); + } + + /** + * Returns warnings + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} warnings + */ + getWarnings(moduleGraph) { + let warnings = super.getWarnings(moduleGraph); + + if (this.critical) { + if (!warnings) warnings = []; + const CriticalDependencyWarning = getCriticalDependencyWarning(); + warnings.push(new CriticalDependencyWarning(this.critical)); + } + + if (this.hadGlobalOrStickyRegExp) { + if (!warnings) warnings = []; + const CriticalDependencyWarning = getCriticalDependencyWarning(); + warnings.push( + new CriticalDependencyWarning( + "Contexts can't use RegExps with the 'g' or 'y' flags." + ) + ); + } + + return warnings; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.options); + write(this.userRequest); + write(this.critical); + write(this.hadGlobalOrStickyRegExp); + write(this.request); + write(this._requestContext); + write(this.range); + write(this.valueRange); + write(this.prepend); + write(this.replaces); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.options = read(); + this.userRequest = read(); + this.critical = read(); + this.hadGlobalOrStickyRegExp = read(); + this.request = read(); + this._requestContext = read(); + this.range = read(); + this.valueRange = read(); + this.prepend = read(); + this.replaces = read(); + + super.deserialize(context); + } +} + +makeSerializable( + ContextDependency, + "webpack/lib/dependencies/ContextDependency" +); + +ContextDependency.Template = DependencyTemplate; + +module.exports = ContextDependency; diff --git a/webpack-lib/lib/dependencies/ContextDependencyHelpers.js b/webpack-lib/lib/dependencies/ContextDependencyHelpers.js new file mode 100644 index 00000000000..ed635328202 --- /dev/null +++ b/webpack-lib/lib/dependencies/ContextDependencyHelpers.js @@ -0,0 +1,262 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { parseResource } = require("../util/identifier"); + +/** @typedef {import("estree").Node} EsTreeNode */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("./ContextDependency")} ContextDependency */ +/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ + +/** + * Escapes regular expression metacharacters + * @param {string} str String to quote + * @returns {string} Escaped string + */ +const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); + +/** + * @param {string} prefix prefix + * @returns {{prefix: string, context: string}} result + */ +const splitContextFromPrefix = prefix => { + const idx = prefix.lastIndexOf("/"); + let context = "."; + if (idx >= 0) { + context = prefix.slice(0, idx); + prefix = `.${prefix.slice(idx)}`; + } + return { + context, + prefix + }; +}; + +/** @typedef {Partial>} PartialContextDependencyOptions */ +/** @typedef {{ new(options: ContextDependencyOptions, range: Range, valueRange: [number, number], ...args: any[]): ContextDependency }} ContextDependencyConstructor */ + +/** + * @param {ContextDependencyConstructor} Dep the Dependency class + * @param {Range} range source range + * @param {BasicEvaluatedExpression} param context param + * @param {EsTreeNode} expr expr + * @param {Pick} options options for context creation + * @param {PartialContextDependencyOptions} contextOptions options for the ContextModule + * @param {JavascriptParser} parser the parser + * @param {...any} depArgs depArgs + * @returns {ContextDependency} the created Dependency + */ +module.exports.create = ( + Dep, + range, + param, + expr, + options, + contextOptions, + parser, + ...depArgs +) => { + if (param.isTemplateString()) { + const quasis = /** @type {BasicEvaluatedExpression[]} */ (param.quasis); + const prefixRaw = /** @type {string} */ (quasis[0].string); + const postfixRaw = + /** @type {string} */ + (quasis.length > 1 ? quasis[quasis.length - 1].string : ""); + + const valueRange = /** @type {Range} */ (param.range); + const { context, prefix } = splitContextFromPrefix(prefixRaw); + const { + path: postfix, + query, + fragment + } = parseResource(postfixRaw, parser); + + // When there are more than two quasis, the generated RegExp can be more precise + // We join the quasis with the expression regexp + const innerQuasis = quasis.slice(1, -1); + const innerRegExp = + /** @type {RegExp} */ (options.wrappedContextRegExp).source + + innerQuasis + .map( + q => + quoteMeta(/** @type {string} */ (q.string)) + + /** @type {RegExp} */ (options.wrappedContextRegExp).source + ) + .join(""); + + // Example: `./context/pre${e}inner${e}inner2${e}post?query#frag` + // context: "./context" + // prefix: "./pre" + // innerQuasis: [BEE("inner"), BEE("inner2")] + // (BEE = BasicEvaluatedExpression) + // postfix: "post" + // query: "?query" + // fragment: "#frag" + // regExp: /^\.\/pre.*inner.*inner2.*post$/ + const regExp = new RegExp( + `^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$` + ); + const dep = new Dep( + { + request: context + query + fragment, + recursive: /** @type {boolean} */ (options.wrappedContextRecursive), + regExp, + mode: "sync", + ...contextOptions + }, + range, + valueRange, + ...depArgs + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + + /** @type {{ value: string, range: Range }[]} */ + const replaces = []; + const parts = /** @type {BasicEvaluatedExpression[]} */ (param.parts); + + for (const [i, part] of parts.entries()) { + if (i % 2 === 0) { + // Quasis or merged quasi + let range = /** @type {Range} */ (part.range); + let value = /** @type {string} */ (part.string); + if (param.templateStringKind === "cooked") { + value = JSON.stringify(value); + value = value.slice(1, -1); + } + if (i === 0) { + // prefix + value = prefix; + range = [ + /** @type {Range} */ (param.range)[0], + /** @type {Range} */ (part.range)[1] + ]; + value = + (param.templateStringKind === "cooked" ? "`" : "String.raw`") + + value; + } else if (i === parts.length - 1) { + // postfix + value = postfix; + range = [ + /** @type {Range} */ (part.range)[0], + /** @type {Range} */ (param.range)[1] + ]; + value = `${value}\``; + } else if ( + part.expression && + part.expression.type === "TemplateElement" && + part.expression.value.raw === value + ) { + // Shortcut when it's a single quasi and doesn't need to be replaced + continue; + } + replaces.push({ + range, + value + }); + } else { + // Expression + parser.walkExpression(part.expression); + } + } + + dep.replaces = replaces; + dep.critical = + options.wrappedContextCritical && + "a part of the request of a dependency is an expression"; + return dep; + } else if ( + param.isWrapped() && + ((param.prefix && param.prefix.isString()) || + (param.postfix && param.postfix.isString())) + ) { + const prefixRaw = + /** @type {string} */ + (param.prefix && param.prefix.isString() ? param.prefix.string : ""); + const postfixRaw = + /** @type {string} */ + (param.postfix && param.postfix.isString() ? param.postfix.string : ""); + const prefixRange = + param.prefix && param.prefix.isString() ? param.prefix.range : null; + const postfixRange = + param.postfix && param.postfix.isString() ? param.postfix.range : null; + const valueRange = /** @type {Range} */ (param.range); + const { context, prefix } = splitContextFromPrefix(prefixRaw); + const { + path: postfix, + query, + fragment + } = parseResource(postfixRaw, parser); + const regExp = new RegExp( + `^${quoteMeta(prefix)}${ + /** @type {RegExp} */ (options.wrappedContextRegExp).source + }${quoteMeta(postfix)}$` + ); + const dep = new Dep( + { + request: context + query + fragment, + recursive: /** @type {boolean} */ (options.wrappedContextRecursive), + regExp, + mode: "sync", + ...contextOptions + }, + range, + valueRange, + ...depArgs + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + const replaces = []; + if (prefixRange) { + replaces.push({ + range: prefixRange, + value: JSON.stringify(prefix) + }); + } + if (postfixRange) { + replaces.push({ + range: postfixRange, + value: JSON.stringify(postfix) + }); + } + dep.replaces = replaces; + dep.critical = + options.wrappedContextCritical && + "a part of the request of a dependency is an expression"; + + if (parser && param.wrappedInnerExpressions) { + for (const part of param.wrappedInnerExpressions) { + if (part.expression) parser.walkExpression(part.expression); + } + } + + return dep; + } + const dep = new Dep( + { + request: /** @type {string} */ (options.exprContextRequest), + recursive: /** @type {boolean} */ (options.exprContextRecursive), + regExp: /** @type {RegExp} */ (options.exprContextRegExp), + mode: "sync", + ...contextOptions + }, + range, + /** @type {Range} */ (param.range), + ...depArgs + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.critical = + options.exprContextCritical && + "the request of a dependency is an expression"; + + parser.walkExpression(param.expression); + + return dep; +}; diff --git a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js new file mode 100644 index 00000000000..b0eb8b2c318 --- /dev/null +++ b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js @@ -0,0 +1,62 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ContextDependency = require("./ContextDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ + +class ContextDependencyTemplateAsId extends ContextDependency.Template { + /** + * @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, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ContextDependency} */ (dependency); + const module = moduleGraph.getModule(dep); + const moduleExports = runtimeTemplate.moduleExports({ + module, + chunkGraph, + request: dep.request, + weak: dep.weak, + runtimeRequirements + }); + + if (module) { + if (dep.valueRange) { + if (Array.isArray(dep.replaces)) { + for (let i = 0; i < dep.replaces.length; i++) { + const rep = dep.replaces[i]; + source.replace(rep.range[0], rep.range[1] - 1, rep.value); + } + } + source.replace(dep.valueRange[1], dep.range[1] - 1, ")"); + source.replace( + dep.range[0], + dep.valueRange[0] - 1, + `${moduleExports}.resolve(` + ); + } else { + source.replace( + dep.range[0], + dep.range[1] - 1, + `${moduleExports}.resolve` + ); + } + } else { + source.replace(dep.range[0], dep.range[1] - 1, moduleExports); + } + } +} +module.exports = ContextDependencyTemplateAsId; diff --git a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js new file mode 100644 index 00000000000..8907f9f55d8 --- /dev/null +++ b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js @@ -0,0 +1,59 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ContextDependency = require("./ContextDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ + +class ContextDependencyTemplateAsRequireCall extends ContextDependency.Template { + /** + * @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, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ContextDependency} */ (dependency); + let moduleExports = runtimeTemplate.moduleExports({ + module: moduleGraph.getModule(dep), + chunkGraph, + request: dep.request, + runtimeRequirements + }); + + if (dep.inShorthand) { + moduleExports = `${dep.inShorthand}: ${moduleExports}`; + } + if (moduleGraph.getModule(dep)) { + if (dep.valueRange) { + if (Array.isArray(dep.replaces)) { + for (let i = 0; i < dep.replaces.length; i++) { + const rep = dep.replaces[i]; + source.replace(rep.range[0], rep.range[1] - 1, rep.value); + } + } + source.replace(dep.valueRange[1], dep.range[1] - 1, ")"); + source.replace( + dep.range[0], + dep.valueRange[0] - 1, + `${moduleExports}(` + ); + } else { + source.replace(dep.range[0], dep.range[1] - 1, moduleExports); + } + } else { + source.replace(dep.range[0], dep.range[1] - 1, moduleExports); + } + } +} +module.exports = ContextDependencyTemplateAsRequireCall; diff --git a/webpack-lib/lib/dependencies/ContextElementDependency.js b/webpack-lib/lib/dependencies/ContextElementDependency.js new file mode 100644 index 00000000000..43d06edded6 --- /dev/null +++ b/webpack-lib/lib/dependencies/ContextElementDependency.js @@ -0,0 +1,135 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("../ContextModule")} ContextModule */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class ContextElementDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {string|undefined} userRequest user request + * @param {string | undefined} typePrefix type prefix + * @param {string} category category + * @param {(string[][] | null)=} referencedExports referenced exports + * @param {string=} context context + * @param {ImportAttributes=} attributes import assertions + */ + constructor( + request, + userRequest, + typePrefix, + category, + referencedExports, + context, + attributes + ) { + super(request); + this.referencedExports = referencedExports; + this._typePrefix = typePrefix; + this._category = category; + this._context = context || undefined; + + if (userRequest) { + this.userRequest = userRequest; + } + + this.assertions = attributes; + } + + get type() { + if (this._typePrefix) { + return `${this._typePrefix} context element`; + } + + return "context element"; + } + + get category() { + return this._category; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + if (!this.referencedExports) return Dependency.EXPORTS_OBJECT_REFERENCED; + const refs = []; + for (const referencedExport of this.referencedExports) { + if ( + this._typePrefix === "import()" && + referencedExport[0] === "default" + ) { + const selfModule = + /** @type {ContextModule} */ + (moduleGraph.getParentModule(this)); + const importedModule = + /** @type {Module} */ + (moduleGraph.getModule(this)); + const exportsType = importedModule.getExportsType( + moduleGraph, + selfModule.options.namespaceObject === "strict" + ); + if ( + exportsType === "default-only" || + exportsType === "default-with-named" + ) { + return Dependency.EXPORTS_OBJECT_REFERENCED; + } + } + refs.push({ + name: referencedExport, + canMangle: false + }); + } + return refs; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this._typePrefix); + write(this._category); + write(this.referencedExports); + write(this.assertions); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this._typePrefix = read(); + this._category = read(); + this.referencedExports = read(); + this.assertions = read(); + super.deserialize(context); + } +} + +makeSerializable( + ContextElementDependency, + "webpack/lib/dependencies/ContextElementDependency" +); + +module.exports = ContextElementDependency; diff --git a/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js b/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js new file mode 100644 index 00000000000..9abb5c50cf4 --- /dev/null +++ b/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js @@ -0,0 +1,75 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class CreateScriptUrlDependency extends NullDependency { + /** + * @param {Range} range range + */ + constructor(range) { + super(); + this.range = range; + } + + get type() { + return "create script url"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + super.deserialize(context); + } +} + +CreateScriptUrlDependency.Template = class CreateScriptUrlDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { runtimeRequirements }) { + const dep = /** @type {CreateScriptUrlDependency} */ (dependency); + + runtimeRequirements.add(RuntimeGlobals.createScriptUrl); + + source.insert(dep.range[0], `${RuntimeGlobals.createScriptUrl}(`); + source.insert(dep.range[1], ")"); + } +}; + +makeSerializable( + CreateScriptUrlDependency, + "webpack/lib/dependencies/CreateScriptUrlDependency" +); + +module.exports = CreateScriptUrlDependency; diff --git a/webpack-lib/lib/dependencies/CriticalDependencyWarning.js b/webpack-lib/lib/dependencies/CriticalDependencyWarning.js new file mode 100644 index 00000000000..3299150bd97 --- /dev/null +++ b/webpack-lib/lib/dependencies/CriticalDependencyWarning.js @@ -0,0 +1,28 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); +const makeSerializable = require("../util/makeSerializable"); + +class CriticalDependencyWarning extends WebpackError { + /** + * @param {string} message message + */ + constructor(message) { + super(); + + this.name = "CriticalDependencyWarning"; + this.message = `Critical dependency: ${message}`; + } +} + +makeSerializable( + CriticalDependencyWarning, + "webpack/lib/dependencies/CriticalDependencyWarning" +); + +module.exports = CriticalDependencyWarning; diff --git a/webpack-lib/lib/dependencies/CssIcssExportDependency.js b/webpack-lib/lib/dependencies/CssIcssExportDependency.js new file mode 100644 index 00000000000..1b43d897577 --- /dev/null +++ b/webpack-lib/lib/dependencies/CssIcssExportDependency.js @@ -0,0 +1,156 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { cssExportConvention } = require("../util/conventions"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ +/** @typedef {import("../CssModule")} CssModule */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../css/CssGenerator")} CssGenerator */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class CssIcssExportDependency extends NullDependency { + /** + * @param {string} name name + * @param {string} value value + */ + constructor(name, value) { + super(); + this.name = name; + this.value = value; + this._hashUpdate = undefined; + } + + get type() { + return "css :export"; + } + + /** + * @param {string} name export name + * @param {CssGeneratorExportsConvention} convention convention of the export name + * @returns {string[]} convention results + */ + getExportsConventionNames(name, convention) { + if (this._conventionNames) { + return this._conventionNames; + } + this._conventionNames = cssExportConvention(name, convention); + return this._conventionNames; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this)); + const convention = + /** @type {CssGenerator} */ + (module.generator).convention; + const names = this.getExportsConventionNames(this.name, convention); + return { + exports: names.map(name => ({ + name, + canMangle: true + })), + dependencies: undefined + }; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, { chunkGraph }) { + if (this._hashUpdate === undefined) { + const module = + /** @type {CssModule} */ + (chunkGraph.moduleGraph.getParentModule(this)); + const generator = + /** @type {CssGenerator} */ + (module.generator); + const names = this.getExportsConventionNames( + this.name, + generator.convention + ); + this._hashUpdate = JSON.stringify(names); + } + hash.update("exportsConvention"); + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.name); + write(this.value); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.name = read(); + this.value = read(); + super.deserialize(context); + } +} + +CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { cssData, module: m, runtime, moduleGraph }) { + const dep = /** @type {CssIcssExportDependency} */ (dependency); + const module = /** @type {CssModule} */ (m); + const convention = + /** @type {CssGenerator} */ + (module.generator).convention; + const names = dep.getExportsConventionNames(dep.name, convention); + const usedNames = + /** @type {string[]} */ + ( + names + .map(name => + moduleGraph.getExportInfo(module, name).getUsedName(name, runtime) + ) + .filter(Boolean) + ); + + for (const used of usedNames.concat(names)) { + cssData.exports.set(used, dep.value); + } + } +}; + +makeSerializable( + CssIcssExportDependency, + "webpack/lib/dependencies/CssIcssExportDependency" +); + +module.exports = CssIcssExportDependency; diff --git a/webpack-lib/lib/dependencies/CssIcssImportDependency.js b/webpack-lib/lib/dependencies/CssIcssImportDependency.js new file mode 100644 index 00000000000..4206b6484c7 --- /dev/null +++ b/webpack-lib/lib/dependencies/CssIcssImportDependency.js @@ -0,0 +1,118 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const CssIcssExportDependency = require("./CssIcssExportDependency"); +const CssLocalIdentifierDependency = require("./CssLocalIdentifierDependency"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class CssIcssImportDependency extends ModuleDependency { + /** + * Example of dependency: + * + *:import('./style.css') { IMPORTED_NAME: v-primary } + * @param {string} request request request path which needs resolving + * @param {string} exportName export name + * @param {[number, number]} range the range of dependency + */ + constructor(request, exportName, range) { + super(request); + this.range = range; + this.exportName = exportName; + } + + get type() { + return "css :import"; + } + + get category() { + return "css-import"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.exportName); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.exportName = read(); + super.deserialize(context); + } +} + +CssIcssImportDependency.Template = class CssIcssImportDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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 dep = /** @type {CssIcssImportDependency} */ (dependency); + const { range } = dep; + const module = templateContext.moduleGraph.getModule(dep); + let value; + + for (const item of module.dependencies) { + if ( + item instanceof CssLocalIdentifierDependency && + dep.exportName === item.name + ) { + value = CssLocalIdentifierDependency.Template.getIdentifier( + item, + dep.exportName, + { + ...templateContext, + module + } + ); + break; + } else if ( + item instanceof CssIcssExportDependency && + dep.exportName === item.name + ) { + value = item.value; + break; + } + } + + if (!value) { + throw new Error( + `Imported '${dep.exportName}' name from '${dep.request}' not found` + ); + } + + source.replace(range[0], range[1], value); + } +}; + +makeSerializable( + CssIcssImportDependency, + "webpack/lib/dependencies/CssIcssImportDependency" +); + +module.exports = CssIcssImportDependency; diff --git a/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js b/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js new file mode 100644 index 00000000000..298e5d1cdc5 --- /dev/null +++ b/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js @@ -0,0 +1,132 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../css/CssParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class CssIcssSymbolDependency extends NullDependency { + /** + * @param {string} name name + * @param {string} value value + * @param {Range} range range + */ + constructor(name, value, range) { + super(); + this.name = name; + this.value = value; + this.range = range; + this._hashUpdate = undefined; + } + + get type() { + return "css @value identifier"; + } + + get category() { + return "self"; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = `${this.range}${this.name}${this.value}`; + } + hash.update(this._hashUpdate); + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + return { + exports: [ + { + name: this.name, + canMangle: true + } + ], + dependencies: undefined + }; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return [[this.name]]; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.name); + write(this.value); + write(this.range); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.name = read(); + this.value = read(); + this.range = read(); + super.deserialize(context); + } +} + +CssIcssSymbolDependency.Template = class CssValueAtRuleDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { cssData }) { + const dep = /** @type {CssIcssSymbolDependency} */ (dependency); + + source.replace(dep.range[0], dep.range[1] - 1, dep.value); + + cssData.exports.set(dep.name, dep.value); + } +}; + +makeSerializable( + CssIcssSymbolDependency, + "webpack/lib/dependencies/CssIcssSymbolDependency" +); + +module.exports = CssIcssSymbolDependency; diff --git a/webpack-lib/lib/dependencies/CssImportDependency.js b/webpack-lib/lib/dependencies/CssImportDependency.js new file mode 100644 index 00000000000..b6a0772d2ba --- /dev/null +++ b/webpack-lib/lib/dependencies/CssImportDependency.js @@ -0,0 +1,117 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../css/CssParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class CssImportDependency extends ModuleDependency { + /** + * Example of dependency: + * \@import url("landscape.css") layer(forms) screen and (orientation: landscape) screen and (orientation: landscape); + * @param {string} request request + * @param {Range} range range of the argument + * @param {string | undefined} layer layer + * @param {string | undefined} supports list of supports conditions + * @param {string | undefined} media list of media conditions + */ + constructor(request, range, layer, supports, media) { + super(request); + this.range = range; + this.layer = layer; + this.supports = supports; + this.media = media; + } + + get type() { + return "css @import"; + } + + get category() { + return "css-import"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + let str = `context${this._context || ""}|module${this.request}`; + + if (this.layer) { + str += `|layer${this.layer}`; + } + + if (this.supports) { + str += `|supports${this.supports}`; + } + + if (this.media) { + str += `|media${this.media}`; + } + + return str; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.layer); + write(this.supports); + write(this.media); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.layer = read(); + this.supports = read(); + this.media = read(); + super.deserialize(context); + } +} + +CssImportDependency.Template = class CssImportDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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 dep = /** @type {CssImportDependency} */ (dependency); + + source.replace(dep.range[0], dep.range[1] - 1, ""); + } +}; + +makeSerializable( + CssImportDependency, + "webpack/lib/dependencies/CssImportDependency" +); + +module.exports = CssImportDependency; diff --git a/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js b/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js new file mode 100644 index 00000000000..7f8ddbf3bef --- /dev/null +++ b/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js @@ -0,0 +1,250 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { cssExportConvention } = require("../util/conventions"); +const createHash = require("../util/createHash"); +const { makePathsRelative } = require("../util/identifier"); +const makeSerializable = require("../util/makeSerializable"); +const memoize = require("../util/memoize"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CssModule")} CssModule */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../css/CssGenerator")} CssGenerator */ +/** @typedef {import("../css/CssParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/createHash").Algorithm} Algorithm */ + +const getCssParser = memoize(() => require("../css/CssParser")); + +/** + * @param {string} local css local + * @param {CssModule} module module + * @param {ChunkGraph} chunkGraph chunk graph + * @param {RuntimeTemplate} runtimeTemplate runtime template + * @returns {string} local ident + */ +const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => { + const localIdentName = + /** @type {CssGenerator} */ + (module.generator).localIdentName; + const relativeResourcePath = makePathsRelative( + /** @type {string} */ + (module.context), + module.matchResource || module.resource, + runtimeTemplate.compilation.compiler.root + ); + const { hashFunction, hashDigest, hashDigestLength, hashSalt, uniqueName } = + runtimeTemplate.outputOptions; + const hash = createHash(/** @type {Algorithm} */ (hashFunction)); + + if (hashSalt) { + hash.update(hashSalt); + } + + hash.update(relativeResourcePath); + + if (!/\[local\]/.test(localIdentName)) { + hash.update(local); + } + + const localIdentHash = + /** @type {string} */ + (hash.digest(hashDigest)).slice(0, hashDigestLength); + + return runtimeTemplate.compilation + .getPath(localIdentName, { + filename: relativeResourcePath, + hash: localIdentHash, + contentHash: localIdentHash, + chunkGraph, + module + }) + .replace(/\[local\]/g, local) + .replace(/\[uniqueName\]/g, /** @type {string} */ (uniqueName)) + .replace(/^((-?[0-9])|--)/, "_$1"); +}; + +class CssLocalIdentifierDependency extends NullDependency { + /** + * @param {string} name name + * @param {Range} range range + * @param {string=} prefix prefix + */ + constructor(name, range, prefix = "") { + super(); + this.name = name; + this.range = range; + this.prefix = prefix; + this._conventionNames = undefined; + this._hashUpdate = undefined; + } + + get type() { + return "css local identifier"; + } + + /** + * @param {string} name export name + * @param {CssGeneratorExportsConvention} convention convention of the export name + * @returns {string[]} convention results + */ + getExportsConventionNames(name, convention) { + if (this._conventionNames) { + return this._conventionNames; + } + this._conventionNames = cssExportConvention(this.name, convention); + return this._conventionNames; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this)); + const convention = + /** @type {CssGenerator} */ + (module.generator).convention; + const names = this.getExportsConventionNames(this.name, convention); + return { + exports: names.map(name => ({ + name, + canMangle: true + })), + dependencies: undefined + }; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, { chunkGraph }) { + if (this._hashUpdate === undefined) { + const module = + /** @type {CssModule} */ + (chunkGraph.moduleGraph.getParentModule(this)); + const generator = + /** @type {CssGenerator} */ + (module.generator); + const names = this.getExportsConventionNames( + this.name, + generator.convention + ); + this._hashUpdate = `exportsConvention|${JSON.stringify(names)}|localIdentName|${JSON.stringify(generator.localIdentName)}`; + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.name); + write(this.range); + write(this.prefix); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.name = read(); + this.range = read(); + this.prefix = read(); + super.deserialize(context); + } +} + +CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @param {Dependency} dependency the dependency for which the template should be applied + * @param {string} local local name + * @param {DependencyTemplateContext} templateContext the context object + * @returns {string} identifier + */ + static getIdentifier( + dependency, + local, + { module: m, chunkGraph, runtimeTemplate } + ) { + const dep = /** @type {CssLocalIdentifierDependency} */ (dependency); + const module = /** @type {CssModule} */ (m); + + return ( + dep.prefix + + getCssParser().escapeIdentifier( + getLocalIdent(local, module, chunkGraph, runtimeTemplate) + ) + ); + } + + /** + * @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 { module: m, moduleGraph, runtime, cssData } = templateContext; + const dep = /** @type {CssLocalIdentifierDependency} */ (dependency); + const module = /** @type {CssModule} */ (m); + const convention = + /** @type {CssGenerator} */ + (module.generator).convention; + const names = dep.getExportsConventionNames(dep.name, convention); + const usedNames = + /** @type {(string)[]} */ + ( + names + .map(name => + moduleGraph.getExportInfo(module, name).getUsedName(name, runtime) + ) + .filter(Boolean) + ); + const local = usedNames.length === 0 ? names[0] : usedNames[0]; + const identifier = CssLocalIdentifierDependencyTemplate.getIdentifier( + dep, + local, + templateContext + ); + + source.replace(dep.range[0], dep.range[1] - 1, identifier); + + for (const used of usedNames.concat(names)) { + cssData.exports.set(used, getCssParser().unescapeIdentifier(identifier)); + } + } +}; + +makeSerializable( + CssLocalIdentifierDependency, + "webpack/lib/dependencies/CssLocalIdentifierDependency" +); + +module.exports = CssLocalIdentifierDependency; diff --git a/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js b/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js new file mode 100644 index 00000000000..b63ff53a74e --- /dev/null +++ b/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js @@ -0,0 +1,111 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); +const CssLocalIdentifierDependency = require("./CssLocalIdentifierDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../css/CssParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class CssSelfLocalIdentifierDependency extends CssLocalIdentifierDependency { + /** + * @param {string} name name + * @param {Range} range range + * @param {string=} prefix prefix + * @param {Set=} declaredSet set of declared names (will only be active when in declared set) + */ + constructor(name, range, prefix = "", declaredSet = undefined) { + super(name, range, prefix); + this.declaredSet = declaredSet; + } + + get type() { + return "css self local identifier"; + } + + get category() { + return "self"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return "self"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + if (this.declaredSet && !this.declaredSet.has(this.name)) return; + return super.getExports(moduleGraph); + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + if (this.declaredSet && !this.declaredSet.has(this.name)) + return Dependency.NO_EXPORTS_REFERENCED; + return [[this.name]]; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.declaredSet); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.declaredSet = read(); + super.deserialize(context); + } +} + +CssSelfLocalIdentifierDependency.Template = class CssSelfLocalIdentifierDependencyTemplate extends ( + CssLocalIdentifierDependency.Template +) { + /** + * @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 dep = /** @type {CssSelfLocalIdentifierDependency} */ (dependency); + if (dep.declaredSet && !dep.declaredSet.has(dep.name)) return; + super.apply(dependency, source, templateContext); + } +}; + +makeSerializable( + CssSelfLocalIdentifierDependency, + "webpack/lib/dependencies/CssSelfLocalIdentifierDependency" +); + +module.exports = CssSelfLocalIdentifierDependency; diff --git a/webpack-lib/lib/dependencies/CssUrlDependency.js b/webpack-lib/lib/dependencies/CssUrlDependency.js new file mode 100644 index 00000000000..a177a89df5d --- /dev/null +++ b/webpack-lib/lib/dependencies/CssUrlDependency.js @@ -0,0 +1,195 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const RawDataUrlModule = require("../asset/RawDataUrlModule"); +const makeSerializable = require("../util/makeSerializable"); +const memoize = require("../util/memoize"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +const getIgnoredRawDataUrlModule = memoize( + () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)") +); + +class CssUrlDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {Range} range range of the argument + * @param {"string" | "url" | "src"} urlType dependency type e.g. url() or string + */ + constructor(request, range, urlType) { + super(request); + this.range = range; + this.urlType = urlType; + } + + get type() { + return "css url()"; + } + + get category() { + return "url"; + } + + /** + * @param {string} context context directory + * @returns {Module | null} a module + */ + createIgnoredModule(context) { + return getIgnoredRawDataUrlModule(); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.urlType); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.urlType = read(); + super.deserialize(context); + } +} + +/** + * @param {string} str string + * @returns {string} string in quotes if needed + */ +const cssEscapeString = str => { + let countWhiteOrBracket = 0; + let countQuotation = 0; + let countApostrophe = 0; + for (let i = 0; i < str.length; i++) { + const cc = str.charCodeAt(i); + switch (cc) { + case 9: // tab + case 10: // nl + case 32: // space + case 40: // ( + case 41: // ) + countWhiteOrBracket++; + break; + case 34: + countQuotation++; + break; + case 39: + countApostrophe++; + break; + } + } + if (countWhiteOrBracket < 2) { + return str.replace(/[\n\t ()'"\\]/g, m => `\\${m}`); + } else if (countQuotation <= countApostrophe) { + return `"${str.replace(/[\n"\\]/g, m => `\\${m}`)}"`; + } + return `'${str.replace(/[\n'\\]/g, m => `\\${m}`)}'`; +}; + +CssUrlDependency.Template = class CssUrlDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, + { moduleGraph, runtimeTemplate, codeGenerationResults } + ) { + const dep = /** @type {CssUrlDependency} */ (dependency); + const module = /** @type {Module} */ (moduleGraph.getModule(dep)); + + /** @type {string | undefined} */ + let newValue; + + switch (dep.urlType) { + case "string": + newValue = cssEscapeString( + this.assetUrl({ + module, + codeGenerationResults + }) + ); + break; + case "url": + newValue = `url(${cssEscapeString( + this.assetUrl({ + module, + codeGenerationResults + }) + )})`; + break; + case "src": + newValue = `src(${cssEscapeString( + this.assetUrl({ + module, + codeGenerationResults + }) + )})`; + break; + } + + source.replace( + dep.range[0], + dep.range[1] - 1, + /** @type {string} */ (newValue) + ); + } + + /** + * @param {object} options options object + * @param {Module} options.module the module + * @param {RuntimeSpec=} options.runtime runtime + * @param {CodeGenerationResults} options.codeGenerationResults the code generation results + * @returns {string} the url of the asset + */ + assetUrl({ runtime, module, codeGenerationResults }) { + if (!module) { + return "data:,"; + } + const codeGen = codeGenerationResults.get(module, runtime); + const data = + /** @type {NonNullable} */ + (codeGen.data); + if (!data) return "data:,"; + const url = data.get("url"); + if (!url || !url["css-url"]) return "data:,"; + return url["css-url"]; + } +}; + +makeSerializable(CssUrlDependency, "webpack/lib/dependencies/CssUrlDependency"); + +CssUrlDependency.PUBLIC_PATH_AUTO = "__WEBPACK_CSS_PUBLIC_PATH_AUTO__"; + +module.exports = CssUrlDependency; diff --git a/webpack-lib/lib/dependencies/DelegatedSourceDependency.js b/webpack-lib/lib/dependencies/DelegatedSourceDependency.js new file mode 100644 index 00000000000..737f60e7727 --- /dev/null +++ b/webpack-lib/lib/dependencies/DelegatedSourceDependency.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +class DelegatedSourceDependency extends ModuleDependency { + /** + * @param {string} request the request string + */ + constructor(request) { + super(request); + } + + get type() { + return "delegated source"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + DelegatedSourceDependency, + "webpack/lib/dependencies/DelegatedSourceDependency" +); + +module.exports = DelegatedSourceDependency; diff --git a/webpack-lib/lib/dependencies/DllEntryDependency.js b/webpack-lib/lib/dependencies/DllEntryDependency.js new file mode 100644 index 00000000000..74697042150 --- /dev/null +++ b/webpack-lib/lib/dependencies/DllEntryDependency.js @@ -0,0 +1,61 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./EntryDependency")} EntryDependency */ + +class DllEntryDependency extends Dependency { + /** + * @param {EntryDependency[]} dependencies dependencies + * @param {string} name name + */ + constructor(dependencies, name) { + super(); + + this.dependencies = dependencies; + this.name = name; + } + + get type() { + return "dll entry"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.dependencies); + write(this.name); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.dependencies = read(); + this.name = read(); + + super.deserialize(context); + } +} + +makeSerializable( + DllEntryDependency, + "webpack/lib/dependencies/DllEntryDependency" +); + +module.exports = DllEntryDependency; diff --git a/webpack-lib/lib/dependencies/DynamicExports.js b/webpack-lib/lib/dependencies/DynamicExports.js new file mode 100644 index 00000000000..cdc5e9c9820 --- /dev/null +++ b/webpack-lib/lib/dependencies/DynamicExports.js @@ -0,0 +1,73 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ + +/** @type {WeakMap} */ +const parserStateExportsState = new WeakMap(); + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.bailout = parserState => { + const value = parserStateExportsState.get(parserState); + parserStateExportsState.set(parserState, false); + if (value === true) { + const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); + buildMeta.exportsType = undefined; + buildMeta.defaultObject = false; + } +}; + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.enable = parserState => { + const value = parserStateExportsState.get(parserState); + if (value === false) return; + parserStateExportsState.set(parserState, true); + if (value !== true) { + const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); + buildMeta.exportsType = "default"; + buildMeta.defaultObject = "redirect"; + } +}; + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.setFlagged = parserState => { + const value = parserStateExportsState.get(parserState); + if (value !== true) return; + const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); + if (buildMeta.exportsType === "dynamic") return; + buildMeta.exportsType = "flagged"; +}; + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.setDynamic = parserState => { + const value = parserStateExportsState.get(parserState); + if (value !== true) return; + /** @type {BuildMeta} */ + (parserState.module.buildMeta).exportsType = "dynamic"; +}; + +/** + * @param {ParserState} parserState parser state + * @returns {boolean} true, when enabled + */ +module.exports.isEnabled = parserState => { + const value = parserStateExportsState.get(parserState); + return value === true; +}; diff --git a/webpack-lib/lib/dependencies/EntryDependency.js b/webpack-lib/lib/dependencies/EntryDependency.js new file mode 100644 index 00000000000..f46444945b7 --- /dev/null +++ b/webpack-lib/lib/dependencies/EntryDependency.js @@ -0,0 +1,30 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +class EntryDependency extends ModuleDependency { + /** + * @param {string} request request path for entry + */ + constructor(request) { + super(request); + } + + get type() { + return "entry"; + } + + get category() { + return "esm"; + } +} + +makeSerializable(EntryDependency, "webpack/lib/dependencies/EntryDependency"); + +module.exports = EntryDependency; diff --git a/webpack-lib/lib/dependencies/ExportsInfoDependency.js b/webpack-lib/lib/dependencies/ExportsInfoDependency.js new file mode 100644 index 00000000000..70383e25d3a --- /dev/null +++ b/webpack-lib/lib/dependencies/ExportsInfoDependency.js @@ -0,0 +1,158 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {Module} module the module + * @param {string[] | null} _exportName name of the export if any + * @param {string | null} property name of the requested property + * @param {RuntimeSpec} runtime for which runtime + * @returns {any} value of the property + */ +const getProperty = (moduleGraph, module, _exportName, property, runtime) => { + if (!_exportName) { + switch (property) { + case "usedExports": { + const usedExports = moduleGraph + .getExportsInfo(module) + .getUsedExports(runtime); + if ( + typeof usedExports === "boolean" || + usedExports === undefined || + usedExports === null + ) { + return usedExports; + } + return Array.from(usedExports).sort(); + } + } + } + const exportName = /** @type {string[]} */ (_exportName); + switch (property) { + case "canMangle": { + const exportsInfo = moduleGraph.getExportsInfo(module); + const exportInfo = exportsInfo.getReadOnlyExportInfoRecursive(exportName); + if (exportInfo) return exportInfo.canMangle; + return exportsInfo.otherExportsInfo.canMangle; + } + case "used": + return ( + moduleGraph.getExportsInfo(module).getUsed(exportName, runtime) !== + UsageState.Unused + ); + case "useInfo": { + const state = moduleGraph + .getExportsInfo(module) + .getUsed(exportName, runtime); + switch (state) { + case UsageState.Used: + case UsageState.OnlyPropertiesUsed: + return true; + case UsageState.Unused: + return false; + case UsageState.NoInfo: + return; + case UsageState.Unknown: + return null; + default: + throw new Error(`Unexpected UsageState ${state}`); + } + } + case "provideInfo": + return moduleGraph.getExportsInfo(module).isExportProvided(exportName); + } +}; + +class ExportsInfoDependency extends NullDependency { + /** + * @param {Range} range range + * @param {string[] | null} exportName export name + * @param {string | null} property property + */ + constructor(range, exportName, property) { + super(); + this.range = range; + this.exportName = exportName; + this.property = property; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.exportName); + write(this.property); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ExportsInfoDependency} ExportsInfoDependency + */ + static deserialize(context) { + const obj = new ExportsInfoDependency( + context.read(), + context.read(), + context.read() + ); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + ExportsInfoDependency, + "webpack/lib/dependencies/ExportsInfoDependency" +); + +ExportsInfoDependency.Template = class ExportsInfoDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { module, moduleGraph, runtime }) { + const dep = /** @type {ExportsInfoDependency} */ (dependency); + + const value = getProperty( + moduleGraph, + module, + dep.exportName, + dep.property, + runtime + ); + source.replace( + dep.range[0], + dep.range[1] - 1, + value === undefined ? "undefined" : JSON.stringify(value) + ); + } +}; + +module.exports = ExportsInfoDependency; diff --git a/webpack-lib/lib/dependencies/ExternalModuleDependency.js b/webpack-lib/lib/dependencies/ExternalModuleDependency.js new file mode 100644 index 00000000000..ce3e3785846 --- /dev/null +++ b/webpack-lib/lib/dependencies/ExternalModuleDependency.js @@ -0,0 +1,109 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const CachedConstDependency = require("./CachedConstDependency"); +const ExternalModuleInitFragment = require("./ExternalModuleInitFragment"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class ExternalModuleDependency extends CachedConstDependency { + /** + * @param {string} module module + * @param {{ name: string, value: string }[]} importSpecifiers import specifiers + * @param {string | undefined} defaultImport default import + * @param {string} expression expression + * @param {Range} range range + * @param {string} identifier identifier + */ + constructor( + module, + importSpecifiers, + defaultImport, + expression, + range, + identifier + ) { + super(expression, range, identifier); + + this.importedModule = module; + this.specifiers = importSpecifiers; + this.default = defaultImport; + } + + /** + * @returns {string} hash update + */ + _createHashUpdate() { + return `${this.importedModule}${JSON.stringify(this.specifiers)}${ + this.default || "null" + }${super._createHashUpdate()}`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + super.serialize(context); + const { write } = context; + write(this.importedModule); + write(this.specifiers); + write(this.default); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + super.deserialize(context); + const { read } = context; + this.importedModule = read(); + this.specifiers = read(); + this.default = read(); + } +} + +makeSerializable( + ExternalModuleDependency, + "webpack/lib/dependencies/ExternalModuleDependency" +); + +ExternalModuleDependency.Template = class ExternalModuleDependencyTemplate extends ( + CachedConstDependency.Template +) { + /** + * @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) { + super.apply(dependency, source, templateContext); + const dep = /** @type {ExternalModuleDependency} */ (dependency); + const { chunkInitFragments, runtimeTemplate } = templateContext; + + chunkInitFragments.push( + new ExternalModuleInitFragment( + `${runtimeTemplate.supportNodePrefixForCoreModules() ? "node:" : ""}${ + dep.importedModule + }`, + dep.specifiers, + dep.default + ) + ); + } +}; + +module.exports = ExternalModuleDependency; diff --git a/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js b/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js new file mode 100644 index 00000000000..41bbe538e14 --- /dev/null +++ b/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js @@ -0,0 +1,133 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const InitFragment = require("../InitFragment"); +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 {Map>} ImportSpecifiers */ + +/** + * @extends {InitFragment} + */ +class ExternalModuleInitFragment extends InitFragment { + /** + * @param {string} importedModule imported module + * @param {Array<{ name: string, value?: string }> | ImportSpecifiers} specifiers import specifiers + * @param {string=} defaultImport default import + */ + constructor(importedModule, specifiers, defaultImport) { + super( + undefined, + InitFragment.STAGE_CONSTANTS, + 0, + `external module imports|${importedModule}|${defaultImport || "null"}` + ); + this.importedModule = importedModule; + if (Array.isArray(specifiers)) { + /** @type {ImportSpecifiers} */ + this.specifiers = new Map(); + for (const { name, value } of specifiers) { + let specifiers = this.specifiers.get(name); + if (!specifiers) { + specifiers = new Set(); + this.specifiers.set(name, specifiers); + } + specifiers.add(value || name); + } + } else { + this.specifiers = specifiers; + } + this.defaultImport = defaultImport; + } + + /** + * @param {ExternalModuleInitFragment} other other + * @returns {ExternalModuleInitFragment} ExternalModuleInitFragment + */ + merge(other) { + const newSpecifiersMap = new Map(this.specifiers); + for (const [name, specifiers] of other.specifiers) { + if (newSpecifiersMap.has(name)) { + const currentSpecifiers = + /** @type {Set} */ + (newSpecifiersMap.get(name)); + for (const spec of specifiers) currentSpecifiers.add(spec); + } else { + newSpecifiersMap.set(name, specifiers); + } + } + return new ExternalModuleInitFragment( + this.importedModule, + newSpecifiersMap, + this.defaultImport + ); + } + + /** + * @param {GenerateContext} context context + * @returns {string | Source | undefined} the source code that will be included as initialization code + */ + getContent({ runtimeRequirements }) { + const namedImports = []; + + for (const [name, specifiers] of this.specifiers) { + for (const spec of specifiers) { + if (spec === name) { + namedImports.push(name); + } else { + namedImports.push(`${name} as ${spec}`); + } + } + } + + let importsString = + namedImports.length > 0 ? `{${namedImports.join(",")}}` : ""; + + if (this.defaultImport) { + importsString = `${this.defaultImport}${ + importsString ? `, ${importsString}` : "" + }`; + } + + return `import ${importsString} from ${JSON.stringify( + this.importedModule + )};`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + super.serialize(context); + const { write } = context; + write(this.importedModule); + write(this.specifiers); + write(this.defaultImport); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + super.deserialize(context); + const { read } = context; + this.importedModule = read(); + this.specifiers = read(); + this.defaultImport = read(); + } +} + +makeSerializable( + ExternalModuleInitFragment, + "webpack/lib/dependencies/ExternalModuleInitFragment" +); + +module.exports = ExternalModuleInitFragment; diff --git a/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js b/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js new file mode 100644 index 00000000000..4817b722d7e --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js @@ -0,0 +1,143 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Template = require("../Template"); +const makeSerializable = require("../util/makeSerializable"); +const HarmonyImportDependency = require("./HarmonyImportDependency"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./HarmonyAcceptImportDependency")} HarmonyAcceptImportDependency */ + +class HarmonyAcceptDependency extends NullDependency { + /** + * @param {Range} range expression range + * @param {HarmonyAcceptImportDependency[]} dependencies import dependencies + * @param {boolean} hasCallback true, if the range wraps an existing callback + */ + constructor(range, dependencies, hasCallback) { + super(); + this.range = range; + this.dependencies = dependencies; + this.hasCallback = hasCallback; + } + + get type() { + return "accepted harmony modules"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.dependencies); + write(this.hasCallback); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.dependencies = read(); + this.hasCallback = read(); + super.deserialize(context); + } +} + +makeSerializable( + HarmonyAcceptDependency, + "webpack/lib/dependencies/HarmonyAcceptDependency" +); + +HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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 dep = /** @type {HarmonyAcceptDependency} */ (dependency); + const { + module, + runtime, + runtimeRequirements, + runtimeTemplate, + moduleGraph, + chunkGraph + } = templateContext; + const content = dep.dependencies + .map(dependency => { + const referencedModule = moduleGraph.getModule(dependency); + return { + dependency, + runtimeCondition: referencedModule + ? HarmonyImportDependency.Template.getImportEmittedRuntime( + module, + referencedModule + ) + : false + }; + }) + .filter(({ runtimeCondition }) => runtimeCondition !== false) + .map(({ dependency, runtimeCondition }) => { + const condition = runtimeTemplate.runtimeConditionExpression({ + chunkGraph, + runtime, + runtimeCondition, + runtimeRequirements + }); + const s = dependency.getImportStatement(true, templateContext); + const code = s[0] + s[1]; + if (condition !== "true") { + return `if (${condition}) {\n${Template.indent(code)}\n}\n`; + } + return code; + }) + .join(""); + + if (dep.hasCallback) { + if (runtimeTemplate.supportsArrowFunction()) { + source.insert( + dep.range[0], + `__WEBPACK_OUTDATED_DEPENDENCIES__ => { ${content}(` + ); + source.insert(dep.range[1], ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }"); + } else { + source.insert( + dep.range[0], + `function(__WEBPACK_OUTDATED_DEPENDENCIES__) { ${content}(` + ); + source.insert( + dep.range[1], + ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }.bind(this)" + ); + } + return; + } + + const arrow = runtimeTemplate.supportsArrowFunction(); + source.insert( + dep.range[1] - 0.5, + `, ${arrow ? "() =>" : "function()"} { ${content} }` + ); + } +}; + +module.exports = HarmonyAcceptDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js b/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js new file mode 100644 index 00000000000..03cb0002ddc --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js @@ -0,0 +1,40 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const HarmonyImportDependency = require("./HarmonyImportDependency"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ + +class HarmonyAcceptImportDependency extends HarmonyImportDependency { + /** + * @param {string} request the request string + */ + constructor(request) { + super(request, Number.NaN); + this.weak = true; + } + + get type() { + return "harmony accept"; + } +} + +makeSerializable( + HarmonyAcceptImportDependency, + "webpack/lib/dependencies/HarmonyAcceptImportDependency" +); + +HarmonyAcceptImportDependency.Template = + /** @type {typeof HarmonyImportDependency.Template} */ ( + NullDependency.Template + ); + +module.exports = HarmonyAcceptImportDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js b/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js new file mode 100644 index 00000000000..5464f91b1f0 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js @@ -0,0 +1,92 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); +const InitFragment = require("../InitFragment"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ + +class HarmonyCompatibilityDependency extends NullDependency { + get type() { + return "harmony export header"; + } +} + +makeSerializable( + HarmonyCompatibilityDependency, + "webpack/lib/dependencies/HarmonyCompatibilityDependency" +); + +HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { + module, + runtimeTemplate, + moduleGraph, + initFragments, + runtimeRequirements, + runtime, + concatenationScope + } + ) { + if (concatenationScope) return; + const exportsInfo = moduleGraph.getExportsInfo(module); + if ( + exportsInfo.getReadOnlyExportInfo("__esModule").getUsed(runtime) !== + UsageState.Unused + ) { + const content = runtimeTemplate.defineEsModuleFlagStatement({ + exportsArgument: module.exportsArgument, + runtimeRequirements + }); + initFragments.push( + new InitFragment( + content, + InitFragment.STAGE_HARMONY_EXPORTS, + 0, + "harmony compatibility" + ) + ); + } + if (moduleGraph.isAsync(module)) { + runtimeRequirements.add(RuntimeGlobals.module); + runtimeRequirements.add(RuntimeGlobals.asyncModule); + initFragments.push( + new InitFragment( + runtimeTemplate.supportsArrowFunction() + ? `${RuntimeGlobals.asyncModule}(${module.moduleArgument}, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try {\n` + : `${RuntimeGlobals.asyncModule}(${module.moduleArgument}, async function (__webpack_handle_async_dependencies__, __webpack_async_result__) { try {\n`, + InitFragment.STAGE_ASYNC_BOUNDARY, + 0, + undefined, + `\n__webpack_async_result__();\n} catch(e) { __webpack_async_result__(e); } }${ + /** @type {BuildMeta} */ (module.buildMeta).async ? ", 1" : "" + });` + ) + ); + } + } +}; + +module.exports = HarmonyCompatibilityDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js new file mode 100644 index 00000000000..4cf84fc1ec5 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js @@ -0,0 +1,123 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const EnvironmentNotSupportAsyncWarning = require("../EnvironmentNotSupportAsyncWarning"); +const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants"); +const DynamicExports = require("./DynamicExports"); +const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency"); +const HarmonyExports = require("./HarmonyExports"); + +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("./HarmonyModulesPlugin").HarmonyModulesPluginOptions} HarmonyModulesPluginOptions */ + +module.exports = class HarmonyDetectionParserPlugin { + /** + * @param {HarmonyModulesPluginOptions} options options + */ + constructor(options) { + const { topLevelAwait = false } = options || {}; + this.topLevelAwait = topLevelAwait; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.program.tap("HarmonyDetectionParserPlugin", ast => { + const isStrictHarmony = + parser.state.module.type === JAVASCRIPT_MODULE_TYPE_ESM; + const isHarmony = + isStrictHarmony || + ast.body.some( + statement => + statement.type === "ImportDeclaration" || + statement.type === "ExportDefaultDeclaration" || + statement.type === "ExportNamedDeclaration" || + statement.type === "ExportAllDeclaration" + ); + if (isHarmony) { + const module = parser.state.module; + const compatDep = new HarmonyCompatibilityDependency(); + compatDep.loc = { + start: { + line: -1, + column: 0 + }, + end: { + line: -1, + column: 0 + }, + index: -3 + }; + module.addPresentationalDependency(compatDep); + DynamicExports.bailout(parser.state); + HarmonyExports.enable(parser.state, isStrictHarmony); + parser.scope.isStrict = true; + } + }); + + parser.hooks.topLevelAwait.tap("HarmonyDetectionParserPlugin", () => { + const module = parser.state.module; + if (!this.topLevelAwait) { + throw new Error( + "The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enable it)" + ); + } + if (!HarmonyExports.isEnabled(parser.state)) { + throw new Error( + "Top-level-await is only supported in EcmaScript Modules" + ); + } + /** @type {BuildMeta} */ + (module.buildMeta).async = true; + EnvironmentNotSupportAsyncWarning.check( + module, + parser.state.compilation.runtimeTemplate, + "topLevelAwait" + ); + }); + + /** + * @returns {boolean | undefined} true if in harmony + */ + const skipInHarmony = () => { + if (HarmonyExports.isEnabled(parser.state)) { + return true; + } + }; + + /** + * @returns {null | undefined} null if in harmony + */ + const nullInHarmony = () => { + if (HarmonyExports.isEnabled(parser.state)) { + return null; + } + }; + + const nonHarmonyIdentifiers = ["define", "exports"]; + for (const identifier of nonHarmonyIdentifiers) { + parser.hooks.evaluateTypeof + .for(identifier) + .tap("HarmonyDetectionParserPlugin", nullInHarmony); + parser.hooks.typeof + .for(identifier) + .tap("HarmonyDetectionParserPlugin", skipInHarmony); + parser.hooks.evaluate + .for(identifier) + .tap("HarmonyDetectionParserPlugin", nullInHarmony); + parser.hooks.expression + .for(identifier) + .tap("HarmonyDetectionParserPlugin", skipInHarmony); + parser.hooks.call + .for(identifier) + .tap("HarmonyDetectionParserPlugin", skipInHarmony); + } + } +}; diff --git a/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js new file mode 100644 index 00000000000..09ceb8e4aa0 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js @@ -0,0 +1,152 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +/** + * Dependency for static evaluating import specifier. e.g. + * @example + * import a from "a"; + * "x" in a; + * a.x !== undefined; // if x value statically analyzable + */ +class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDependency { + /** + * @param {string} request the request string + * @param {number} sourceOrder source order + * @param {TODO} ids ids + * @param {TODO} name name + * @param {Range} range location in source code + * @param {ImportAttributes} attributes import assertions + * @param {string} operator operator + */ + constructor(request, sourceOrder, ids, name, range, attributes, operator) { + super(request, sourceOrder, ids, name, range, false, attributes, []); + this.operator = operator; + } + + get type() { + return `evaluated X ${this.operator} harmony import specifier`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + super.serialize(context); + const { write } = context; + write(this.operator); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + super.deserialize(context); + const { read } = context; + this.operator = read(); + } +} + +makeSerializable( + HarmonyEvaluatedImportSpecifierDependency, + "webpack/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency" +); + +HarmonyEvaluatedImportSpecifierDependency.Template = class HarmonyEvaluatedImportSpecifierDependencyTemplate extends ( + HarmonyImportSpecifierDependency.Template +) { + /** + * @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 dep = /** @type {HarmonyEvaluatedImportSpecifierDependency} */ ( + dependency + ); + const { module, moduleGraph, runtime } = templateContext; + const connection = moduleGraph.getConnection(dep); + // Skip rendering depending when dependency is conditional + if (connection && !connection.isTargetActive(runtime)) return; + + const exportsInfo = moduleGraph.getExportsInfo( + /** @type {ModuleGraphConnection} */ (connection).module + ); + const ids = dep.getIds(moduleGraph); + + let value; + + const exportsType = + /** @type {ModuleGraphConnection} */ + (connection).module.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ + (module.buildMeta).strictHarmonyModule + ); + switch (exportsType) { + case "default-with-named": { + if (ids[0] === "default") { + value = + ids.length === 1 || exportsInfo.isExportProvided(ids.slice(1)); + } else { + value = exportsInfo.isExportProvided(ids); + } + break; + } + case "namespace": { + value = + ids[0] === "__esModule" + ? ids.length === 1 || undefined + : exportsInfo.isExportProvided(ids); + break; + } + case "dynamic": { + if (ids[0] !== "default") { + value = exportsInfo.isExportProvided(ids); + } + break; + } + // default-only could lead to runtime error, when default value is primitive + } + + if (typeof value === "boolean") { + source.replace(dep.range[0], dep.range[1] - 1, ` ${value}`); + } else { + const usedName = exportsInfo.getUsedName(ids, runtime); + + const code = this._getCodeForIds( + dep, + source, + templateContext, + ids.slice(0, -1) + ); + source.replace( + dep.range[0], + dep.range[1] - 1, + `${ + usedName ? JSON.stringify(usedName[usedName.length - 1]) : '""' + } in ${code}` + ); + } + } +}; + +module.exports = HarmonyEvaluatedImportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js new file mode 100644 index 00000000000..247d0c155ac --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js @@ -0,0 +1,221 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { getImportAttributes } = require("../javascript/JavascriptParser"); +const InnerGraph = require("../optimize/InnerGraph"); +const ConstDependency = require("./ConstDependency"); +const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency"); +const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency"); +const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency"); +const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency"); +const { ExportPresenceModes } = require("./HarmonyImportDependency"); +const { + harmonySpecifierTag +} = require("./HarmonyImportDependencyParserPlugin"); +const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); + +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").FunctionDeclaration} FunctionDeclaration */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const { HarmonyStarExportsList } = HarmonyExportImportedSpecifierDependency; + +module.exports = class HarmonyExportDependencyParserPlugin { + /** + * @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options options + */ + constructor(options) { + this.exportPresenceMode = + options.reexportExportsPresence !== undefined + ? ExportPresenceModes.fromUserOption(options.reexportExportsPresence) + : options.exportsPresence !== undefined + ? ExportPresenceModes.fromUserOption(options.exportsPresence) + : options.strictExportPresence + ? ExportPresenceModes.ERROR + : ExportPresenceModes.AUTO; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const { exportPresenceMode } = this; + parser.hooks.export.tap( + "HarmonyExportDependencyParserPlugin", + statement => { + const dep = new HarmonyExportHeaderDependency( + /** @type {Range | false} */ ( + statement.declaration && statement.declaration.range + ), + /** @type {Range} */ (statement.range) + ); + dep.loc = Object.create( + /** @type {DependencyLocation} */ (statement.loc) + ); + dep.loc.index = -1; + parser.state.module.addPresentationalDependency(dep); + return true; + } + ); + parser.hooks.exportImport.tap( + "HarmonyExportDependencyParserPlugin", + (statement, source) => { + parser.state.lastHarmonyImportOrder = + (parser.state.lastHarmonyImportOrder || 0) + 1; + const clearDep = new ConstDependency( + "", + /** @type {Range} */ (statement.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); + clearDep.loc.index = -1; + parser.state.module.addPresentationalDependency(clearDep); + const sideEffectDep = new HarmonyImportSideEffectDependency( + /** @type {string} */ (source), + parser.state.lastHarmonyImportOrder, + getImportAttributes(statement) + ); + sideEffectDep.loc = Object.create( + /** @type {DependencyLocation} */ (statement.loc) + ); + sideEffectDep.loc.index = -1; + parser.state.current.addDependency(sideEffectDep); + return true; + } + ); + parser.hooks.exportExpression.tap( + "HarmonyExportDependencyParserPlugin", + (statement, expr) => { + const isFunctionDeclaration = expr.type === "FunctionDeclaration"; + const exprRange = /** @type {Range} */ (expr.range); + const statementRange = /** @type {Range} */ (statement.range); + const comments = parser.getComments([statementRange[0], exprRange[0]]); + const dep = new HarmonyExportExpressionDependency( + exprRange, + statementRange, + comments + .map(c => { + switch (c.type) { + case "Block": + return `/*${c.value}*/`; + case "Line": + return `//${c.value}\n`; + } + return ""; + }) + .join(""), + expr.type.endsWith("Declaration") && expr.id + ? expr.id.name + : isFunctionDeclaration + ? { + range: [ + exprRange[0], + expr.params.length > 0 + ? /** @type {Range} */ (expr.params[0].range)[0] + : /** @type {Range} */ (expr.body.range)[0] + ], + prefix: `${expr.async ? "async " : ""}function${ + expr.generator ? "*" : "" + } `, + suffix: `(${expr.params.length > 0 ? "" : ") "}` + } + : undefined + ); + dep.loc = Object.create( + /** @type {DependencyLocation} */ (statement.loc) + ); + dep.loc.index = -1; + parser.state.current.addDependency(dep); + InnerGraph.addVariableUsage( + parser, + expr.type.endsWith("Declaration") && expr.id + ? expr.id.name + : "*default*", + "default" + ); + return true; + } + ); + parser.hooks.exportSpecifier.tap( + "HarmonyExportDependencyParserPlugin", + (statement, id, name, idx) => { + const settings = parser.getTagData(id, harmonySpecifierTag); + const harmonyNamedExports = (parser.state.harmonyNamedExports = + parser.state.harmonyNamedExports || new Set()); + harmonyNamedExports.add(name); + InnerGraph.addVariableUsage(parser, id, name); + const dep = settings + ? new HarmonyExportImportedSpecifierDependency( + settings.source, + settings.sourceOrder, + settings.ids, + name, + harmonyNamedExports, + null, + exportPresenceMode, + null, + settings.assertions + ) + : new HarmonyExportSpecifierDependency(id, name); + dep.loc = Object.create( + /** @type {DependencyLocation} */ (statement.loc) + ); + dep.loc.index = idx; + const isAsiSafe = !parser.isAsiPosition( + /** @type {Range} */ + (statement.range)[0] + ); + if (!isAsiSafe) { + parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]); + } + parser.state.current.addDependency(dep); + return true; + } + ); + parser.hooks.exportImportSpecifier.tap( + "HarmonyExportDependencyParserPlugin", + (statement, source, id, name, idx) => { + const harmonyNamedExports = (parser.state.harmonyNamedExports = + parser.state.harmonyNamedExports || new Set()); + let harmonyStarExports = null; + if (name) { + harmonyNamedExports.add(name); + } else { + harmonyStarExports = parser.state.harmonyStarExports = + parser.state.harmonyStarExports || new HarmonyStarExportsList(); + } + const dep = new HarmonyExportImportedSpecifierDependency( + /** @type {string} */ (source), + parser.state.lastHarmonyImportOrder, + id ? [id] : [], + name, + harmonyNamedExports, + harmonyStarExports && harmonyStarExports.slice(), + exportPresenceMode, + harmonyStarExports + ); + if (harmonyStarExports) { + harmonyStarExports.push(dep); + } + dep.loc = Object.create( + /** @type {DependencyLocation} */ (statement.loc) + ); + dep.loc.index = idx; + const isAsiSafe = !parser.isAsiPosition( + /** @type {Range} */ + (statement.range)[0] + ); + if (!isAsiSafe) { + parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]); + } + parser.state.current.addDependency(dep); + return true; + } + ); + } +}; diff --git a/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js b/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js new file mode 100644 index 00000000000..12481cf963c --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js @@ -0,0 +1,207 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ConcatenationScope = require("../ConcatenationScope"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class HarmonyExportExpressionDependency extends NullDependency { + /** + * @param {Range} range range + * @param {Range} rangeStatement range statement + * @param {string} prefix prefix + * @param {string | { range: Range, prefix: string, suffix: string }} [declarationId] declaration id + */ + constructor(range, rangeStatement, prefix, declarationId) { + super(); + this.range = range; + this.rangeStatement = rangeStatement; + this.prefix = prefix; + this.declarationId = declarationId; + } + + get type() { + return "harmony export expression"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + return { + exports: ["default"], + priority: 1, + terminalBinding: true, + dependencies: undefined + }; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + // The expression/declaration is already covered by SideEffectsFlagPlugin + return false; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.rangeStatement); + write(this.prefix); + write(this.declarationId); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.rangeStatement = read(); + this.prefix = read(); + this.declarationId = read(); + super.deserialize(context); + } +} + +makeSerializable( + HarmonyExportExpressionDependency, + "webpack/lib/dependencies/HarmonyExportExpressionDependency" +); + +HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { + module, + moduleGraph, + runtimeTemplate, + runtimeRequirements, + initFragments, + runtime, + concatenationScope + } + ) { + const dep = /** @type {HarmonyExportExpressionDependency} */ (dependency); + const { declarationId } = dep; + const exportsName = module.exportsArgument; + if (declarationId) { + let name; + if (typeof declarationId === "string") { + name = declarationId; + } else { + name = ConcatenationScope.DEFAULT_EXPORT; + source.replace( + declarationId.range[0], + declarationId.range[1] - 1, + `${declarationId.prefix}${name}${declarationId.suffix}` + ); + } + + if (concatenationScope) { + concatenationScope.registerExport("default", name); + } else { + const used = moduleGraph + .getExportsInfo(module) + .getUsedName("default", runtime); + if (used) { + const map = new Map(); + map.set(used, `/* export default binding */ ${name}`); + initFragments.push(new HarmonyExportInitFragment(exportsName, map)); + } + } + + source.replace( + dep.rangeStatement[0], + dep.range[0] - 1, + `/* harmony default export */ ${dep.prefix}` + ); + } else { + /** @type {string} */ + let content; + const name = ConcatenationScope.DEFAULT_EXPORT; + if (runtimeTemplate.supportsConst()) { + content = `/* harmony default export */ const ${name} = `; + if (concatenationScope) { + concatenationScope.registerExport("default", name); + } else { + const used = moduleGraph + .getExportsInfo(module) + .getUsedName("default", runtime); + if (used) { + runtimeRequirements.add(RuntimeGlobals.exports); + const map = new Map(); + map.set(used, name); + initFragments.push(new HarmonyExportInitFragment(exportsName, map)); + } else { + content = `/* unused harmony default export */ var ${name} = `; + } + } + } else if (concatenationScope) { + content = `/* harmony default export */ var ${name} = `; + concatenationScope.registerExport("default", name); + } else { + const used = moduleGraph + .getExportsInfo(module) + .getUsedName("default", runtime); + if (used) { + runtimeRequirements.add(RuntimeGlobals.exports); + // This is a little bit incorrect as TDZ is not correct, but we can't use const. + content = `/* harmony default export */ ${exportsName}${propertyAccess( + typeof used === "string" ? [used] : used + )} = `; + } else { + content = `/* unused harmony default export */ var ${name} = `; + } + } + + if (dep.range) { + source.replace( + dep.rangeStatement[0], + dep.range[0] - 1, + `${content}(${dep.prefix}` + ); + source.replace(dep.range[1], dep.rangeStatement[1] - 0.5, ");"); + return; + } + + source.replace(dep.rangeStatement[0], dep.rangeStatement[1] - 1, content); + } + } +}; + +module.exports = HarmonyExportExpressionDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js b/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js new file mode 100644 index 00000000000..ae649796686 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js @@ -0,0 +1,78 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class HarmonyExportHeaderDependency extends NullDependency { + /** + * @param {Range | false} range range + * @param {Range} rangeStatement range statement + */ + constructor(range, rangeStatement) { + super(); + this.range = range; + this.rangeStatement = rangeStatement; + } + + get type() { + return "harmony export header"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.rangeStatement); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.rangeStatement = read(); + super.deserialize(context); + } +} + +makeSerializable( + HarmonyExportHeaderDependency, + "webpack/lib/dependencies/HarmonyExportHeaderDependency" +); + +HarmonyExportHeaderDependency.Template = class HarmonyExportDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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 dep = /** @type {HarmonyExportHeaderDependency} */ (dependency); + const content = ""; + const replaceUntil = dep.range + ? dep.range[0] - 1 + : dep.rangeStatement[1] - 1; + source.replace(dep.rangeStatement[0], replaceUntil, content); + } +}; + +module.exports = HarmonyExportHeaderDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js new file mode 100644 index 00000000000..95d4507e273 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js @@ -0,0 +1,1396 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ConditionalInitFragment = require("../ConditionalInitFragment"); +const Dependency = require("../Dependency"); +const { UsageState } = require("../ExportsInfo"); +const HarmonyLinkingError = require("../HarmonyLinkingError"); +const InitFragment = require("../InitFragment"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const { countIterable } = require("../util/IterableHelpers"); +const { first, combine } = require("../util/SetHelpers"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const { propertyName } = require("../util/propertyName"); +const { + getRuntimeKey, + keyToRuntime, + filterRuntime +} = require("../util/runtime"); +const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); +const HarmonyImportDependency = require("./HarmonyImportDependency"); +const processExportInfo = require("./processExportInfo"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ExportsInfo")} ExportsInfo */ +/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ +/** @typedef {import("../ExportsInfo").UsedName} UsedName */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @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("./processExportInfo").ReferencedExports} ReferencedExports */ + +/** @typedef {"missing"|"unused"|"empty-star"|"reexport-dynamic-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-fake-namespace-object"|"reexport-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */ + +const { ExportPresenceModes } = HarmonyImportDependency; + +const idsSymbol = Symbol("HarmonyExportImportedSpecifierDependency.ids"); + +class NormalReexportItem { + /** + * @param {string} name export name + * @param {string[]} ids reexported ids from other module + * @param {ExportInfo} exportInfo export info from other module + * @param {boolean} checked true, if it should be checked at runtime if this export exists + * @param {boolean} hidden true, if it is hidden behind another active export in the same module + */ + constructor(name, ids, exportInfo, checked, hidden) { + this.name = name; + this.ids = ids; + this.exportInfo = exportInfo; + this.checked = checked; + this.hidden = hidden; + } +} + +/** @typedef {Set} ExportModeIgnored */ +/** @typedef {Set} ExportModeHidden */ + +class ExportMode { + /** + * @param {ExportModeType} type type of the mode + */ + constructor(type) { + /** @type {ExportModeType} */ + this.type = type; + + // for "normal-reexport": + /** @type {NormalReexportItem[] | null} */ + this.items = null; + + // for "reexport-named-default" | "reexport-fake-namespace-object" | "reexport-namespace-object" + /** @type {string | null} */ + this.name = null; + /** @type {ExportInfo | null} */ + this.partialNamespaceExportInfo = null; + + // for "dynamic-reexport": + /** @type {ExportModeIgnored | null} */ + this.ignored = null; + + // for "dynamic-reexport" | "empty-star": + /** @type {ExportModeHidden | undefined | null} */ + this.hidden = null; + + // for "missing": + /** @type {string | null} */ + this.userRequest = null; + + // for "reexport-fake-namespace-object": + /** @type {number} */ + this.fakeType = 0; + } +} + +/** + * @param {ModuleGraph} moduleGraph module graph + * @param {TODO} dependencies dependencies + * @param {TODO=} additionalDependency additional dependency + * @returns {TODO} result + */ +const determineExportAssignments = ( + moduleGraph, + dependencies, + additionalDependency +) => { + const names = new Set(); + /** @type {number[]} */ + const dependencyIndices = []; + + if (additionalDependency) { + dependencies = dependencies.concat(additionalDependency); + } + + for (const dep of dependencies) { + const i = dependencyIndices.length; + dependencyIndices[i] = names.size; + const otherImportedModule = moduleGraph.getModule(dep); + if (otherImportedModule) { + const exportsInfo = moduleGraph.getExportsInfo(otherImportedModule); + for (const exportInfo of exportsInfo.exports) { + if ( + exportInfo.provided === true && + exportInfo.name !== "default" && + !names.has(exportInfo.name) + ) { + names.add(exportInfo.name); + dependencyIndices[i] = names.size; + } + } + } + } + dependencyIndices.push(names.size); + + return { names: Array.from(names), dependencyIndices }; +}; + +const findDependencyForName = ( + { names, dependencyIndices }, + name, + dependencies +) => { + const dependenciesIt = dependencies[Symbol.iterator](); + const dependencyIndicesIt = dependencyIndices[Symbol.iterator](); + let dependenciesItResult = dependenciesIt.next(); + let dependencyIndicesItResult = dependencyIndicesIt.next(); + if (dependencyIndicesItResult.done) return; + for (let i = 0; i < names.length; i++) { + while (i >= dependencyIndicesItResult.value) { + dependenciesItResult = dependenciesIt.next(); + dependencyIndicesItResult = dependencyIndicesIt.next(); + if (dependencyIndicesItResult.done) return; + } + if (names[i] === name) return dependenciesItResult.value; + } + return undefined; +}; + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {HarmonyExportImportedSpecifierDependency} dep the dependency + * @param {string} runtimeKey the runtime key + * @returns {ExportMode} the export mode + */ +const getMode = (moduleGraph, dep, runtimeKey) => { + const importedModule = moduleGraph.getModule(dep); + + if (!importedModule) { + const mode = new ExportMode("missing"); + + mode.userRequest = dep.userRequest; + + return mode; + } + + const name = dep.name; + const runtime = keyToRuntime(runtimeKey); + const parentModule = /** @type {Module} */ (moduleGraph.getParentModule(dep)); + const exportsInfo = moduleGraph.getExportsInfo(parentModule); + + if ( + name + ? exportsInfo.getUsed(name, runtime) === UsageState.Unused + : exportsInfo.isUsed(runtime) === false + ) { + const mode = new ExportMode("unused"); + + mode.name = name || "*"; + + return mode; + } + + const importedExportsType = importedModule.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule + ); + + const ids = dep.getIds(moduleGraph); + + // Special handling for reexporting the default export + // from non-namespace modules + if (name && ids.length > 0 && ids[0] === "default") { + switch (importedExportsType) { + case "dynamic": { + const mode = new ExportMode("reexport-dynamic-default"); + + mode.name = name; + + return mode; + } + case "default-only": + case "default-with-named": { + const exportInfo = exportsInfo.getReadOnlyExportInfo(name); + const mode = new ExportMode("reexport-named-default"); + + mode.name = name; + mode.partialNamespaceExportInfo = exportInfo; + + return mode; + } + } + } + + // reexporting with a fixed name + if (name) { + let mode; + const exportInfo = exportsInfo.getReadOnlyExportInfo(name); + + if (ids.length > 0) { + // export { name as name } + switch (importedExportsType) { + case "default-only": + mode = new ExportMode("reexport-undefined"); + mode.name = name; + break; + default: + mode = new ExportMode("normal-reexport"); + mode.items = [ + new NormalReexportItem(name, ids, exportInfo, false, false) + ]; + break; + } + } else { + // export * as name + switch (importedExportsType) { + case "default-only": + mode = new ExportMode("reexport-fake-namespace-object"); + mode.name = name; + mode.partialNamespaceExportInfo = exportInfo; + mode.fakeType = 0; + break; + case "default-with-named": + mode = new ExportMode("reexport-fake-namespace-object"); + mode.name = name; + mode.partialNamespaceExportInfo = exportInfo; + mode.fakeType = 2; + break; + case "dynamic": + default: + mode = new ExportMode("reexport-namespace-object"); + mode.name = name; + mode.partialNamespaceExportInfo = exportInfo; + } + } + + return mode; + } + + // Star reexporting + + const { ignoredExports, exports, checked, hidden } = dep.getStarReexports( + moduleGraph, + runtime, + exportsInfo, + importedModule + ); + if (!exports) { + // We have too few info about the modules + // Delegate the logic to the runtime code + + const mode = new ExportMode("dynamic-reexport"); + mode.ignored = ignoredExports; + mode.hidden = hidden; + + return mode; + } + + if (exports.size === 0) { + const mode = new ExportMode("empty-star"); + mode.hidden = hidden; + + return mode; + } + + const mode = new ExportMode("normal-reexport"); + + mode.items = Array.from( + exports, + exportName => + new NormalReexportItem( + exportName, + [exportName], + exportsInfo.getReadOnlyExportInfo(exportName), + /** @type {Set} */ + (checked).has(exportName), + false + ) + ); + if (hidden !== undefined) { + for (const exportName of hidden) { + mode.items.push( + new NormalReexportItem( + exportName, + [exportName], + exportsInfo.getReadOnlyExportInfo(exportName), + false, + true + ) + ); + } + } + + return mode; +}; + +/** @typedef {string[]} Ids */ +/** @typedef {Set} Exports */ +/** @typedef {Set} Checked */ +/** @typedef {Set} Hidden */ +/** @typedef {Set} IgnoredExports */ + +class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { + /** + * @param {string} request the request string + * @param {number} sourceOrder the order in the original source file + * @param {Ids} ids the requested export name of the imported module + * @param {string | null} name the export name of for this module + * @param {Set} activeExports other named exports in the module + * @param {ReadonlyArray | Iterable | null} otherStarExports other star exports in the module before this import + * @param {number} exportPresenceMode mode of checking export names + * @param {HarmonyStarExportsList | null} allStarExports all star exports in the module + * @param {ImportAttributes=} attributes import attributes + */ + constructor( + request, + sourceOrder, + ids, + name, + activeExports, + otherStarExports, + exportPresenceMode, + allStarExports, + attributes + ) { + super(request, sourceOrder, attributes); + + this.ids = ids; + this.name = name; + this.activeExports = activeExports; + this.otherStarExports = otherStarExports; + this.exportPresenceMode = exportPresenceMode; + this.allStarExports = allStarExports; + } + + /** + * @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 Dependency.TRANSITIVE; + } + + // TODO webpack 6 remove + get id() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + // TODO webpack 6 remove + getId() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + // TODO webpack 6 remove + setId() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + get type() { + return "harmony export imported specifier"; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {Ids} the imported id + */ + getIds(moduleGraph) { + return moduleGraph.getMeta(this)[idsSymbol] || this.ids; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {Ids} ids the imported ids + * @returns {void} + */ + setIds(moduleGraph, ids) { + /** @type {TODO} */ + (moduleGraph.getMeta(this))[idsSymbol] = ids; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {RuntimeSpec} runtime the runtime + * @returns {ExportMode} the export mode + */ + getMode(moduleGraph, runtime) { + return moduleGraph.dependencyCacheProvide( + this, + getRuntimeKey(runtime), + getMode + ); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {RuntimeSpec} runtime the runtime + * @param {ExportsInfo} exportsInfo exports info about the current module (optional) + * @param {Module} importedModule the imported module (optional) + * @returns {{exports?: Exports, checked?: Checked, ignoredExports: IgnoredExports, hidden?: Hidden}} information + */ + getStarReexports( + moduleGraph, + runtime, + exportsInfo = moduleGraph.getExportsInfo( + /** @type {Module} */ (moduleGraph.getParentModule(this)) + ), + importedModule = /** @type {Module} */ (moduleGraph.getModule(this)) + ) { + const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); + const noExtraExports = + importedExportsInfo.otherExportsInfo.provided === false; + const noExtraImports = + exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused; + + const ignoredExports = new Set(["default", ...this.activeExports]); + + let hiddenExports; + const otherStarExports = + this._discoverActiveExportsFromOtherStarExports(moduleGraph); + if (otherStarExports !== undefined) { + hiddenExports = new Set(); + for (let i = 0; i < otherStarExports.namesSlice; i++) { + hiddenExports.add(otherStarExports.names[i]); + } + for (const e of ignoredExports) hiddenExports.delete(e); + } + + if (!noExtraExports && !noExtraImports) { + return { + ignoredExports, + hidden: hiddenExports + }; + } + + /** @type {Exports} */ + const exports = new Set(); + /** @type {Checked} */ + const checked = new Set(); + /** @type {Hidden | undefined} */ + const hidden = hiddenExports !== undefined ? new Set() : undefined; + + if (noExtraImports) { + for (const exportInfo of exportsInfo.orderedExports) { + const name = exportInfo.name; + if (ignoredExports.has(name)) continue; + if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; + const importedExportInfo = + importedExportsInfo.getReadOnlyExportInfo(name); + if (importedExportInfo.provided === false) continue; + if (hiddenExports !== undefined && hiddenExports.has(name)) { + /** @type {Set} */ + (hidden).add(name); + continue; + } + exports.add(name); + if (importedExportInfo.provided === true) continue; + checked.add(name); + } + } else if (noExtraExports) { + for (const importedExportInfo of importedExportsInfo.orderedExports) { + const name = importedExportInfo.name; + if (ignoredExports.has(name)) continue; + if (importedExportInfo.provided === false) continue; + const exportInfo = exportsInfo.getReadOnlyExportInfo(name); + if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; + if (hiddenExports !== undefined && hiddenExports.has(name)) { + /** @type {ExportModeHidden} */ + (hidden).add(name); + continue; + } + exports.add(name); + if (importedExportInfo.provided === true) continue; + checked.add(name); + } + } + + return { ignoredExports, exports, checked, hidden }; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return (connection, runtime) => { + const mode = this.getMode(moduleGraph, runtime); + return mode.type !== "unused" && mode.type !== "empty-star"; + }; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return false; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + const mode = this.getMode(moduleGraph, runtime); + + switch (mode.type) { + case "missing": + case "unused": + case "empty-star": + case "reexport-undefined": + return Dependency.NO_EXPORTS_REFERENCED; + + case "reexport-dynamic-default": + return Dependency.EXPORTS_OBJECT_REFERENCED; + + case "reexport-named-default": { + if (!mode.partialNamespaceExportInfo) + return Dependency.EXPORTS_OBJECT_REFERENCED; + /** @type {ReferencedExports} */ + const referencedExports = []; + processExportInfo( + runtime, + referencedExports, + [], + /** @type {ExportInfo} */ (mode.partialNamespaceExportInfo) + ); + return referencedExports; + } + + case "reexport-namespace-object": + case "reexport-fake-namespace-object": { + if (!mode.partialNamespaceExportInfo) + return Dependency.EXPORTS_OBJECT_REFERENCED; + /** @type {ReferencedExports} */ + const referencedExports = []; + processExportInfo( + runtime, + referencedExports, + [], + /** @type {ExportInfo} */ (mode.partialNamespaceExportInfo), + mode.type === "reexport-fake-namespace-object" + ); + return referencedExports; + } + + case "dynamic-reexport": + return Dependency.EXPORTS_OBJECT_REFERENCED; + + case "normal-reexport": { + /** @type {ReferencedExports} */ + const referencedExports = []; + for (const { + ids, + exportInfo, + hidden + } of /** @type {NormalReexportItem[]} */ (mode.items)) { + if (hidden) continue; + processExportInfo(runtime, referencedExports, ids, exportInfo, false); + } + return referencedExports; + } + + default: + throw new Error(`Unknown mode ${mode.type}`); + } + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {{ names: string[], namesSlice: number, dependencyIndices: number[], dependencyIndex: number } | undefined} exported names and their origin dependency + */ + _discoverActiveExportsFromOtherStarExports(moduleGraph) { + if (!this.otherStarExports) return; + + const i = + "length" in this.otherStarExports + ? this.otherStarExports.length + : countIterable(this.otherStarExports); + if (i === 0) return; + + if (this.allStarExports) { + const { names, dependencyIndices } = moduleGraph.cached( + determineExportAssignments, + this.allStarExports.dependencies + ); + + return { + names, + namesSlice: dependencyIndices[i - 1], + dependencyIndices, + dependencyIndex: i + }; + } + + const { names, dependencyIndices } = moduleGraph.cached( + determineExportAssignments, + this.otherStarExports, + this + ); + + return { + names, + namesSlice: dependencyIndices[i - 1], + dependencyIndices, + dependencyIndex: i + }; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + const mode = this.getMode(moduleGraph, undefined); + + switch (mode.type) { + case "missing": + return; + case "dynamic-reexport": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: true, + from, + canMangle: false, + excludeExports: mode.hidden + ? combine( + /** @type {ExportModeIgnored} */ (mode.ignored), + mode.hidden + ) + : /** @type {ExportModeIgnored} */ (mode.ignored), + hideExports: mode.hidden, + dependencies: [from.module] + }; + } + case "empty-star": + return { + exports: [], + hideExports: mode.hidden, + dependencies: [/** @type {Module} */ (moduleGraph.getModule(this))] + }; + // falls through + case "normal-reexport": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: Array.from( + /** @type {NormalReexportItem[]} */ (mode.items), + item => ({ + name: item.name, + from, + export: item.ids, + hidden: item.hidden + }) + ), + priority: 1, + dependencies: [from.module] + }; + } + case "reexport-dynamic-default": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: [ + { + name: /** @type {string} */ (mode.name), + from, + export: ["default"] + } + ], + priority: 1, + dependencies: [from.module] + }; + } + case "reexport-undefined": + return { + exports: [/** @type {string} */ (mode.name)], + dependencies: [/** @type {Module} */ (moduleGraph.getModule(this))] + }; + case "reexport-fake-namespace-object": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: [ + { + name: /** @type {string} */ (mode.name), + from, + export: null, + exports: [ + { + name: "default", + canMangle: false, + from, + export: null + } + ] + } + ], + priority: 1, + dependencies: [from.module] + }; + } + case "reexport-namespace-object": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: [ + { + name: /** @type {string} */ (mode.name), + from, + export: null + } + ], + priority: 1, + dependencies: [from.module] + }; + } + case "reexport-named-default": { + const from = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(this)); + return { + exports: [ + { + name: /** @type {string} */ (mode.name), + from, + export: ["default"] + } + ], + priority: 1, + dependencies: [from.module] + }; + } + default: + throw new Error(`Unknown mode ${mode.type}`); + } + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {number} effective mode + */ + _getEffectiveExportPresenceLevel(moduleGraph) { + if (this.exportPresenceMode !== ExportPresenceModes.AUTO) + return this.exportPresenceMode; + const module = /** @type {Module} */ (moduleGraph.getParentModule(this)); + return /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule + ? ExportPresenceModes.ERROR + : ExportPresenceModes.WARN; + } + + /** + * Returns warnings + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} warnings + */ + getWarnings(moduleGraph) { + const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); + if (exportsPresence === ExportPresenceModes.WARN) { + return this._getErrors(moduleGraph); + } + return null; + } + + /** + * Returns errors + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} errors + */ + getErrors(moduleGraph) { + const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); + if (exportsPresence === ExportPresenceModes.ERROR) { + return this._getErrors(moduleGraph); + } + return null; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | undefined} errors + */ + _getErrors(moduleGraph) { + const ids = this.getIds(moduleGraph); + let errors = this.getLinkingErrors( + moduleGraph, + ids, + `(reexported as '${this.name}')` + ); + if (ids.length === 0 && this.name === null) { + const potentialConflicts = + this._discoverActiveExportsFromOtherStarExports(moduleGraph); + if (potentialConflicts && potentialConflicts.namesSlice > 0) { + const ownNames = new Set( + potentialConflicts.names.slice( + potentialConflicts.namesSlice, + potentialConflicts.dependencyIndices[ + potentialConflicts.dependencyIndex + ] + ) + ); + const importedModule = moduleGraph.getModule(this); + if (importedModule) { + const exportsInfo = moduleGraph.getExportsInfo(importedModule); + /** @type {Map} */ + const conflicts = new Map(); + for (const exportInfo of exportsInfo.orderedExports) { + if (exportInfo.provided !== true) continue; + if (exportInfo.name === "default") continue; + if (this.activeExports.has(exportInfo.name)) continue; + if (ownNames.has(exportInfo.name)) continue; + const conflictingDependency = findDependencyForName( + potentialConflicts, + exportInfo.name, + this.allStarExports + ? this.allStarExports.dependencies + : [...this.otherStarExports, this] + ); + if (!conflictingDependency) continue; + const target = exportInfo.getTerminalBinding(moduleGraph); + if (!target) continue; + const conflictingModule = + /** @type {Module} */ + (moduleGraph.getModule(conflictingDependency)); + if (conflictingModule === importedModule) continue; + const conflictingExportInfo = moduleGraph.getExportInfo( + conflictingModule, + exportInfo.name + ); + const conflictingTarget = + conflictingExportInfo.getTerminalBinding(moduleGraph); + if (!conflictingTarget) continue; + if (target === conflictingTarget) continue; + const list = conflicts.get(conflictingDependency.request); + if (list === undefined) { + conflicts.set(conflictingDependency.request, [exportInfo.name]); + } else { + list.push(exportInfo.name); + } + } + for (const [request, exports] of conflicts) { + if (!errors) errors = []; + errors.push( + new HarmonyLinkingError( + `The requested module '${ + this.request + }' contains conflicting star exports for the ${ + exports.length > 1 ? "names" : "name" + } ${exports + .map(e => `'${e}'`) + .join(", ")} with the previous requested module '${request}'` + ) + ); + } + } + } + } + return errors; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write, setCircularReference } = context; + + setCircularReference(this); + write(this.ids); + write(this.name); + write(this.activeExports); + write(this.otherStarExports); + write(this.exportPresenceMode); + write(this.allStarExports); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read, setCircularReference } = context; + + setCircularReference(this); + this.ids = read(); + this.name = read(); + this.activeExports = read(); + this.otherStarExports = read(); + this.exportPresenceMode = read(); + this.allStarExports = read(); + + super.deserialize(context); + } +} + +makeSerializable( + HarmonyExportImportedSpecifierDependency, + "webpack/lib/dependencies/HarmonyExportImportedSpecifierDependency" +); + +module.exports = HarmonyExportImportedSpecifierDependency; + +HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedSpecifierDependencyTemplate extends ( + HarmonyImportDependency.Template +) { + /** + * @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 { moduleGraph, runtime, concatenationScope } = templateContext; + + const dep = /** @type {HarmonyExportImportedSpecifierDependency} */ ( + dependency + ); + + const mode = dep.getMode(moduleGraph, runtime); + + if (concatenationScope) { + switch (mode.type) { + case "reexport-undefined": + concatenationScope.registerRawExport( + /** @type {NonNullable} */ (mode.name), + "/* reexport non-default export from non-harmony */ undefined" + ); + } + return; + } + + if (mode.type !== "unused" && mode.type !== "empty-star") { + super.apply(dependency, source, templateContext); + + this._addExportFragments( + templateContext.initFragments, + dep, + mode, + templateContext.module, + moduleGraph, + runtime, + templateContext.runtimeTemplate, + templateContext.runtimeRequirements + ); + } + } + + /** + * @param {InitFragment[]} initFragments target array for init fragments + * @param {HarmonyExportImportedSpecifierDependency} dep dependency + * @param {ExportMode} mode the export mode + * @param {Module} module the current module + * @param {ModuleGraph} moduleGraph the module graph + * @param {RuntimeSpec} runtime the runtime + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {RuntimeRequirements} runtimeRequirements runtime requirements + * @returns {void} + */ + _addExportFragments( + initFragments, + dep, + mode, + module, + moduleGraph, + runtime, + runtimeTemplate, + runtimeRequirements + ) { + const importedModule = /** @type {Module} */ (moduleGraph.getModule(dep)); + const importVar = dep.getImportVar(moduleGraph); + + switch (mode.type) { + case "missing": + case "empty-star": + initFragments.push( + new InitFragment( + "/* empty/unused harmony star reexport */\n", + InitFragment.STAGE_HARMONY_EXPORTS, + 1 + ) + ); + break; + + case "unused": + initFragments.push( + new InitFragment( + `${Template.toNormalComment( + `unused harmony reexport ${mode.name}` + )}\n`, + InitFragment.STAGE_HARMONY_EXPORTS, + 1 + ) + ); + break; + + case "reexport-dynamic-default": + initFragments.push( + this.getReexportFragment( + module, + "reexport default from dynamic", + moduleGraph + .getExportsInfo(module) + .getUsedName(/** @type {string} */ (mode.name), runtime), + importVar, + null, + runtimeRequirements + ) + ); + break; + + case "reexport-fake-namespace-object": + initFragments.push( + ...this.getReexportFakeNamespaceObjectFragments( + module, + moduleGraph + .getExportsInfo(module) + .getUsedName(/** @type {string} */ (mode.name), runtime), + importVar, + mode.fakeType, + runtimeRequirements + ) + ); + break; + + case "reexport-undefined": + initFragments.push( + this.getReexportFragment( + module, + "reexport non-default export from non-harmony", + moduleGraph + .getExportsInfo(module) + .getUsedName(/** @type {string} */ (mode.name), runtime), + "undefined", + "", + runtimeRequirements + ) + ); + break; + + case "reexport-named-default": + initFragments.push( + this.getReexportFragment( + module, + "reexport default export from named module", + moduleGraph + .getExportsInfo(module) + .getUsedName(/** @type {string} */ (mode.name), runtime), + importVar, + "", + runtimeRequirements + ) + ); + break; + + case "reexport-namespace-object": + initFragments.push( + this.getReexportFragment( + module, + "reexport module object", + moduleGraph + .getExportsInfo(module) + .getUsedName(/** @type {string} */ (mode.name), runtime), + importVar, + "", + runtimeRequirements + ) + ); + break; + + case "normal-reexport": + for (const { + name, + ids, + checked, + hidden + } of /** @type {NormalReexportItem[]} */ (mode.items)) { + if (hidden) continue; + if (checked) { + const connection = moduleGraph.getConnection(dep); + const key = `harmony reexport (checked) ${importVar} ${name}`; + const runtimeCondition = dep.weak + ? false + : connection + ? filterRuntime(runtime, r => connection.isTargetActive(r)) + : true; + initFragments.push( + new ConditionalInitFragment( + `/* harmony reexport (checked) */ ${this.getConditionalReexportStatement( + module, + name, + importVar, + ids, + runtimeRequirements + )}`, + moduleGraph.isAsync(importedModule) + ? InitFragment.STAGE_ASYNC_HARMONY_IMPORTS + : InitFragment.STAGE_HARMONY_IMPORTS, + dep.sourceOrder, + key, + runtimeCondition + ) + ); + } else { + initFragments.push( + this.getReexportFragment( + module, + "reexport safe", + moduleGraph.getExportsInfo(module).getUsedName(name, runtime), + importVar, + moduleGraph + .getExportsInfo(importedModule) + .getUsedName(ids, runtime), + runtimeRequirements + ) + ); + } + } + break; + + case "dynamic-reexport": { + const ignored = mode.hidden + ? combine( + /** @type {ExportModeIgnored} */ + (mode.ignored), + mode.hidden + ) + : /** @type {ExportModeIgnored} */ (mode.ignored); + const modern = + runtimeTemplate.supportsConst() && + runtimeTemplate.supportsArrowFunction(); + let content = + "/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};\n" + + `/* harmony reexport (unknown) */ for(${ + modern ? "const" : "var" + } __WEBPACK_IMPORT_KEY__ in ${importVar}) `; + + // Filter out exports which are defined by other exports + // and filter out default export because it cannot be reexported with * + if (ignored.size > 1) { + content += `if(${JSON.stringify( + Array.from(ignored) + )}.indexOf(__WEBPACK_IMPORT_KEY__) < 0) `; + } else if (ignored.size === 1) { + content += `if(__WEBPACK_IMPORT_KEY__ !== ${JSON.stringify( + first(ignored) + )}) `; + } + + content += "__WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = "; + content += modern + ? `() => ${importVar}[__WEBPACK_IMPORT_KEY__]` + : `function(key) { return ${importVar}[key]; }.bind(0, __WEBPACK_IMPORT_KEY__)`; + + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + + const exportsName = module.exportsArgument; + initFragments.push( + new InitFragment( + `${content}\n/* harmony reexport (unknown) */ ${RuntimeGlobals.definePropertyGetters}(${exportsName}, __WEBPACK_REEXPORT_OBJECT__);\n`, + moduleGraph.isAsync(importedModule) + ? InitFragment.STAGE_ASYNC_HARMONY_IMPORTS + : InitFragment.STAGE_HARMONY_IMPORTS, + dep.sourceOrder + ) + ); + break; + } + + default: + throw new Error(`Unknown mode ${mode.type}`); + } + } + + /** + * @param {Module} module the current module + * @param {string} comment comment + * @param {UsedName} key key + * @param {string} name name + * @param {string | string[] | null | false} valueKey value key + * @param {RuntimeRequirements} runtimeRequirements runtime requirements + * @returns {HarmonyExportInitFragment} harmony export init fragment + */ + getReexportFragment( + module, + comment, + key, + name, + valueKey, + runtimeRequirements + ) { + const returnValue = this.getReturnValue(name, valueKey); + + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + + const map = new Map(); + map.set(key, `/* ${comment} */ ${returnValue}`); + + return new HarmonyExportInitFragment(module.exportsArgument, map); + } + + /** + * @param {Module} module module + * @param {string | string[] | false} key key + * @param {string} name name + * @param {number} fakeType fake type + * @param {RuntimeRequirements} runtimeRequirements runtime requirements + * @returns {[InitFragment, HarmonyExportInitFragment]} init fragments + */ + getReexportFakeNamespaceObjectFragments( + module, + key, + name, + fakeType, + runtimeRequirements + ) { + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); + + const map = new Map(); + map.set( + key, + `/* reexport fake namespace object from non-harmony */ ${name}_namespace_cache || (${name}_namespace_cache = ${ + RuntimeGlobals.createFakeNamespaceObject + }(${name}${fakeType ? `, ${fakeType}` : ""}))` + ); + + return [ + new InitFragment( + `var ${name}_namespace_cache;\n`, + InitFragment.STAGE_CONSTANTS, + -1, + `${name}_namespace_cache` + ), + new HarmonyExportInitFragment(module.exportsArgument, map) + ]; + } + + /** + * @param {Module} module module + * @param {string} key key + * @param {string} name name + * @param {string | string[] | false} valueKey value key + * @param {RuntimeRequirements} runtimeRequirements runtime requirements + * @returns {string} result + */ + getConditionalReexportStatement( + module, + key, + name, + valueKey, + runtimeRequirements + ) { + if (valueKey === false) { + return "/* unused export */\n"; + } + + const exportsName = module.exportsArgument; + const returnValue = this.getReturnValue(name, valueKey); + + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + runtimeRequirements.add(RuntimeGlobals.hasOwnProperty); + + return `if(${RuntimeGlobals.hasOwnProperty}(${name}, ${JSON.stringify( + valueKey[0] + )})) ${ + RuntimeGlobals.definePropertyGetters + }(${exportsName}, { ${propertyName( + key + )}: function() { return ${returnValue}; } });\n`; + } + + /** + * @param {string} name name + * @param {null | false | string | string[]} valueKey value key + * @returns {string | undefined} value + */ + getReturnValue(name, valueKey) { + if (valueKey === null) { + return `${name}_default.a`; + } + + if (valueKey === "") { + return name; + } + + if (valueKey === false) { + return "/* unused export */ undefined"; + } + + return `${name}${propertyAccess(valueKey)}`; + } +}; + +class HarmonyStarExportsList { + constructor() { + /** @type {HarmonyExportImportedSpecifierDependency[]} */ + this.dependencies = []; + } + + /** + * @param {HarmonyExportImportedSpecifierDependency} dep dependency + * @returns {void} + */ + push(dep) { + this.dependencies.push(dep); + } + + slice() { + return this.dependencies.slice(); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize({ write, setCircularReference }) { + setCircularReference(this); + write(this.dependencies); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize({ read, setCircularReference }) { + setCircularReference(this); + this.dependencies = read(); + } +} + +makeSerializable( + HarmonyStarExportsList, + "webpack/lib/dependencies/HarmonyExportImportedSpecifierDependency", + "HarmonyStarExportsList" +); + +module.exports.HarmonyStarExportsList = HarmonyStarExportsList; diff --git a/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js b/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js new file mode 100644 index 00000000000..8125cc2db8b --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js @@ -0,0 +1,177 @@ +/* + 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 { first } = require("../util/SetHelpers"); +const { propertyName } = require("../util/propertyName"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ + +/** + * @param {Iterable} iterable iterable strings + * @returns {string} result + */ +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; +}; + +const EMPTY_MAP = new Map(); +const EMPTY_SET = new Set(); + +/** + * @extends {InitFragment} Context + */ +class HarmonyExportInitFragment extends InitFragment { + /** + * @param {string} exportsArgument the exports identifier + * @param {Map} exportMap mapping from used name to exposed variable name + * @param {Set} unusedExports list of unused export names + */ + constructor( + exportsArgument, + exportMap = EMPTY_MAP, + unusedExports = EMPTY_SET + ) { + super(undefined, InitFragment.STAGE_HARMONY_EXPORTS, 1, "harmony-exports"); + this.exportsArgument = exportsArgument; + this.exportMap = exportMap; + this.unusedExports = unusedExports; + } + + /** + * @param {HarmonyExportInitFragment[]} fragments all fragments to merge + * @returns {HarmonyExportInitFragment} merged fragment + */ + mergeAll(fragments) { + let exportMap; + let exportMapOwned = false; + let unusedExports; + let unusedExportsOwned = false; + + for (const fragment of fragments) { + if (fragment.exportMap.size !== 0) { + if (exportMap === undefined) { + exportMap = fragment.exportMap; + exportMapOwned = false; + } else { + if (!exportMapOwned) { + exportMap = new Map(exportMap); + exportMapOwned = true; + } + for (const [key, value] of fragment.exportMap) { + if (!exportMap.has(key)) exportMap.set(key, value); + } + } + } + if (fragment.unusedExports.size !== 0) { + if (unusedExports === undefined) { + unusedExports = fragment.unusedExports; + unusedExportsOwned = false; + } else { + if (!unusedExportsOwned) { + unusedExports = new Set(unusedExports); + unusedExportsOwned = true; + } + for (const value of fragment.unusedExports) { + unusedExports.add(value); + } + } + } + } + return new HarmonyExportInitFragment( + this.exportsArgument, + exportMap, + unusedExports + ); + } + + /** + * @param {HarmonyExportInitFragment} other other + * @returns {HarmonyExportInitFragment} merged result + */ + merge(other) { + let exportMap; + if (this.exportMap.size === 0) { + exportMap = other.exportMap; + } else if (other.exportMap.size === 0) { + exportMap = this.exportMap; + } else { + exportMap = new Map(other.exportMap); + for (const [key, value] of this.exportMap) { + if (!exportMap.has(key)) exportMap.set(key, value); + } + } + let unusedExports; + if (this.unusedExports.size === 0) { + unusedExports = other.unusedExports; + } else if (other.unusedExports.size === 0) { + unusedExports = this.unusedExports; + } else { + unusedExports = new Set(other.unusedExports); + for (const value of this.unusedExports) { + unusedExports.add(value); + } + } + return new HarmonyExportInitFragment( + this.exportsArgument, + exportMap, + unusedExports + ); + } + + /** + * @param {GenerateContext} context context + * @returns {string | Source | undefined} the source code that will be included as initialization code + */ + getContent({ runtimeTemplate, runtimeRequirements }) { + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + + const unusedPart = + this.unusedExports.size > 1 + ? `/* unused harmony exports ${joinIterableWithComma( + this.unusedExports + )} */\n` + : this.unusedExports.size > 0 + ? `/* unused harmony export ${first(this.unusedExports)} */\n` + : ""; + const definitions = []; + const orderedExportMap = Array.from(this.exportMap).sort(([a], [b]) => + a < b ? -1 : 1 + ); + for (const [key, value] of orderedExportMap) { + definitions.push( + `\n/* harmony export */ ${propertyName( + key + )}: ${runtimeTemplate.returningFunction(value)}` + ); + } + const definePart = + this.exportMap.size > 0 + ? `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${ + this.exportsArgument + }, {${definitions.join(",")}\n/* harmony export */ });\n` + : ""; + return `${definePart}${unusedPart}`; + } +} + +module.exports = HarmonyExportInitFragment; diff --git a/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js new file mode 100644 index 00000000000..4e742935942 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js @@ -0,0 +1,123 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class HarmonyExportSpecifierDependency extends NullDependency { + /** + * @param {TODO} id id + * @param {TODO} name name + */ + constructor(id, name) { + super(); + this.id = id; + this.name = name; + } + + get type() { + return "harmony export specifier"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + return { + exports: [this.name], + priority: 1, + terminalBinding: true, + dependencies: undefined + }; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return false; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.id); + write(this.name); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.id = read(); + this.name = read(); + super.deserialize(context); + } +} + +makeSerializable( + HarmonyExportSpecifierDependency, + "webpack/lib/dependencies/HarmonyExportSpecifierDependency" +); + +HarmonyExportSpecifierDependency.Template = class HarmonyExportSpecifierDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { module, moduleGraph, initFragments, runtime, concatenationScope } + ) { + const dep = /** @type {HarmonyExportSpecifierDependency} */ (dependency); + if (concatenationScope) { + concatenationScope.registerExport(dep.name, dep.id); + return; + } + const used = moduleGraph + .getExportsInfo(module) + .getUsedName(dep.name, runtime); + if (!used) { + const set = new Set(); + set.add(dep.name || "namespace"); + initFragments.push( + new HarmonyExportInitFragment(module.exportsArgument, undefined, set) + ); + return; + } + + const map = new Map(); + map.set(used, `/* binding */ ${dep.id}`); + initFragments.push( + new HarmonyExportInitFragment(module.exportsArgument, map, undefined) + ); + } +}; + +module.exports = HarmonyExportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExports.js b/webpack-lib/lib/dependencies/HarmonyExports.js new file mode 100644 index 00000000000..1f6e6d4acb9 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyExports.js @@ -0,0 +1,46 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); + +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ + +/** @type {WeakMap} */ +const parserStateExportsState = new WeakMap(); + +/** + * @param {ParserState} parserState parser state + * @param {boolean} isStrictHarmony strict harmony mode should be enabled + * @returns {void} + */ +module.exports.enable = (parserState, isStrictHarmony) => { + const value = parserStateExportsState.get(parserState); + if (value === false) return; + parserStateExportsState.set(parserState, true); + if (value !== true) { + const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); + buildMeta.exportsType = "namespace"; + const buildInfo = /** @type {BuildInfo} */ (parserState.module.buildInfo); + buildInfo.strict = true; + buildInfo.exportsArgument = RuntimeGlobals.exports; + if (isStrictHarmony) { + buildMeta.strictHarmonyModule = true; + buildInfo.moduleArgument = "__webpack_module__"; + } + } +}; + +/** + * @param {ParserState} parserState parser state + * @returns {boolean} true, when enabled + */ +module.exports.isEnabled = parserState => { + const value = parserStateExportsState.get(parserState); + return value === true; +}; diff --git a/webpack-lib/lib/dependencies/HarmonyImportDependency.js b/webpack-lib/lib/dependencies/HarmonyImportDependency.js new file mode 100644 index 00000000000..e9f26fc9459 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyImportDependency.js @@ -0,0 +1,382 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ConditionalInitFragment = require("../ConditionalInitFragment"); +const Dependency = require("../Dependency"); +const HarmonyLinkingError = require("../HarmonyLinkingError"); +const InitFragment = require("../InitFragment"); +const Template = require("../Template"); +const AwaitDependenciesInitFragment = require("../async-modules/AwaitDependenciesInitFragment"); +const { filterRuntime, mergeRuntime } = require("../util/runtime"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ExportsInfo")} ExportsInfo */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +const ExportPresenceModes = { + NONE: /** @type {0} */ (0), + WARN: /** @type {1} */ (1), + AUTO: /** @type {2} */ (2), + ERROR: /** @type {3} */ (3), + /** + * @param {string | false} str param + * @returns {0 | 1 | 2 | 3} result + */ + fromUserOption(str) { + switch (str) { + case "error": + return ExportPresenceModes.ERROR; + case "warn": + return ExportPresenceModes.WARN; + case "auto": + return ExportPresenceModes.AUTO; + case false: + return ExportPresenceModes.NONE; + default: + throw new Error(`Invalid export presence value ${str}`); + } + } +}; + +class HarmonyImportDependency extends ModuleDependency { + /** + * @param {string} request request string + * @param {number} sourceOrder source order + * @param {ImportAttributes=} attributes import attributes + */ + constructor(request, sourceOrder, attributes) { + super(request); + this.sourceOrder = sourceOrder; + this.assertions = attributes; + } + + get category() { + return "esm"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return Dependency.NO_EXPORTS_REFERENCED; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {string} name of the variable for the import + */ + getImportVar(moduleGraph) { + const module = /** @type {Module} */ (moduleGraph.getParentModule(this)); + const meta = /** @type {TODO} */ (moduleGraph.getMeta(module)); + let importVarMap = meta.importVarMap; + if (!importVarMap) meta.importVarMap = importVarMap = new Map(); + let importVar = importVarMap.get( + /** @type {Module} */ (moduleGraph.getModule(this)) + ); + if (importVar) return importVar; + importVar = `${Template.toIdentifier( + `${this.userRequest}` + )}__WEBPACK_IMPORTED_MODULE_${importVarMap.size}__`; + importVarMap.set( + /** @type {Module} */ (moduleGraph.getModule(this)), + importVar + ); + return importVar; + } + + /** + * @param {boolean} update create new variables or update existing one + * @param {DependencyTemplateContext} templateContext the template context + * @returns {[string, string]} the import statement and the compat statement + */ + getImportStatement( + update, + { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } + ) { + return runtimeTemplate.importStatement({ + update, + module: /** @type {Module} */ (moduleGraph.getModule(this)), + chunkGraph, + importVar: this.getImportVar(moduleGraph), + request: this.request, + originModule: module, + runtimeRequirements + }); + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @param {string[]} ids imported ids + * @param {string} additionalMessage extra info included in the error message + * @returns {WebpackError[] | undefined} errors + */ + getLinkingErrors(moduleGraph, ids, additionalMessage) { + const importedModule = moduleGraph.getModule(this); + // ignore errors for missing or failed modules + if (!importedModule || importedModule.getNumberOfErrors() > 0) { + return; + } + + const parentModule = + /** @type {Module} */ + (moduleGraph.getParentModule(this)); + const exportsType = importedModule.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule + ); + if (exportsType === "namespace" || exportsType === "default-with-named") { + if (ids.length === 0) { + return; + } + + if ( + (exportsType !== "default-with-named" || ids[0] !== "default") && + moduleGraph.isExportProvided(importedModule, ids) === false + ) { + // We are sure that it's not provided + + // Try to provide detailed info in the error message + let pos = 0; + let exportsInfo = moduleGraph.getExportsInfo(importedModule); + while (pos < ids.length && exportsInfo) { + const id = ids[pos++]; + const exportInfo = exportsInfo.getReadOnlyExportInfo(id); + if (exportInfo.provided === false) { + // We are sure that it's not provided + const providedExports = exportsInfo.getProvidedExports(); + const moreInfo = !Array.isArray(providedExports) + ? " (possible exports unknown)" + : providedExports.length === 0 + ? " (module has no exports)" + : ` (possible exports: ${providedExports.join(", ")})`; + return [ + new HarmonyLinkingError( + `export ${ids + .slice(0, pos) + .map(id => `'${id}'`) + .join(".")} ${additionalMessage} was not found in '${ + this.userRequest + }'${moreInfo}` + ) + ]; + } + exportsInfo = + /** @type {ExportsInfo} */ + (exportInfo.getNestedExportsInfo()); + } + + // General error message + return [ + new HarmonyLinkingError( + `export ${ids + .map(id => `'${id}'`) + .join(".")} ${additionalMessage} was not found in '${ + this.userRequest + }'` + ) + ]; + } + } + switch (exportsType) { + case "default-only": + // It's has only a default export + if (ids.length > 0 && ids[0] !== "default") { + // In strict harmony modules we only support the default export + return [ + new HarmonyLinkingError( + `Can't import the named export ${ids + .map(id => `'${id}'`) + .join( + "." + )} ${additionalMessage} from default-exporting module (only default export is available)` + ) + ]; + } + break; + case "default-with-named": + // It has a default export and named properties redirect + // In some cases we still want to warn here + if ( + ids.length > 0 && + ids[0] !== "default" && + /** @type {BuildMeta} */ + (importedModule.buildMeta).defaultObject === "redirect-warn" + ) { + // For these modules only the default export is supported + return [ + new HarmonyLinkingError( + `Should not import the named export ${ids + .map(id => `'${id}'`) + .join( + "." + )} ${additionalMessage} from default-exporting module (only default export is available soon)` + ) + ]; + } + break; + } + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.sourceOrder); + write(this.assertions); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.sourceOrder = read(); + this.assertions = read(); + super.deserialize(context); + } +} + +module.exports = HarmonyImportDependency; + +/** @type {WeakMap>} */ +const importEmittedMap = new WeakMap(); + +HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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 dep = /** @type {HarmonyImportDependency} */ (dependency); + const { module, chunkGraph, moduleGraph, runtime } = templateContext; + + const connection = moduleGraph.getConnection(dep); + if (connection && !connection.isTargetActive(runtime)) return; + + const referencedModule = connection && connection.module; + + if ( + connection && + connection.weak && + referencedModule && + chunkGraph.getModuleId(referencedModule) === null + ) { + // in weak references, module might not be in any chunk + // but that's ok, we don't need that logic in this case + return; + } + + const moduleKey = referencedModule + ? referencedModule.identifier() + : dep.request; + const key = `harmony import ${moduleKey}`; + + const runtimeCondition = dep.weak + ? false + : connection + ? filterRuntime(runtime, r => connection.isTargetActive(r)) + : true; + + if (module && referencedModule) { + let emittedModules = importEmittedMap.get(module); + if (emittedModules === undefined) { + emittedModules = new WeakMap(); + importEmittedMap.set(module, emittedModules); + } + let mergedRuntimeCondition = runtimeCondition; + const oldRuntimeCondition = emittedModules.get(referencedModule) || false; + if (oldRuntimeCondition !== false && mergedRuntimeCondition !== true) { + if (mergedRuntimeCondition === false || oldRuntimeCondition === true) { + mergedRuntimeCondition = oldRuntimeCondition; + } else { + mergedRuntimeCondition = mergeRuntime( + oldRuntimeCondition, + mergedRuntimeCondition + ); + } + } + emittedModules.set(referencedModule, mergedRuntimeCondition); + } + + const importStatement = dep.getImportStatement(false, templateContext); + if ( + referencedModule && + templateContext.moduleGraph.isAsync(referencedModule) + ) { + templateContext.initFragments.push( + new ConditionalInitFragment( + importStatement[0], + InitFragment.STAGE_HARMONY_IMPORTS, + dep.sourceOrder, + key, + runtimeCondition + ) + ); + templateContext.initFragments.push( + new AwaitDependenciesInitFragment( + new Set([dep.getImportVar(templateContext.moduleGraph)]) + ) + ); + templateContext.initFragments.push( + new ConditionalInitFragment( + importStatement[1], + InitFragment.STAGE_ASYNC_HARMONY_IMPORTS, + dep.sourceOrder, + `${key} compat`, + runtimeCondition + ) + ); + } else { + templateContext.initFragments.push( + new ConditionalInitFragment( + importStatement[0] + importStatement[1], + InitFragment.STAGE_HARMONY_IMPORTS, + dep.sourceOrder, + key, + runtimeCondition + ) + ); + } + } + + /** + * @param {Module} module the module + * @param {Module} referencedModule the referenced module + * @returns {RuntimeSpec | boolean} runtimeCondition in which this import has been emitted + */ + static getImportEmittedRuntime(module, referencedModule) { + const emittedModules = importEmittedMap.get(module); + if (emittedModules === undefined) return false; + return emittedModules.get(referencedModule) || false; + } +}; + +module.exports.ExportPresenceModes = ExportPresenceModes; diff --git a/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js new file mode 100644 index 00000000000..e7c556339e0 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js @@ -0,0 +1,370 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin"); +const { getImportAttributes } = require("../javascript/JavascriptParser"); +const InnerGraph = require("../optimize/InnerGraph"); +const ConstDependency = require("./ConstDependency"); +const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); +const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); +const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency"); +const HarmonyExports = require("./HarmonyExports"); +const { ExportPresenceModes } = require("./HarmonyImportDependency"); +const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); +const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); + +/** @typedef {import("estree").Identifier} Identifier */ +/** @typedef {import("estree").Literal} Literal */ +/** @typedef {import("estree").MemberExpression} MemberExpression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").Property} Property */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ +/** @typedef {import("../javascript/JavascriptParser").ExportAllDeclaration} ExportAllDeclaration */ +/** @typedef {import("../javascript/JavascriptParser").ExportNamedDeclaration} ExportNamedDeclaration */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").ImportDeclaration} ImportDeclaration */ +/** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */ +/** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */ +/** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */ + +const harmonySpecifierTag = Symbol("harmony import"); + +/** + * @typedef {object} HarmonySettings + * @property {string[]} ids + * @property {string} source + * @property {number} sourceOrder + * @property {string} name + * @property {boolean} await + * @property {Record | undefined} assertions + */ + +module.exports = class HarmonyImportDependencyParserPlugin { + /** + * @param {JavascriptParserOptions} options options + */ + constructor(options) { + this.exportPresenceMode = + options.importExportsPresence !== undefined + ? ExportPresenceModes.fromUserOption(options.importExportsPresence) + : options.exportsPresence !== undefined + ? ExportPresenceModes.fromUserOption(options.exportsPresence) + : options.strictExportPresence + ? ExportPresenceModes.ERROR + : ExportPresenceModes.AUTO; + this.strictThisContextOnImports = options.strictThisContextOnImports; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const { exportPresenceMode } = this; + + /** + * @param {string[]} members members + * @param {boolean[]} membersOptionals members Optionals + * @returns {string[]} a non optional part + */ + function getNonOptionalPart(members, membersOptionals) { + let i = 0; + while (i < members.length && membersOptionals[i] === false) i++; + return i !== members.length ? members.slice(0, i) : members; + } + + /** + * @param {TODO} node member expression + * @param {number} count count + * @returns {TODO} member expression + */ + function getNonOptionalMemberChain(node, count) { + while (count--) node = node.object; + return node; + } + + parser.hooks.isPure + .for("Identifier") + .tap("HarmonyImportDependencyParserPlugin", expression => { + const expr = /** @type {Identifier} */ (expression); + if ( + parser.isVariableDefined(expr.name) || + parser.getTagData(expr.name, harmonySpecifierTag) + ) { + return true; + } + }); + parser.hooks.import.tap( + "HarmonyImportDependencyParserPlugin", + (statement, source) => { + parser.state.lastHarmonyImportOrder = + (parser.state.lastHarmonyImportOrder || 0) + 1; + const clearDep = new ConstDependency( + parser.isAsiPosition(/** @type {Range} */ (statement.range)[0]) + ? ";" + : "", + /** @type {Range} */ (statement.range) + ); + clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); + parser.state.module.addPresentationalDependency(clearDep); + parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]); + const attributes = getImportAttributes(statement); + const sideEffectDep = new HarmonyImportSideEffectDependency( + /** @type {string} */ (source), + parser.state.lastHarmonyImportOrder, + attributes + ); + sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc); + parser.state.module.addDependency(sideEffectDep); + return true; + } + ); + parser.hooks.importSpecifier.tap( + "HarmonyImportDependencyParserPlugin", + (statement, source, id, name) => { + const ids = id === null ? [] : [id]; + parser.tagVariable(name, harmonySpecifierTag, { + name, + source, + ids, + sourceOrder: parser.state.lastHarmonyImportOrder, + assertions: getImportAttributes(statement) + }); + return true; + } + ); + parser.hooks.binaryExpression.tap( + "HarmonyImportDependencyParserPlugin", + expression => { + if (expression.operator !== "in") return; + + const leftPartEvaluated = parser.evaluateExpression(expression.left); + if (leftPartEvaluated.couldHaveSideEffects()) return; + const leftPart = leftPartEvaluated.asString(); + if (!leftPart) return; + + const rightPart = parser.evaluateExpression(expression.right); + if (!rightPart.isIdentifier()) return; + + const rootInfo = rightPart.rootInfo; + if ( + typeof rootInfo === "string" || + !rootInfo || + !rootInfo.tagInfo || + rootInfo.tagInfo.tag !== harmonySpecifierTag + ) + return; + const settings = rootInfo.tagInfo.data; + const members = + /** @type {(() => string[])} */ + (rightPart.getMembers)(); + const dep = new HarmonyEvaluatedImportSpecifierDependency( + settings.source, + settings.sourceOrder, + settings.ids.concat(members).concat([leftPart]), + settings.name, + /** @type {Range} */ (expression.range), + settings.assertions, + "in" + ); + dep.directImport = members.length === 0; + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expression.range)[0] + ); + dep.loc = /** @type {DependencyLocation} */ (expression.loc); + parser.state.module.addDependency(dep); + InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); + return true; + } + ); + parser.hooks.expression + .for(harmonySpecifierTag) + .tap("HarmonyImportDependencyParserPlugin", expr => { + const settings = /** @type {HarmonySettings} */ (parser.currentTagData); + const dep = new HarmonyImportSpecifierDependency( + settings.source, + settings.sourceOrder, + settings.ids, + settings.name, + /** @type {Range} */ (expr.range), + exportPresenceMode, + settings.assertions, + [] + ); + dep.referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(expr); + dep.shorthand = parser.scope.inShorthand; + dep.directImport = true; + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.call = parser.scope.inTaggedTemplateTag; + parser.state.module.addDependency(dep); + InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); + return true; + }); + parser.hooks.expressionMemberChain + .for(harmonySpecifierTag) + .tap( + "HarmonyImportDependencyParserPlugin", + (expression, members, membersOptionals, memberRanges) => { + const settings = /** @type {HarmonySettings} */ ( + parser.currentTagData + ); + const nonOptionalMembers = getNonOptionalPart( + members, + membersOptionals + ); + /** @type {Range[]} */ + const ranges = memberRanges.slice( + 0, + memberRanges.length - (members.length - nonOptionalMembers.length) + ); + const expr = + nonOptionalMembers !== members + ? getNonOptionalMemberChain( + expression, + members.length - nonOptionalMembers.length + ) + : expression; + const ids = settings.ids.concat(nonOptionalMembers); + const dep = new HarmonyImportSpecifierDependency( + settings.source, + settings.sourceOrder, + ids, + settings.name, + /** @type {Range} */ (expr.range), + exportPresenceMode, + settings.assertions, + ranges + ); + dep.referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(expr); + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); + return true; + } + ); + parser.hooks.callMemberChain + .for(harmonySpecifierTag) + .tap( + "HarmonyImportDependencyParserPlugin", + (expression, members, membersOptionals, memberRanges) => { + const { arguments: args, callee } = expression; + const settings = /** @type {HarmonySettings} */ ( + parser.currentTagData + ); + const nonOptionalMembers = getNonOptionalPart( + members, + membersOptionals + ); + /** @type {Range[]} */ + const ranges = memberRanges.slice( + 0, + memberRanges.length - (members.length - nonOptionalMembers.length) + ); + const expr = + nonOptionalMembers !== members + ? getNonOptionalMemberChain( + callee, + members.length - nonOptionalMembers.length + ) + : callee; + const ids = settings.ids.concat(nonOptionalMembers); + const dep = new HarmonyImportSpecifierDependency( + settings.source, + settings.sourceOrder, + ids, + settings.name, + /** @type {Range} */ (expr.range), + exportPresenceMode, + settings.assertions, + ranges + ); + dep.directImport = members.length === 0; + dep.call = true; + dep.asiSafe = !parser.isAsiPosition( + /** @type {Range} */ (expr.range)[0] + ); + // only in case when we strictly follow the spec we need a special case here + dep.namespaceObjectAsContext = + members.length > 0 && + /** @type {boolean} */ (this.strictThisContextOnImports); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + if (args) parser.walkExpressions(args); + InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); + return true; + } + ); + const { hotAcceptCallback, hotAcceptWithoutCallback } = + HotModuleReplacementPlugin.getParserHooks(parser); + hotAcceptCallback.tap( + "HarmonyImportDependencyParserPlugin", + (expr, requests) => { + if (!HarmonyExports.isEnabled(parser.state)) { + // This is not a harmony module, skip it + return; + } + const dependencies = requests.map(request => { + const dep = new HarmonyAcceptImportDependency(request); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + return dep; + }); + if (dependencies.length > 0) { + const dep = new HarmonyAcceptDependency( + /** @type {Range} */ + (expr.range), + dependencies, + true + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + } + } + ); + hotAcceptWithoutCallback.tap( + "HarmonyImportDependencyParserPlugin", + (expr, requests) => { + if (!HarmonyExports.isEnabled(parser.state)) { + // This is not a harmony module, skip it + return; + } + const dependencies = requests.map(request => { + const dep = new HarmonyAcceptImportDependency(request); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + return dep; + }); + if (dependencies.length > 0) { + const dep = new HarmonyAcceptDependency( + /** @type {Range} */ + (expr.range), + dependencies, + false + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + } + } + ); + } +}; + +module.exports.harmonySpecifierTag = harmonySpecifierTag; diff --git a/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js b/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js new file mode 100644 index 00000000000..bf691745ed3 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js @@ -0,0 +1,86 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const HarmonyImportDependency = require("./HarmonyImportDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class HarmonyImportSideEffectDependency extends HarmonyImportDependency { + /** + * @param {string} request the request string + * @param {number} sourceOrder source order + * @param {ImportAttributes=} attributes import attributes + */ + constructor(request, sourceOrder, attributes) { + super(request, sourceOrder, attributes); + } + + get type() { + return "harmony side effect evaluation"; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return connection => { + const refModule = connection.resolvedModule; + if (!refModule) return true; + return refModule.getSideEffectsConnectionState(moduleGraph); + }; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + const refModule = moduleGraph.getModule(this); + if (!refModule) return true; + return refModule.getSideEffectsConnectionState(moduleGraph); + } +} + +makeSerializable( + HarmonyImportSideEffectDependency, + "webpack/lib/dependencies/HarmonyImportSideEffectDependency" +); + +HarmonyImportSideEffectDependency.Template = class HarmonyImportSideEffectDependencyTemplate extends ( + HarmonyImportDependency.Template +) { + /** + * @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 { moduleGraph, concatenationScope } = templateContext; + if (concatenationScope) { + const module = /** @type {Module} */ (moduleGraph.getModule(dependency)); + if (concatenationScope.isModuleInScope(module)) { + return; + } + } + super.apply(dependency, source, templateContext); + } +}; + +module.exports = HarmonyImportSideEffectDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js new file mode 100644 index 00000000000..3ea38c111f2 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -0,0 +1,468 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const Template = require("../Template"); +const { + getDependencyUsedByExportsCondition +} = require("../optimize/InnerGraph"); +const { getTrimmedIdsAndRange } = require("../util/chainedImports"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const HarmonyImportDependency = require("./HarmonyImportDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +const idsSymbol = Symbol("HarmonyImportSpecifierDependency.ids"); + +const { ExportPresenceModes } = HarmonyImportDependency; + +class HarmonyImportSpecifierDependency extends HarmonyImportDependency { + /** + * @param {string} request request + * @param {number} sourceOrder source order + * @param {string[]} ids ids + * @param {string} name name + * @param {Range} range range + * @param {TODO} exportPresenceMode export presence mode + * @param {ImportAttributes | undefined} attributes import attributes + * @param {Range[] | undefined} idRanges ranges for members of ids; the two arrays are right-aligned + */ + constructor( + request, + sourceOrder, + ids, + name, + range, + exportPresenceMode, + attributes, + idRanges // TODO webpack 6 make this non-optional. It must always be set to properly trim ids. + ) { + super(request, sourceOrder, attributes); + this.ids = ids; + this.name = name; + this.range = range; + this.idRanges = idRanges; + this.exportPresenceMode = exportPresenceMode; + this.namespaceObjectAsContext = false; + this.call = undefined; + this.directImport = undefined; + this.shorthand = undefined; + this.asiSafe = undefined; + /** @type {Set | boolean | undefined} */ + this.usedByExports = undefined; + /** @type {Set | undefined} */ + this.referencedPropertiesInDestructuring = undefined; + } + + // TODO webpack 6 remove + get id() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + // TODO webpack 6 remove + getId() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + // TODO webpack 6 remove + setId() { + throw new Error("id was renamed to ids and type changed to string[]"); + } + + get type() { + return "harmony import specifier"; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {string[]} the imported ids + */ + getIds(moduleGraph) { + const meta = moduleGraph.getMetaIfExisting(this); + if (meta === undefined) return this.ids; + const ids = meta[/** @type {keyof object} */ (idsSymbol)]; + return ids !== undefined ? ids : this.ids; + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {string[]} ids the imported ids + * @returns {void} + */ + setIds(moduleGraph, ids) { + /** @type {TODO} */ + (moduleGraph.getMeta(this))[idsSymbol] = ids; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return getDependencyUsedByExportsCondition( + this, + this.usedByExports, + moduleGraph + ); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return false; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + let ids = this.getIds(moduleGraph); + if (ids.length === 0) return this._getReferencedExportsInDestructuring(); + let namespaceObjectAsContext = this.namespaceObjectAsContext; + if (ids[0] === "default") { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(this)); + const importedModule = + /** @type {Module} */ + (moduleGraph.getModule(this)); + switch ( + importedModule.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ + (selfModule.buildMeta).strictHarmonyModule + ) + ) { + case "default-only": + case "default-with-named": + if (ids.length === 1) + return this._getReferencedExportsInDestructuring(); + ids = ids.slice(1); + namespaceObjectAsContext = true; + break; + case "dynamic": + return Dependency.EXPORTS_OBJECT_REFERENCED; + } + } + + if ( + this.call && + !this.directImport && + (namespaceObjectAsContext || ids.length > 1) + ) { + if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED; + ids = ids.slice(0, -1); + } + + return this._getReferencedExportsInDestructuring(ids); + } + + /** + * @param {string[]=} ids ids + * @returns {string[][]} referenced exports + */ + _getReferencedExportsInDestructuring(ids) { + if (this.referencedPropertiesInDestructuring) { + /** @type {string[][]} */ + const refs = []; + for (const { id } of this.referencedPropertiesInDestructuring) { + refs.push(ids ? ids.concat([id]) : [id]); + } + return refs; + } + return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {number} effective mode + */ + _getEffectiveExportPresenceLevel(moduleGraph) { + if (this.exportPresenceMode !== ExportPresenceModes.AUTO) + return this.exportPresenceMode; + const buildMeta = + /** @type {BuildMeta} */ + ( + /** @type {Module} */ + (moduleGraph.getParentModule(this)).buildMeta + ); + return buildMeta.strictHarmonyModule + ? ExportPresenceModes.ERROR + : ExportPresenceModes.WARN; + } + + /** + * Returns warnings + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} warnings + */ + getWarnings(moduleGraph) { + const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); + if (exportsPresence === ExportPresenceModes.WARN) { + return this._getErrors(moduleGraph); + } + return null; + } + + /** + * Returns errors + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} errors + */ + getErrors(moduleGraph) { + const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); + if (exportsPresence === ExportPresenceModes.ERROR) { + return this._getErrors(moduleGraph); + } + return null; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | undefined} errors + */ + _getErrors(moduleGraph) { + const ids = this.getIds(moduleGraph); + return this.getLinkingErrors( + moduleGraph, + ids, + `(imported as '${this.name}')` + ); + } + + /** + * 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 0; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.ids); + write(this.name); + write(this.range); + write(this.idRanges); + write(this.exportPresenceMode); + write(this.namespaceObjectAsContext); + write(this.call); + write(this.directImport); + write(this.shorthand); + write(this.asiSafe); + write(this.usedByExports); + write(this.referencedPropertiesInDestructuring); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.ids = read(); + this.name = read(); + this.range = read(); + this.idRanges = read(); + this.exportPresenceMode = read(); + this.namespaceObjectAsContext = read(); + this.call = read(); + this.directImport = read(); + this.shorthand = read(); + this.asiSafe = read(); + this.usedByExports = read(); + this.referencedPropertiesInDestructuring = read(); + super.deserialize(context); + } +} + +makeSerializable( + HarmonyImportSpecifierDependency, + "webpack/lib/dependencies/HarmonyImportSpecifierDependency" +); + +HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends ( + HarmonyImportDependency.Template +) { + /** + * @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 dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency); + const { moduleGraph, runtime } = templateContext; + const connection = moduleGraph.getConnection(dep); + // Skip rendering depending when dependency is conditional + if (connection && !connection.isTargetActive(runtime)) return; + + const ids = dep.getIds(moduleGraph); + const { + trimmedRange: [trimmedRangeStart, trimmedRangeEnd], + trimmedIds + } = getTrimmedIdsAndRange(ids, dep.range, dep.idRanges, moduleGraph, dep); + + const exportExpr = this._getCodeForIds( + dep, + source, + templateContext, + trimmedIds + ); + if (dep.shorthand) { + source.insert(trimmedRangeEnd, `: ${exportExpr}`); + } else { + source.replace(trimmedRangeStart, trimmedRangeEnd - 1, exportExpr); + } + + if (dep.referencedPropertiesInDestructuring) { + let prefixedIds = ids; + + if (ids[0] === "default") { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(dep)); + const importedModule = + /** @type {Module} */ + (moduleGraph.getModule(dep)); + const exportsType = importedModule.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ + (selfModule.buildMeta).strictHarmonyModule + ); + if ( + (exportsType === "default-only" || + exportsType === "default-with-named") && + ids.length >= 1 + ) { + prefixedIds = ids.slice(1); + } + } + + for (const { + id, + shorthand, + range + } of dep.referencedPropertiesInDestructuring) { + const concatedIds = prefixedIds.concat([id]); + const module = /** @type {Module} */ (moduleGraph.getModule(dep)); + const used = moduleGraph + .getExportsInfo(module) + .getUsedName(concatedIds, runtime); + if (!used) return; + const newName = used[used.length - 1]; + const name = concatedIds[concatedIds.length - 1]; + if (newName === name) continue; + + const comment = `${Template.toNormalComment(name)} `; + const key = comment + JSON.stringify(newName); + source.replace( + /** @type {Range} */ + (range)[0], + /** @type {Range} */ + (range)[1] - 1, + shorthand ? `${key}: ${name}` : `${key}` + ); + } + } + } + + /** + * @param {HarmonyImportSpecifierDependency} dep dependency + * @param {ReplaceSource} source source + * @param {DependencyTemplateContext} templateContext context + * @param {string[]} ids ids + * @returns {string} generated code + */ + _getCodeForIds(dep, source, templateContext, ids) { + const { moduleGraph, module, runtime, concatenationScope } = + templateContext; + const connection = moduleGraph.getConnection(dep); + let exportExpr; + if ( + connection && + concatenationScope && + concatenationScope.isModuleInScope(connection.module) + ) { + if (ids.length === 0) { + exportExpr = concatenationScope.createModuleReference( + connection.module, + { + asiSafe: dep.asiSafe + } + ); + } else if (dep.namespaceObjectAsContext && ids.length === 1) { + exportExpr = + concatenationScope.createModuleReference(connection.module, { + asiSafe: dep.asiSafe + }) + propertyAccess(ids); + } else { + exportExpr = concatenationScope.createModuleReference( + connection.module, + { + ids, + call: dep.call, + directImport: dep.directImport, + asiSafe: dep.asiSafe + } + ); + } + } else { + super.apply(dep, source, templateContext); + + const { runtimeTemplate, initFragments, runtimeRequirements } = + templateContext; + + exportExpr = runtimeTemplate.exportFromImport({ + moduleGraph, + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + request: dep.request, + exportName: ids, + originModule: module, + asiSafe: dep.shorthand ? true : dep.asiSafe, + isCall: dep.call, + callContext: !dep.directImport, + defaultInterop: true, + importVar: dep.getImportVar(moduleGraph), + initFragments, + runtime, + runtimeRequirements + }); + } + return exportExpr; + } +}; + +module.exports = HarmonyImportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js b/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js new file mode 100644 index 00000000000..a3bbd98de82 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js @@ -0,0 +1,149 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); +const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); +const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency"); +const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency"); +const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency"); +const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency"); +const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency"); +const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency"); +const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); +const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const HarmonyDetectionParserPlugin = require("./HarmonyDetectionParserPlugin"); +const HarmonyExportDependencyParserPlugin = require("./HarmonyExportDependencyParserPlugin"); +const HarmonyImportDependencyParserPlugin = require("./HarmonyImportDependencyParserPlugin"); +const HarmonyTopLevelThisParserPlugin = require("./HarmonyTopLevelThisParserPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "HarmonyModulesPlugin"; + +/** @typedef {{ topLevelAwait?: boolean }} HarmonyModulesPluginOptions */ + +class HarmonyModulesPlugin { + /** + * @param {HarmonyModulesPluginOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyTemplates.set( + HarmonyCompatibilityDependency, + new HarmonyCompatibilityDependency.Template() + ); + + compilation.dependencyFactories.set( + HarmonyImportSideEffectDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + HarmonyImportSideEffectDependency, + new HarmonyImportSideEffectDependency.Template() + ); + + compilation.dependencyFactories.set( + HarmonyImportSpecifierDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + HarmonyImportSpecifierDependency, + new HarmonyImportSpecifierDependency.Template() + ); + + compilation.dependencyFactories.set( + HarmonyEvaluatedImportSpecifierDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + HarmonyEvaluatedImportSpecifierDependency, + new HarmonyEvaluatedImportSpecifierDependency.Template() + ); + + compilation.dependencyTemplates.set( + HarmonyExportHeaderDependency, + new HarmonyExportHeaderDependency.Template() + ); + + compilation.dependencyTemplates.set( + HarmonyExportExpressionDependency, + new HarmonyExportExpressionDependency.Template() + ); + + compilation.dependencyTemplates.set( + HarmonyExportSpecifierDependency, + new HarmonyExportSpecifierDependency.Template() + ); + + compilation.dependencyFactories.set( + HarmonyExportImportedSpecifierDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + HarmonyExportImportedSpecifierDependency, + new HarmonyExportImportedSpecifierDependency.Template() + ); + + compilation.dependencyTemplates.set( + HarmonyAcceptDependency, + new HarmonyAcceptDependency.Template() + ); + + compilation.dependencyFactories.set( + HarmonyAcceptImportDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + HarmonyAcceptImportDependency, + new HarmonyAcceptImportDependency.Template() + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + // TODO webpack 6: rename harmony to esm or module + if (parserOptions.harmony !== undefined && !parserOptions.harmony) + return; + + new HarmonyDetectionParserPlugin(this.options).apply(parser); + new HarmonyImportDependencyParserPlugin(parserOptions).apply(parser); + new HarmonyExportDependencyParserPlugin(parserOptions).apply(parser); + new HarmonyTopLevelThisParserPlugin().apply(parser); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, handler); + } + ); + } +} +module.exports = HarmonyModulesPlugin; diff --git a/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js new file mode 100644 index 00000000000..b8ba1848649 --- /dev/null +++ b/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js @@ -0,0 +1,39 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const ConstDependency = require("./ConstDependency"); +const HarmonyExports = require("./HarmonyExports"); + +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class HarmonyTopLevelThisParserPlugin { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.expression + .for("this") + .tap("HarmonyTopLevelThisParserPlugin", node => { + if (!parser.scope.topLevelScope) return; + if (HarmonyExports.isEnabled(parser.state)) { + const dep = new ConstDependency( + "undefined", + /** @type {Range} */ (node.range), + null + ); + dep.loc = /** @type {DependencyLocation} */ (node.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + }); + } +} + +module.exports = HarmonyTopLevelThisParserPlugin; diff --git a/webpack-lib/lib/dependencies/ImportContextDependency.js b/webpack-lib/lib/dependencies/ImportContextDependency.js new file mode 100644 index 00000000000..a1811e722bc --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportContextDependency.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ImportContextDependency extends ContextDependency { + /** + * @param {TODO} options options + * @param {Range} range range + * @param {Range} valueRange value range + */ + constructor(options, range, valueRange) { + super(options); + + this.range = range; + this.valueRange = valueRange; + } + + get type() { + return `import() context ${this.options.mode}`; + } + + get category() { + return "esm"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.valueRange); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.valueRange = read(); + + super.deserialize(context); + } +} + +makeSerializable( + ImportContextDependency, + "webpack/lib/dependencies/ImportContextDependency" +); + +ImportContextDependency.Template = ContextDependencyTemplateAsRequireCall; + +module.exports = ImportContextDependency; diff --git a/webpack-lib/lib/dependencies/ImportDependency.js b/webpack-lib/lib/dependencies/ImportDependency.js new file mode 100644 index 00000000000..1368d405a10 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportDependency.js @@ -0,0 +1,139 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class ImportDependency extends ModuleDependency { + /** + * @param {string} request the request + * @param {Range} range expression range + * @param {(string[][] | null)=} referencedExports list of referenced exports + * @param {ImportAttributes=} attributes import attributes + */ + constructor(request, range, referencedExports, attributes) { + super(request); + this.range = range; + this.referencedExports = referencedExports; + this.assertions = attributes; + } + + get type() { + return "import()"; + } + + get category() { + return "esm"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + if (!this.referencedExports) return Dependency.EXPORTS_OBJECT_REFERENCED; + const refs = []; + for (const referencedExport of this.referencedExports) { + if (referencedExport[0] === "default") { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(this)); + const importedModule = + /** @type {Module} */ + (moduleGraph.getModule(this)); + const exportsType = importedModule.getExportsType( + moduleGraph, + /** @type {BuildMeta} */ + (selfModule.buildMeta).strictHarmonyModule + ); + if ( + exportsType === "default-only" || + exportsType === "default-with-named" + ) { + return Dependency.EXPORTS_OBJECT_REFERENCED; + } + } + refs.push({ + name: referencedExport, + canMangle: false + }); + } + return refs; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + context.write(this.range); + context.write(this.referencedExports); + context.write(this.assertions); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + this.range = context.read(); + this.referencedExports = context.read(); + this.assertions = context.read(); + super.deserialize(context); + } +} + +makeSerializable(ImportDependency, "webpack/lib/dependencies/ImportDependency"); + +ImportDependency.Template = class ImportDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, + { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ImportDependency} */ (dependency); + const block = /** @type {AsyncDependenciesBlock} */ ( + moduleGraph.getParentBlock(dep) + ); + const content = runtimeTemplate.moduleNamespacePromise({ + chunkGraph, + block, + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + request: dep.request, + strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, + message: "import()", + runtimeRequirements + }); + + source.replace(dep.range[0], dep.range[1] - 1, content); + } +}; + +module.exports = ImportDependency; diff --git a/webpack-lib/lib/dependencies/ImportEagerDependency.js b/webpack-lib/lib/dependencies/ImportEagerDependency.js new file mode 100644 index 00000000000..dd607302029 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportEagerDependency.js @@ -0,0 +1,74 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ImportDependency = require("./ImportDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ImportEagerDependency extends ImportDependency { + /** + * @param {string} request the request + * @param {Range} range expression range + * @param {(string[][] | null)=} referencedExports list of referenced exports + * @param {ImportAttributes=} attributes import attributes + */ + constructor(request, range, referencedExports, attributes) { + super(request, range, referencedExports, attributes); + } + + get type() { + return "import() eager"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ImportEagerDependency, + "webpack/lib/dependencies/ImportEagerDependency" +); + +ImportEagerDependency.Template = class ImportEagerDependencyTemplate extends ( + ImportDependency.Template +) { + /** + * @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, + { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ImportEagerDependency} */ (dependency); + const content = runtimeTemplate.moduleNamespacePromise({ + chunkGraph, + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + request: dep.request, + strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, + message: "import() eager", + runtimeRequirements + }); + + source.replace(dep.range[0], dep.range[1] - 1, content); + } +}; + +module.exports = ImportEagerDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextDependency.js b/webpack-lib/lib/dependencies/ImportMetaContextDependency.js new file mode 100644 index 00000000000..ee27ee1573f --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaContextDependency.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ + +class ImportMetaContextDependency extends ContextDependency { + /** + * @param {ContextDependencyOptions} options options + * @param {Range} range range + */ + constructor(options, range) { + super(options); + + this.range = range; + } + + get category() { + return "esm"; + } + + get type() { + return `import.meta.webpackContext ${this.options.mode}`; + } +} + +makeSerializable( + ImportMetaContextDependency, + "webpack/lib/dependencies/ImportMetaContextDependency" +); + +ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId; + +module.exports = ImportMetaContextDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js b/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js new file mode 100644 index 00000000000..753f0430e8d --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js @@ -0,0 +1,301 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); +const { + evaluateToIdentifier +} = require("../javascript/JavascriptParserHelpers"); +const ImportMetaContextDependency = require("./ImportMetaContextDependency"); + +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").Property} Property */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../ContextModule").ContextModuleOptions} ContextModuleOptions */ +/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {Pick&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */ + +/** + * @param {TODO} prop property + * @param {string} expect except message + * @returns {WebpackError} error + */ +function createPropertyParseError(prop, expect) { + return createError( + `Parsing import.meta.webpackContext options failed. Unknown value for property ${JSON.stringify( + prop.key.name + )}, expected type ${expect}.`, + prop.value.loc + ); +} + +/** + * @param {string} msg message + * @param {DependencyLocation} loc location + * @returns {WebpackError} error + */ +function createError(msg, loc) { + const error = new WebpackError(msg); + error.name = "ImportMetaContextError"; + error.loc = loc; + return error; +} + +module.exports = class ImportMetaContextDependencyParserPlugin { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.evaluateIdentifier + .for("import.meta.webpackContext") + .tap("ImportMetaContextDependencyParserPlugin", expr => + evaluateToIdentifier( + "import.meta.webpackContext", + "import.meta", + () => ["webpackContext"], + true + )(expr) + ); + parser.hooks.call + .for("import.meta.webpackContext") + .tap("ImportMetaContextDependencyParserPlugin", expr => { + if (expr.arguments.length < 1 || expr.arguments.length > 2) return; + const [directoryNode, optionsNode] = expr.arguments; + if (optionsNode && optionsNode.type !== "ObjectExpression") return; + const requestExpr = parser.evaluateExpression( + /** @type {Expression} */ (directoryNode) + ); + if (!requestExpr.isString()) return; + const request = /** @type {string} */ (requestExpr.string); + const errors = []; + let regExp = /^\.\/.*$/; + let recursive = true; + /** @type {ContextModuleOptions["mode"]} */ + let mode = "sync"; + /** @type {ContextModuleOptions["include"]} */ + let include; + /** @type {ContextModuleOptions["exclude"]} */ + let exclude; + /** @type {RawChunkGroupOptions} */ + const groupOptions = {}; + /** @type {ContextModuleOptions["chunkName"]} */ + let chunkName; + /** @type {ContextModuleOptions["referencedExports"]} */ + let exports; + if (optionsNode) { + for (const prop of /** @type {ObjectExpression} */ (optionsNode) + .properties) { + if (prop.type !== "Property" || prop.key.type !== "Identifier") { + errors.push( + createError( + "Parsing import.meta.webpackContext options failed.", + /** @type {DependencyLocation} */ (optionsNode.loc) + ) + ); + break; + } + switch (prop.key.name) { + case "regExp": { + const regExpExpr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + regExp = /** @type {RegExp} */ (regExpExpr.regExp); + } + break; + } + case "include": { + const regExpExpr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + include = regExpExpr.regExp; + } + break; + } + case "exclude": { + const regExpExpr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!regExpExpr.isRegExp()) { + errors.push(createPropertyParseError(prop, "RegExp")); + } else { + exclude = regExpExpr.regExp; + } + break; + } + case "mode": { + const modeExpr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!modeExpr.isString()) { + errors.push(createPropertyParseError(prop, "string")); + } else { + mode = /** @type {ContextModuleOptions["mode"]} */ ( + modeExpr.string + ); + } + break; + } + case "chunkName": { + const expr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!expr.isString()) { + errors.push(createPropertyParseError(prop, "string")); + } else { + chunkName = expr.string; + } + break; + } + case "exports": { + const expr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (expr.isString()) { + exports = [[/** @type {string} */ (expr.string)]]; + } else if (expr.isArray()) { + const items = + /** @type {BasicEvaluatedExpression[]} */ + (expr.items); + if ( + items.every(i => { + if (!i.isArray()) return false; + const innerItems = + /** @type {BasicEvaluatedExpression[]} */ (i.items); + return innerItems.every(i => i.isString()); + }) + ) { + exports = []; + for (const i1 of items) { + /** @type {string[]} */ + const export_ = []; + for (const i2 of /** @type {BasicEvaluatedExpression[]} */ ( + i1.items + )) { + export_.push(/** @type {string} */ (i2.string)); + } + exports.push(export_); + } + } else { + errors.push( + createPropertyParseError(prop, "string|string[][]") + ); + } + } else { + errors.push( + createPropertyParseError(prop, "string|string[][]") + ); + } + break; + } + case "prefetch": { + const expr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (expr.isBoolean()) { + groupOptions.prefetchOrder = 0; + } else if (expr.isNumber()) { + groupOptions.prefetchOrder = expr.number; + } else { + errors.push(createPropertyParseError(prop, "boolean|number")); + } + break; + } + case "preload": { + const expr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (expr.isBoolean()) { + groupOptions.preloadOrder = 0; + } else if (expr.isNumber()) { + groupOptions.preloadOrder = expr.number; + } else { + errors.push(createPropertyParseError(prop, "boolean|number")); + } + break; + } + case "fetchPriority": { + const expr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if ( + expr.isString() && + ["high", "low", "auto"].includes( + /** @type {string} */ (expr.string) + ) + ) { + groupOptions.fetchPriority = + /** @type {RawChunkGroupOptions["fetchPriority"]} */ ( + expr.string + ); + } else { + errors.push( + createPropertyParseError(prop, '"high"|"low"|"auto"') + ); + } + break; + } + case "recursive": { + const recursiveExpr = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (!recursiveExpr.isBoolean()) { + errors.push(createPropertyParseError(prop, "boolean")); + } else { + recursive = /** @type {boolean} */ (recursiveExpr.bool); + } + break; + } + default: + errors.push( + createError( + `Parsing import.meta.webpackContext options failed. Unknown property ${JSON.stringify( + prop.key.name + )}.`, + /** @type {DependencyLocation} */ (optionsNode.loc) + ) + ); + } + } + } + if (errors.length) { + for (const error of errors) parser.state.current.addError(error); + return; + } + + const dep = new ImportMetaContextDependency( + { + request, + include, + exclude, + recursive, + regExp, + groupOptions, + chunkName, + referencedExports: exports, + mode, + category: "esm" + }, + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + }); + } +}; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js b/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js new file mode 100644 index 00000000000..ed9ac05da53 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js @@ -0,0 +1,72 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const ContextElementDependency = require("./ContextElementDependency"); +const ImportMetaContextDependency = require("./ImportMetaContextDependency"); +const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "ImportMetaContextPlugin"; + +class ImportMetaContextPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyFactories.set( + ImportMetaContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + ImportMetaContextDependency, + new ImportMetaContextDependency.Template() + ); + compilation.dependencyFactories.set( + ContextElementDependency, + normalModuleFactory + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if ( + parserOptions.importMetaContext !== undefined && + !parserOptions.importMetaContext + ) + return; + + new ImportMetaContextDependencyParserPlugin().apply(parser); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, handler); + } + ); + } +} + +module.exports = ImportMetaContextPlugin; diff --git a/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js b/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js new file mode 100644 index 00000000000..70d8199338d --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ImportMetaHotAcceptDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + this.range = range; + this.weak = true; + } + + get type() { + return "import.meta.webpackHot.accept"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ImportMetaHotAcceptDependency, + "webpack/lib/dependencies/ImportMetaHotAcceptDependency" +); + +ImportMetaHotAcceptDependency.Template = ModuleDependencyTemplateAsId; + +module.exports = ImportMetaHotAcceptDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js b/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js new file mode 100644 index 00000000000..c6c35a250ce --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ImportMetaHotDeclineDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + + this.range = range; + this.weak = true; + } + + get type() { + return "import.meta.webpackHot.decline"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ImportMetaHotDeclineDependency, + "webpack/lib/dependencies/ImportMetaHotDeclineDependency" +); + +ImportMetaHotDeclineDependency.Template = ModuleDependencyTemplateAsId; + +module.exports = ImportMetaHotDeclineDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaPlugin.js b/webpack-lib/lib/dependencies/ImportMetaPlugin.js new file mode 100644 index 00000000000..ff9231d21d0 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportMetaPlugin.js @@ -0,0 +1,253 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { pathToFileURL } = require("url"); +const ModuleDependencyWarning = require("../ModuleDependencyWarning"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const Template = require("../Template"); +const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); +const { + evaluateToIdentifier, + toConstantDependency, + evaluateToString, + evaluateToNumber +} = require("../javascript/JavascriptParserHelpers"); +const memoize = require("../util/memoize"); +const propertyAccess = require("../util/propertyAccess"); +const ConstDependency = require("./ConstDependency"); + +/** @typedef {import("estree").MemberExpression} MemberExpression */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const getCriticalDependencyWarning = memoize(() => + require("./CriticalDependencyWarning") +); + +const PLUGIN_NAME = "ImportMetaPlugin"; + +class ImportMetaPlugin { + /** + * @param {Compiler} compiler compiler + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + /** + * @param {NormalModule} module module + * @returns {string} file url + */ + const getUrl = module => pathToFileURL(module.resource).toString(); + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const parserHandler = (parser, { importMeta }) => { + if (importMeta === false) { + const { importMetaName } = compilation.outputOptions; + if (importMetaName === "import.meta") return; + + parser.hooks.expression + .for("import.meta") + .tap(PLUGIN_NAME, metaProperty => { + const dep = new ConstDependency( + /** @type {string} */ (importMetaName), + /** @type {Range} */ (metaProperty.range) + ); + dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + return; + } + + // import.meta direct + const webpackVersion = Number.parseInt( + require("../../package.json").version, + 10 + ); + const importMetaUrl = () => + JSON.stringify(getUrl(parser.state.module)); + const importMetaWebpackVersion = () => JSON.stringify(webpackVersion); + /** + * @param {string[]} members members + * @returns {string} error message + */ + const importMetaUnknownProperty = members => + `${Template.toNormalComment( + `unsupported import.meta.${members.join(".")}` + )} undefined${propertyAccess(members, 1)}`; + parser.hooks.typeof + .for("import.meta") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("object")) + ); + parser.hooks.expression + .for("import.meta") + .tap(PLUGIN_NAME, metaProperty => { + const referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(metaProperty); + if (!referencedPropertiesInDestructuring) { + const CriticalDependencyWarning = + getCriticalDependencyWarning(); + parser.state.module.addWarning( + new ModuleDependencyWarning( + parser.state.module, + new CriticalDependencyWarning( + "Accessing import.meta directly is unsupported (only property access or destructuring is supported)" + ), + /** @type {DependencyLocation} */ (metaProperty.loc) + ) + ); + const dep = new ConstDependency( + `${ + parser.isAsiPosition( + /** @type {Range} */ (metaProperty.range)[0] + ) + ? ";" + : "" + }({})`, + /** @type {Range} */ (metaProperty.range) + ); + dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + } + + let str = ""; + for (const { id: prop } of referencedPropertiesInDestructuring) { + switch (prop) { + case "url": + str += `url: ${importMetaUrl()},`; + break; + case "webpack": + str += `webpack: ${importMetaWebpackVersion()},`; + break; + default: + str += `[${JSON.stringify( + prop + )}]: ${importMetaUnknownProperty([prop])},`; + break; + } + } + const dep = new ConstDependency( + `({${str}})`, + /** @type {Range} */ (metaProperty.range) + ); + dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + parser.hooks.evaluateTypeof + .for("import.meta") + .tap(PLUGIN_NAME, evaluateToString("object")); + parser.hooks.evaluateIdentifier.for("import.meta").tap( + PLUGIN_NAME, + evaluateToIdentifier("import.meta", "import.meta", () => [], true) + ); + + // import.meta.url + parser.hooks.typeof + .for("import.meta.url") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("string")) + ); + parser.hooks.expression + .for("import.meta.url") + .tap(PLUGIN_NAME, expr => { + const dep = new ConstDependency( + importMetaUrl(), + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + parser.hooks.evaluateTypeof + .for("import.meta.url") + .tap(PLUGIN_NAME, evaluateToString("string")); + parser.hooks.evaluateIdentifier + .for("import.meta.url") + .tap(PLUGIN_NAME, expr => + new BasicEvaluatedExpression() + .setString(getUrl(parser.state.module)) + .setRange(/** @type {Range} */ (expr.range)) + ); + + // import.meta.webpack + parser.hooks.typeof + .for("import.meta.webpack") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("number")) + ); + parser.hooks.expression + .for("import.meta.webpack") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, importMetaWebpackVersion()) + ); + parser.hooks.evaluateTypeof + .for("import.meta.webpack") + .tap(PLUGIN_NAME, evaluateToString("number")); + parser.hooks.evaluateIdentifier + .for("import.meta.webpack") + .tap(PLUGIN_NAME, evaluateToNumber(webpackVersion)); + + // Unknown properties + parser.hooks.unhandledExpressionMemberChain + .for("import.meta") + .tap(PLUGIN_NAME, (expr, members) => { + const dep = new ConstDependency( + importMetaUnknownProperty(members), + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + parser.hooks.evaluate + .for("MemberExpression") + .tap(PLUGIN_NAME, expression => { + const expr = /** @type {MemberExpression} */ (expression); + if ( + expr.object.type === "MetaProperty" && + expr.object.meta.name === "import" && + expr.object.property.name === "meta" && + expr.property.type === + (expr.computed ? "Literal" : "Identifier") + ) { + return new BasicEvaluatedExpression() + .setUndefined() + .setRange(/** @type {Range} */ (expr.range)); + } + }); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, parserHandler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, parserHandler); + } + ); + } +} + +module.exports = ImportMetaPlugin; diff --git a/webpack-lib/lib/dependencies/ImportParserPlugin.js b/webpack-lib/lib/dependencies/ImportParserPlugin.js new file mode 100644 index 00000000000..52fdb9317ca --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportParserPlugin.js @@ -0,0 +1,339 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const { getImportAttributes } = require("../javascript/JavascriptParser"); +const ContextDependencyHelpers = require("./ContextDependencyHelpers"); +const ImportContextDependency = require("./ImportContextDependency"); +const ImportDependency = require("./ImportDependency"); +const ImportEagerDependency = require("./ImportEagerDependency"); +const ImportWeakDependency = require("./ImportWeakDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {import("../ContextModule").ContextMode} ContextMode */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ImportParserPlugin { + /** + * @param {JavascriptParserOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + /** + * @template T + * @param {Iterable} enumerable enumerable + * @returns {T[][]} array of array + */ + const exportsFromEnumerable = enumerable => + Array.from(enumerable, e => [e]); + parser.hooks.importCall.tap("ImportParserPlugin", expr => { + const param = parser.evaluateExpression(expr.source); + + let chunkName = null; + let mode = /** @type {ContextMode} */ (this.options.dynamicImportMode); + let include = null; + let exclude = null; + /** @type {string[][] | null} */ + let exports = null; + /** @type {RawChunkGroupOptions} */ + const groupOptions = {}; + + const { + dynamicImportPreload, + dynamicImportPrefetch, + dynamicImportFetchPriority + } = this.options; + if (dynamicImportPreload !== undefined && dynamicImportPreload !== false) + groupOptions.preloadOrder = + dynamicImportPreload === true ? 0 : dynamicImportPreload; + if ( + dynamicImportPrefetch !== undefined && + dynamicImportPrefetch !== false + ) + groupOptions.prefetchOrder = + dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch; + if ( + dynamicImportFetchPriority !== undefined && + dynamicImportFetchPriority !== false + ) + groupOptions.fetchPriority = dynamicImportFetchPriority; + + const { options: importOptions, errors: commentErrors } = + parser.parseCommentOptions(/** @type {Range} */ (expr.range)); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + /** @type {DependencyLocation} */ (comment.loc) + ) + ); + } + } + + if (importOptions) { + if (importOptions.webpackIgnore !== undefined) { + if (typeof importOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else if (importOptions.webpackIgnore) { + // Do not instrument `import()` if `webpackIgnore` is `true` + return false; + } + } + if (importOptions.webpackChunkName !== undefined) { + if (typeof importOptions.webpackChunkName !== "string") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + chunkName = importOptions.webpackChunkName; + } + } + if (importOptions.webpackMode !== undefined) { + if (typeof importOptions.webpackMode !== "string") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + mode = /** @type {ContextMode} */ (importOptions.webpackMode); + } + } + if (importOptions.webpackPrefetch !== undefined) { + if (importOptions.webpackPrefetch === true) { + groupOptions.prefetchOrder = 0; + } else if (typeof importOptions.webpackPrefetch === "number") { + groupOptions.prefetchOrder = importOptions.webpackPrefetch; + } else { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + } + if (importOptions.webpackPreload !== undefined) { + if (importOptions.webpackPreload === true) { + groupOptions.preloadOrder = 0; + } else if (typeof importOptions.webpackPreload === "number") { + groupOptions.preloadOrder = importOptions.webpackPreload; + } else { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + } + if (importOptions.webpackFetchPriority !== undefined) { + if ( + typeof importOptions.webpackFetchPriority === "string" && + ["high", "low", "auto"].includes(importOptions.webpackFetchPriority) + ) { + groupOptions.fetchPriority = + /** @type {"low" | "high" | "auto"} */ + (importOptions.webpackFetchPriority); + } else { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + } + if (importOptions.webpackInclude !== undefined) { + if ( + !importOptions.webpackInclude || + !(importOptions.webpackInclude instanceof RegExp) + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + include = importOptions.webpackInclude; + } + } + if (importOptions.webpackExclude !== undefined) { + if ( + !importOptions.webpackExclude || + !(importOptions.webpackExclude instanceof RegExp) + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + exclude = importOptions.webpackExclude; + } + } + if (importOptions.webpackExports !== undefined) { + if ( + !( + typeof importOptions.webpackExports === "string" || + (Array.isArray(importOptions.webpackExports) && + /** @type {string[]} */ (importOptions.webpackExports).every( + item => typeof item === "string" + )) + ) + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else if (typeof importOptions.webpackExports === "string") { + exports = [[importOptions.webpackExports]]; + } else { + exports = exportsFromEnumerable(importOptions.webpackExports); + } + } + } + + if ( + mode !== "lazy" && + mode !== "lazy-once" && + mode !== "eager" && + mode !== "weak" + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + mode = "lazy"; + } + + const referencedPropertiesInDestructuring = + parser.destructuringAssignmentPropertiesFor(expr); + if (referencedPropertiesInDestructuring) { + if (exports) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + "`webpackExports` could not be used with destructuring assignment.", + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + exports = exportsFromEnumerable( + [...referencedPropertiesInDestructuring].map(({ id }) => id) + ); + } + + if (param.isString()) { + const attributes = getImportAttributes(expr); + + if (mode === "eager") { + const dep = new ImportEagerDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range), + exports, + attributes + ); + parser.state.current.addDependency(dep); + } else if (mode === "weak") { + const dep = new ImportWeakDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range), + exports, + attributes + ); + parser.state.current.addDependency(dep); + } else { + const depBlock = new AsyncDependenciesBlock( + { + ...groupOptions, + name: chunkName + }, + /** @type {DependencyLocation} */ (expr.loc), + param.string + ); + const dep = new ImportDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range), + exports, + attributes + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + depBlock.addDependency(dep); + parser.state.current.addBlock(depBlock); + } + return true; + } + if (mode === "weak") { + mode = "async-weak"; + } + const dep = ContextDependencyHelpers.create( + ImportContextDependency, + /** @type {Range} */ (expr.range), + param, + expr, + this.options, + { + chunkName, + groupOptions, + include, + exclude, + mode, + namespaceObject: /** @type {BuildMeta} */ ( + parser.state.module.buildMeta + ).strictHarmonyModule + ? "strict" + : true, + typePrefix: "import()", + category: "esm", + referencedExports: exports, + attributes: getImportAttributes(expr) + }, + parser + ); + if (!dep) return; + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + }); + } +} + +module.exports = ImportParserPlugin; diff --git a/webpack-lib/lib/dependencies/ImportPlugin.js b/webpack-lib/lib/dependencies/ImportPlugin.js new file mode 100644 index 00000000000..4ee51a8f748 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportPlugin.js @@ -0,0 +1,96 @@ +/* + 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 ImportContextDependency = require("./ImportContextDependency"); +const ImportDependency = require("./ImportDependency"); +const ImportEagerDependency = require("./ImportEagerDependency"); +const ImportParserPlugin = require("./ImportParserPlugin"); +const ImportWeakDependency = require("./ImportWeakDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "ImportPlugin"; + +class ImportPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyFactories.set( + ImportDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + ImportDependency, + new ImportDependency.Template() + ); + + compilation.dependencyFactories.set( + ImportEagerDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + ImportEagerDependency, + new ImportEagerDependency.Template() + ); + + compilation.dependencyFactories.set( + ImportWeakDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + ImportWeakDependency, + new ImportWeakDependency.Template() + ); + + compilation.dependencyFactories.set( + ImportContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + ImportContextDependency, + new ImportContextDependency.Template() + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.import !== undefined && !parserOptions.import) + return; + + new ImportParserPlugin(parserOptions).apply(parser); + }; + + 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 = ImportPlugin; diff --git a/webpack-lib/lib/dependencies/ImportWeakDependency.js b/webpack-lib/lib/dependencies/ImportWeakDependency.js new file mode 100644 index 00000000000..0ed3b053f96 --- /dev/null +++ b/webpack-lib/lib/dependencies/ImportWeakDependency.js @@ -0,0 +1,72 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ImportDependency = require("./ImportDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ImportWeakDependency extends ImportDependency { + /** + * @param {string} request the request + * @param {Range} range expression range + * @param {(string[][] | null)=} referencedExports list of referenced exports + * @param {ImportAttributes=} attributes import attributes + */ + constructor(request, range, referencedExports, attributes) { + super(request, range, referencedExports, attributes); + this.weak = true; + } + + get type() { + return "import() weak"; + } +} + +makeSerializable( + ImportWeakDependency, + "webpack/lib/dependencies/ImportWeakDependency" +); + +ImportWeakDependency.Template = class ImportDependencyTemplate extends ( + ImportDependency.Template +) { + /** + * @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, + { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ImportWeakDependency} */ (dependency); + const content = runtimeTemplate.moduleNamespacePromise({ + chunkGraph, + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + request: dep.request, + strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, + message: "import() weak", + weak: true, + runtimeRequirements + }); + + source.replace(dep.range[0], dep.range[1] - 1, content); + } +}; + +module.exports = ImportWeakDependency; diff --git a/webpack-lib/lib/dependencies/JsonExportsDependency.js b/webpack-lib/lib/dependencies/JsonExportsDependency.js new file mode 100644 index 00000000000..07d022b90a4 --- /dev/null +++ b/webpack-lib/lib/dependencies/JsonExportsDependency.js @@ -0,0 +1,117 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ExportSpec} ExportSpec */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../json/JsonData")} JsonData */ +/** @typedef {import("../json/JsonData").RawJsonData} RawJsonData */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +/** + * @param {number} exportsDepth exportsDepth + * @returns {((data: RawJsonData, curDepth?: number) => ExportSpec[] | undefined)} value + */ +const getExportsWithDepth = exportsDepth => + function getExportsFromData(data, curDepth = 1) { + if (curDepth > exportsDepth) return undefined; + if (data && typeof data === "object") { + if (Array.isArray(data)) { + return data.length < 100 + ? data.map((item, idx) => ({ + name: `${idx}`, + canMangle: true, + exports: getExportsFromData(item, curDepth + 1) + })) + : undefined; + } + const exports = []; + for (const key of Object.keys(data)) { + exports.push({ + name: key, + canMangle: true, + exports: getExportsFromData(data[key], curDepth + 1) + }); + } + return exports; + } + return undefined; + }; + +class JsonExportsDependency extends NullDependency { + /** + * @param {JsonData} data json data + * @param {number} exportsDepth the depth of json exports to analyze + */ + constructor(data, exportsDepth) { + super(); + this.data = data; + this.exportsDepth = exportsDepth; + } + + get type() { + return "json exports"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + return { + exports: getExportsWithDepth(this.exportsDepth)( + this.data && /** @type {RawJsonData} */ (this.data.get()) + ), + dependencies: undefined + }; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + this.data.updateHash(hash); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.data); + write(this.exportsDepth); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.data = read(); + this.exportsDepth = read(); + super.deserialize(context); + } +} + +makeSerializable( + JsonExportsDependency, + "webpack/lib/dependencies/JsonExportsDependency" +); + +module.exports = JsonExportsDependency; diff --git a/webpack-lib/lib/dependencies/LoaderDependency.js b/webpack-lib/lib/dependencies/LoaderDependency.js new file mode 100644 index 00000000000..7ae66b3d2b0 --- /dev/null +++ b/webpack-lib/lib/dependencies/LoaderDependency.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class LoaderDependency extends ModuleDependency { + /** + * @param {string} request request string + */ + constructor(request) { + super(request); + } + + get type() { + return "loader"; + } + + get category() { + return "loader"; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return false; + } +} + +module.exports = LoaderDependency; diff --git a/webpack-lib/lib/dependencies/LoaderImportDependency.js b/webpack-lib/lib/dependencies/LoaderImportDependency.js new file mode 100644 index 00000000000..94937922d60 --- /dev/null +++ b/webpack-lib/lib/dependencies/LoaderImportDependency.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class LoaderImportDependency extends ModuleDependency { + /** + * @param {string} request request string + */ + constructor(request) { + super(request); + this.weak = true; + } + + get type() { + return "loader import"; + } + + get category() { + return "loaderImport"; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return false; + } +} + +module.exports = LoaderImportDependency; diff --git a/webpack-lib/lib/dependencies/LoaderPlugin.js b/webpack-lib/lib/dependencies/LoaderPlugin.js new file mode 100644 index 00000000000..3612cbeac8c --- /dev/null +++ b/webpack-lib/lib/dependencies/LoaderPlugin.js @@ -0,0 +1,292 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NormalModule = require("../NormalModule"); +const LazySet = require("../util/LazySet"); +const LoaderDependency = require("./LoaderDependency"); +const LoaderImportDependency = require("./LoaderImportDependency"); + +/** @typedef {import("../../declarations/LoaderContext").LoaderPluginLoaderContext} LoaderPluginLoaderContext */ +/** @typedef {import("../Compilation").DepConstructor} DepConstructor */ +/** @typedef {import("../Compilation").ExecuteModuleResult} ExecuteModuleResult */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ + +/** + * @callback ImportModuleCallback + * @param {(Error | null)=} err error object + * @param {any=} exports exports of the evaluated module + */ + +/** + * @typedef {object} ImportModuleOptions + * @property {string=} layer the target layer + * @property {string=} publicPath the target public path + * @property {string=} baseUri target base uri + */ + +class LoaderPlugin { + /** + * @param {object} options options + */ + constructor(options = {}) {} + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "LoaderPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + LoaderDependency, + normalModuleFactory + ); + compilation.dependencyFactories.set( + LoaderImportDependency, + normalModuleFactory + ); + } + ); + + compiler.hooks.compilation.tap("LoaderPlugin", compilation => { + const moduleGraph = compilation.moduleGraph; + NormalModule.getCompilationHooks(compilation).loader.tap( + "LoaderPlugin", + loaderContext => { + loaderContext.loadModule = (request, callback) => { + const dep = new LoaderDependency(request); + dep.loc = { + name: request + }; + const factory = compilation.dependencyFactories.get( + /** @type {DepConstructor} */ (dep.constructor) + ); + if (factory === undefined) { + return callback( + new Error( + `No module factory available for dependency type: ${dep.constructor.name}` + ) + ); + } + const oldFactorizeQueueContext = + compilation.factorizeQueue.getContext(); + compilation.factorizeQueue.setContext("load-module"); + const oldAddModuleQueueContext = + compilation.addModuleQueue.getContext(); + compilation.addModuleQueue.setContext("load-module"); + compilation.buildQueue.increaseParallelism(); + compilation.handleModuleCreation( + { + factory, + dependencies: [dep], + originModule: + /** @type {NormalModule} */ + (loaderContext._module), + context: loaderContext.context, + recursive: false + }, + err => { + compilation.factorizeQueue.setContext(oldFactorizeQueueContext); + compilation.addModuleQueue.setContext(oldAddModuleQueueContext); + compilation.buildQueue.decreaseParallelism(); + if (err) { + return callback(err); + } + const referencedModule = moduleGraph.getModule(dep); + if (!referencedModule) { + return callback(new Error("Cannot load the module")); + } + if (referencedModule.getNumberOfErrors() > 0) { + return callback( + new Error("The loaded module contains errors") + ); + } + const moduleSource = referencedModule.originalSource(); + if (!moduleSource) { + return callback( + new Error( + "The module created for a LoaderDependency must have an original source" + ) + ); + } + let map; + let source; + if (moduleSource.sourceAndMap) { + const sourceAndMap = moduleSource.sourceAndMap(); + map = sourceAndMap.map; + source = sourceAndMap.source; + } else { + map = moduleSource.map(); + source = moduleSource.source(); + } + const fileDependencies = new LazySet(); + const contextDependencies = new LazySet(); + const missingDependencies = new LazySet(); + const buildDependencies = new LazySet(); + referencedModule.addCacheDependencies( + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + ); + + for (const d of fileDependencies) { + loaderContext.addDependency(d); + } + for (const d of contextDependencies) { + loaderContext.addContextDependency(d); + } + for (const d of missingDependencies) { + loaderContext.addMissingDependency(d); + } + for (const d of buildDependencies) { + loaderContext.addBuildDependency(d); + } + return callback( + null, + source, + /** @type {object | null} */ (map), + referencedModule + ); + } + ); + }; + + /** + * @param {string} request the request string to load the module from + * @param {ImportModuleOptions} options options + * @param {ImportModuleCallback} callback callback returning the exports + * @returns {void} + */ + const importModule = (request, options, callback) => { + const dep = new LoaderImportDependency(request); + dep.loc = { + name: request + }; + const factory = compilation.dependencyFactories.get( + /** @type {DepConstructor} */ (dep.constructor) + ); + if (factory === undefined) { + return callback( + new Error( + `No module factory available for dependency type: ${dep.constructor.name}` + ) + ); + } + + const oldFactorizeQueueContext = + compilation.factorizeQueue.getContext(); + compilation.factorizeQueue.setContext("import-module"); + const oldAddModuleQueueContext = + compilation.addModuleQueue.getContext(); + compilation.addModuleQueue.setContext("import-module"); + compilation.buildQueue.increaseParallelism(); + compilation.handleModuleCreation( + { + factory, + dependencies: [dep], + originModule: + /** @type {NormalModule} */ + (loaderContext._module), + contextInfo: { + issuerLayer: options.layer + }, + context: loaderContext.context, + connectOrigin: false, + checkCycle: true + }, + err => { + compilation.factorizeQueue.setContext(oldFactorizeQueueContext); + compilation.addModuleQueue.setContext(oldAddModuleQueueContext); + compilation.buildQueue.decreaseParallelism(); + if (err) { + return callback(err); + } + const referencedModule = moduleGraph.getModule(dep); + if (!referencedModule) { + return callback(new Error("Cannot load the module")); + } + compilation.buildQueue.increaseParallelism(); + compilation.executeModule( + referencedModule, + { + entryOptions: { + baseUri: options.baseUri, + publicPath: options.publicPath + } + }, + (err, result) => { + compilation.buildQueue.decreaseParallelism(); + if (err) return callback(err); + const { + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies, + cacheable, + assets, + exports + } = /** @type {ExecuteModuleResult} */ (result); + for (const d of fileDependencies) { + loaderContext.addDependency(d); + } + for (const d of contextDependencies) { + loaderContext.addContextDependency(d); + } + for (const d of missingDependencies) { + loaderContext.addMissingDependency(d); + } + for (const d of buildDependencies) { + loaderContext.addBuildDependency(d); + } + if (cacheable === false) loaderContext.cacheable(false); + for (const [name, { source, info }] of assets) { + const buildInfo = + /** @type {BuildInfo} */ + ( + /** @type {NormalModule} */ (loaderContext._module) + .buildInfo + ); + if (!buildInfo.assets) { + buildInfo.assets = Object.create(null); + buildInfo.assetsInfo = new Map(); + } + /** @type {NonNullable} */ + (buildInfo.assets)[name] = source; + /** @type {NonNullable} */ + (buildInfo.assetsInfo).set(name, info); + } + callback(null, exports); + } + ); + } + ); + }; + + // eslint-disable-next-line no-warning-comments + // @ts-ignore Overloading doesn't work + loaderContext.importModule = (request, options, callback) => { + if (!callback) { + return new Promise((resolve, reject) => { + importModule(request, options || {}, (err, result) => { + if (err) reject(err); + else resolve(result); + }); + }); + } + return importModule(request, options || {}, callback); + }; + } + ); + }); + } +} +module.exports = LoaderPlugin; diff --git a/webpack-lib/lib/dependencies/LocalModule.js b/webpack-lib/lib/dependencies/LocalModule.js new file mode 100644 index 00000000000..7748a06ba6a --- /dev/null +++ b/webpack-lib/lib/dependencies/LocalModule.js @@ -0,0 +1,60 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class LocalModule { + /** + * @param {string} name name + * @param {number} idx index + */ + constructor(name, idx) { + this.name = name; + this.idx = idx; + this.used = false; + } + + flagUsed() { + this.used = true; + } + + /** + * @returns {string} variable name + */ + variableName() { + return `__WEBPACK_LOCAL_MODULE_${this.idx}__`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.name); + write(this.idx); + write(this.used); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.name = read(); + this.idx = read(); + this.used = read(); + } +} + +makeSerializable(LocalModule, "webpack/lib/dependencies/LocalModule"); + +module.exports = LocalModule; diff --git a/webpack-lib/lib/dependencies/LocalModuleDependency.js b/webpack-lib/lib/dependencies/LocalModuleDependency.js new file mode 100644 index 00000000000..2cde22fe145 --- /dev/null +++ b/webpack-lib/lib/dependencies/LocalModuleDependency.js @@ -0,0 +1,84 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./LocalModule")} LocalModule */ + +class LocalModuleDependency extends NullDependency { + /** + * @param {LocalModule} localModule local module + * @param {Range | undefined} range range + * @param {boolean} callNew true, when the local module should be called with new + */ + constructor(localModule, range, callNew) { + super(); + + this.localModule = localModule; + this.range = range; + this.callNew = callNew; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.localModule); + write(this.range); + write(this.callNew); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.localModule = read(); + this.range = read(); + this.callNew = read(); + + super.deserialize(context); + } +} + +makeSerializable( + LocalModuleDependency, + "webpack/lib/dependencies/LocalModuleDependency" +); + +LocalModuleDependency.Template = class LocalModuleDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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 dep = /** @type {LocalModuleDependency} */ (dependency); + if (!dep.range) return; + const moduleInstance = dep.callNew + ? `new (function () { return ${dep.localModule.variableName()}; })()` + : dep.localModule.variableName(); + source.replace(dep.range[0], dep.range[1] - 1, moduleInstance); + } +}; + +module.exports = LocalModuleDependency; diff --git a/webpack-lib/lib/dependencies/LocalModulesHelpers.js b/webpack-lib/lib/dependencies/LocalModulesHelpers.js new file mode 100644 index 00000000000..bcb947c2b02 --- /dev/null +++ b/webpack-lib/lib/dependencies/LocalModulesHelpers.js @@ -0,0 +1,68 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const LocalModule = require("./LocalModule"); + +/** @typedef {import("../javascript/JavascriptParser").ParserState} ParserState */ + +/** + * @param {string} parent parent module + * @param {string} mod module to resolve + * @returns {string} resolved module + */ +const lookup = (parent, mod) => { + if (mod.charAt(0) !== ".") return mod; + + const path = parent.split("/"); + const segments = mod.split("/"); + path.pop(); + + for (let i = 0; i < segments.length; i++) { + const seg = segments[i]; + if (seg === "..") { + path.pop(); + } else if (seg !== ".") { + path.push(seg); + } + } + + return path.join("/"); +}; + +/** + * @param {ParserState} state parser state + * @param {string} name name + * @returns {LocalModule} local module + */ +module.exports.addLocalModule = (state, name) => { + if (!state.localModules) { + state.localModules = []; + } + const m = new LocalModule(name, state.localModules.length); + state.localModules.push(m); + return m; +}; + +/** + * @param {ParserState} state parser state + * @param {string} name name + * @param {string} [namedModule] named module + * @returns {LocalModule | null} local module or null + */ +module.exports.getLocalModule = (state, name, namedModule) => { + if (!state.localModules) return null; + if (namedModule) { + // resolve dependency name relative to the defining named module + name = lookup(namedModule, name); + } + for (let i = 0; i < state.localModules.length; i++) { + if (state.localModules[i].name === name) { + return state.localModules[i]; + } + } + return null; +}; diff --git a/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js b/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js new file mode 100644 index 00000000000..fd2b3fe5f73 --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js @@ -0,0 +1,137 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const InitFragment = require("../InitFragment"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class ModuleDecoratorDependency extends NullDependency { + /** + * @param {string} decorator the decorator requirement + * @param {boolean} allowExportsAccess allow to access exports from module + */ + constructor(decorator, allowExportsAccess) { + super(); + this.decorator = decorator; + this.allowExportsAccess = allowExportsAccess; + this._hashUpdate = undefined; + } + + /** + * @returns {string} a display name for the type of dependency + */ + get type() { + return "module decorator"; + } + + get category() { + return "self"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return "self"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return this.allowExportsAccess + ? Dependency.EXPORTS_OBJECT_REFERENCED + : Dependency.NO_EXPORTS_REFERENCED; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = `${this.decorator}${this.allowExportsAccess}`; + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.decorator); + write(this.allowExportsAccess); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.decorator = read(); + this.allowExportsAccess = read(); + super.deserialize(context); + } +} + +makeSerializable( + ModuleDecoratorDependency, + "webpack/lib/dependencies/ModuleDecoratorDependency" +); + +ModuleDecoratorDependency.Template = class ModuleDecoratorDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { module, chunkGraph, initFragments, runtimeRequirements } + ) { + const dep = /** @type {ModuleDecoratorDependency} */ (dependency); + runtimeRequirements.add(RuntimeGlobals.moduleLoaded); + runtimeRequirements.add(RuntimeGlobals.moduleId); + runtimeRequirements.add(RuntimeGlobals.module); + runtimeRequirements.add(dep.decorator); + initFragments.push( + new InitFragment( + `/* module decorator */ ${module.moduleArgument} = ${dep.decorator}(${module.moduleArgument});\n`, + InitFragment.STAGE_PROVIDES, + 0, + `module decorator ${chunkGraph.getModuleId(module)}` + ) + ); + } +}; + +module.exports = ModuleDecoratorDependency; diff --git a/webpack-lib/lib/dependencies/ModuleDependency.js b/webpack-lib/lib/dependencies/ModuleDependency.js new file mode 100644 index 00000000000..76b9d17484a --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleDependency.js @@ -0,0 +1,98 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const DependencyTemplate = require("../DependencyTemplate"); +const RawModule = require("../RawModule"); + +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ModuleDependency extends Dependency { + /** + * @param {string} request request path which needs resolving + */ + constructor(request) { + super(); + this.request = request; + this.userRequest = request; + this.range = undefined; + // TODO move it to subclasses and rename + // assertions must be serialized by subclasses that use it + /** @type {ImportAttributes | undefined} */ + this.assertions = undefined; + this._context = undefined; + } + + /** + * @returns {string | undefined} a request context + */ + getContext() { + return this._context; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + let str = `context${this._context || ""}|module${this.request}`; + if (this.assertions !== undefined) { + str += JSON.stringify(this.assertions); + } + return str; + } + + /** + * @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 true; + } + + /** + * @param {string} context context directory + * @returns {Module | null} a module + */ + createIgnoredModule(context) { + return new RawModule( + "/* (ignored) */", + `ignored|${context}|${this.request}`, + `${this.request} (ignored)` + ); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.request); + write(this.userRequest); + write(this._context); + write(this.range); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.request = read(); + this.userRequest = read(); + this._context = read(); + this.range = read(); + super.deserialize(context); + } +} + +ModuleDependency.Template = DependencyTemplate; + +module.exports = ModuleDependency; diff --git a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js new file mode 100644 index 00000000000..8086fc79717 --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ + +class ModuleDependencyTemplateAsId extends ModuleDependency.Template { + /** + * @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, { runtimeTemplate, moduleGraph, chunkGraph }) { + const dep = /** @type {ModuleDependency} */ (dependency); + if (!dep.range) return; + const content = runtimeTemplate.moduleId({ + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + chunkGraph, + request: dep.request, + weak: dep.weak + }); + source.replace(dep.range[0], dep.range[1] - 1, content); + } +} + +module.exports = ModuleDependencyTemplateAsId; diff --git a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js new file mode 100644 index 00000000000..9e05906cfe1 --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ + +class ModuleDependencyTemplateAsRequireId extends ModuleDependency.Template { + /** + * @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, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {ModuleDependency} */ (dependency); + if (!dep.range) return; + const content = runtimeTemplate.moduleExports({ + module: moduleGraph.getModule(dep), + chunkGraph, + request: dep.request, + weak: dep.weak, + runtimeRequirements + }); + source.replace(dep.range[0], dep.range[1] - 1, content); + } +} +module.exports = ModuleDependencyTemplateAsRequireId; diff --git a/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js b/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js new file mode 100644 index 00000000000..1916a7e2563 --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ModuleHotAcceptDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + this.range = range; + this.weak = true; + } + + get type() { + return "module.hot.accept"; + } + + get category() { + return "commonjs"; + } +} + +makeSerializable( + ModuleHotAcceptDependency, + "webpack/lib/dependencies/ModuleHotAcceptDependency" +); + +ModuleHotAcceptDependency.Template = ModuleDependencyTemplateAsId; + +module.exports = ModuleHotAcceptDependency; diff --git a/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js b/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js new file mode 100644 index 00000000000..70423774b4e --- /dev/null +++ b/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class ModuleHotDeclineDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + + this.range = range; + this.weak = true; + } + + get type() { + return "module.hot.decline"; + } + + get category() { + return "commonjs"; + } +} + +makeSerializable( + ModuleHotDeclineDependency, + "webpack/lib/dependencies/ModuleHotDeclineDependency" +); + +ModuleHotDeclineDependency.Template = ModuleDependencyTemplateAsId; + +module.exports = ModuleHotDeclineDependency; diff --git a/webpack-lib/lib/dependencies/NullDependency.js b/webpack-lib/lib/dependencies/NullDependency.js new file mode 100644 index 00000000000..c22cafc7c7a --- /dev/null +++ b/webpack-lib/lib/dependencies/NullDependency.js @@ -0,0 +1,40 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const DependencyTemplate = require("../DependencyTemplate"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ + +class NullDependency extends Dependency { + get type() { + return "null"; + } + + /** + * @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 false; + } +} + +NullDependency.Template = class NullDependencyTemplate extends ( + DependencyTemplate +) { + /** + * @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) {} +}; + +module.exports = NullDependency; diff --git a/webpack-lib/lib/dependencies/PrefetchDependency.js b/webpack-lib/lib/dependencies/PrefetchDependency.js new file mode 100644 index 00000000000..59e22c59a79 --- /dev/null +++ b/webpack-lib/lib/dependencies/PrefetchDependency.js @@ -0,0 +1,27 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("./ModuleDependency"); + +class PrefetchDependency extends ModuleDependency { + /** + * @param {string} request the request string + */ + constructor(request) { + super(request); + } + + get type() { + return "prefetch"; + } + + get category() { + return "esm"; + } +} + +module.exports = PrefetchDependency; diff --git a/webpack-lib/lib/dependencies/ProvidedDependency.js b/webpack-lib/lib/dependencies/ProvidedDependency.js new file mode 100644 index 00000000000..9f1d3f6e7dc --- /dev/null +++ b/webpack-lib/lib/dependencies/ProvidedDependency.js @@ -0,0 +1,157 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const InitFragment = require("../InitFragment"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @param {string[]|null} path the property path array + * @returns {string} the converted path + */ +const pathToString = path => + path !== null && path.length > 0 + ? path.map(part => `[${JSON.stringify(part)}]`).join("") + : ""; + +class ProvidedDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {string} identifier identifier + * @param {string[]} ids ids + * @param {Range} range range + */ + constructor(request, identifier, ids, range) { + super(request); + this.identifier = identifier; + this.ids = ids; + this.range = range; + this._hashUpdate = undefined; + } + + get type() { + return "provided"; + } + + get category() { + return "esm"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + const ids = this.ids; + if (ids.length === 0) return Dependency.EXPORTS_OBJECT_REFERENCED; + return [ids]; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = this.identifier + (this.ids ? this.ids.join(",") : ""); + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.identifier); + write(this.ids); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.identifier = read(); + this.ids = read(); + super.deserialize(context); + } +} + +makeSerializable( + ProvidedDependency, + "webpack/lib/dependencies/ProvidedDependency" +); + +class ProvidedDependencyTemplate extends ModuleDependency.Template { + /** + * @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, + { + runtime, + runtimeTemplate, + moduleGraph, + chunkGraph, + initFragments, + runtimeRequirements + } + ) { + const dep = /** @type {ProvidedDependency} */ (dependency); + const connection = + /** @type {ModuleGraphConnection} */ + (moduleGraph.getConnection(dep)); + const exportsInfo = moduleGraph.getExportsInfo(connection.module); + const usedName = exportsInfo.getUsedName(dep.ids, runtime); + initFragments.push( + new InitFragment( + `/* provided dependency */ var ${ + dep.identifier + } = ${runtimeTemplate.moduleExports({ + module: moduleGraph.getModule(dep), + chunkGraph, + request: dep.request, + runtimeRequirements + })}${pathToString(/** @type {string[]} */ (usedName))};\n`, + InitFragment.STAGE_PROVIDES, + 1, + `provided ${dep.identifier}` + ) + ); + source.replace(dep.range[0], dep.range[1] - 1, dep.identifier); + } +} + +ProvidedDependency.Template = ProvidedDependencyTemplate; + +module.exports = ProvidedDependency; diff --git a/webpack-lib/lib/dependencies/PureExpressionDependency.js b/webpack-lib/lib/dependencies/PureExpressionDependency.js new file mode 100644 index 00000000000..3c4312c9847 --- /dev/null +++ b/webpack-lib/lib/dependencies/PureExpressionDependency.js @@ -0,0 +1,162 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); +const makeSerializable = require("../util/makeSerializable"); +const { filterRuntime, runtimeToString } = require("../util/runtime"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class PureExpressionDependency extends NullDependency { + /** + * @param {Range} range the source range + */ + constructor(range) { + super(); + this.range = range; + /** @type {Set | false} */ + this.usedByExports = false; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @param {RuntimeSpec} runtime current runtimes + * @returns {boolean | RuntimeSpec} runtime condition + */ + _getRuntimeCondition(moduleGraph, runtime) { + const usedByExports = this.usedByExports; + if (usedByExports !== false) { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(this)); + const exportsInfo = moduleGraph.getExportsInfo(selfModule); + const runtimeCondition = filterRuntime(runtime, runtime => { + for (const exportName of usedByExports) { + if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) { + return true; + } + } + return false; + }); + return runtimeCondition; + } + return false; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + const runtimeCondition = this._getRuntimeCondition( + context.chunkGraph.moduleGraph, + context.runtime + ); + if (runtimeCondition === true) { + return; + } else if (runtimeCondition === false) { + hash.update("null"); + } else { + hash.update( + `${runtimeToString(runtimeCondition)}|${runtimeToString( + context.runtime + )}` + ); + } + hash.update(String(this.range)); + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConnectionState} how this dependency connects the module to referencing modules + */ + getModuleEvaluationSideEffectsState(moduleGraph) { + return false; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + write(this.usedByExports); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.range = read(); + this.usedByExports = read(); + super.deserialize(context); + } +} + +makeSerializable( + PureExpressionDependency, + "webpack/lib/dependencies/PureExpressionDependency" +); + +PureExpressionDependency.Template = class PureExpressionDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { chunkGraph, moduleGraph, runtime, runtimeTemplate, runtimeRequirements } + ) { + const dep = /** @type {PureExpressionDependency} */ (dependency); + const runtimeCondition = dep._getRuntimeCondition(moduleGraph, runtime); + if (runtimeCondition === true) { + // Do nothing + } else if (runtimeCondition === false) { + source.insert( + dep.range[0], + "(/* unused pure expression or super */ null && (" + ); + source.insert(dep.range[1], "))"); + } else { + const condition = runtimeTemplate.runtimeConditionExpression({ + chunkGraph, + runtime, + runtimeCondition, + runtimeRequirements + }); + source.insert( + dep.range[0], + `(/* runtime-dependent pure expression or super */ ${condition} ? (` + ); + source.insert(dep.range[1], ") : null)"); + } + } +}; + +module.exports = PureExpressionDependency; diff --git a/webpack-lib/lib/dependencies/RequireContextDependency.js b/webpack-lib/lib/dependencies/RequireContextDependency.js new file mode 100644 index 00000000000..9aa883f2edb --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireContextDependency.js @@ -0,0 +1,37 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +class RequireContextDependency extends ContextDependency { + /** + * @param {TODO} options options + * @param {Range} range range + */ + constructor(options, range) { + super(options); + + this.range = range; + } + + get type() { + return "require.context"; + } +} + +makeSerializable( + RequireContextDependency, + "webpack/lib/dependencies/RequireContextDependency" +); + +RequireContextDependency.Template = ModuleDependencyTemplateAsRequireId; + +module.exports = RequireContextDependency; diff --git a/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js b/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js new file mode 100644 index 00000000000..02ce1c1487e --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RequireContextDependency = require("./RequireContextDependency"); + +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +module.exports = class RequireContextDependencyParserPlugin { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.call + .for("require.context") + .tap("RequireContextDependencyParserPlugin", expr => { + let regExp = /^\.\/.*$/; + let recursive = true; + let mode = "sync"; + switch (expr.arguments.length) { + case 4: { + const modeExpr = parser.evaluateExpression(expr.arguments[3]); + if (!modeExpr.isString()) return; + mode = /** @type {string} */ (modeExpr.string); + } + // falls through + case 3: { + const regExpExpr = parser.evaluateExpression(expr.arguments[2]); + if (!regExpExpr.isRegExp()) return; + regExp = /** @type {RegExp} */ (regExpExpr.regExp); + } + // falls through + case 2: { + const recursiveExpr = parser.evaluateExpression(expr.arguments[1]); + if (!recursiveExpr.isBoolean()) return; + recursive = /** @type {boolean} */ (recursiveExpr.bool); + } + // falls through + case 1: { + const requestExpr = parser.evaluateExpression(expr.arguments[0]); + if (!requestExpr.isString()) return; + const dep = new RequireContextDependency( + { + request: requestExpr.string, + recursive, + regExp, + mode, + category: "commonjs" + }, + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + dep.optional = Boolean(parser.scope.inTry); + parser.state.current.addDependency(dep); + return true; + } + } + }); + } +}; diff --git a/webpack-lib/lib/dependencies/RequireContextPlugin.js b/webpack-lib/lib/dependencies/RequireContextPlugin.js new file mode 100644 index 00000000000..30e87fb9e8c --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireContextPlugin.js @@ -0,0 +1,163 @@ +/* + 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 +} = require("../ModuleTypeConstants"); +const { cachedSetProperty } = require("../util/cleverMerge"); +const ContextElementDependency = require("./ContextElementDependency"); +const RequireContextDependency = require("./RequireContextDependency"); +const RequireContextDependencyParserPlugin = require("./RequireContextDependencyParserPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +/** @type {ResolveOptions} */ +const EMPTY_RESOLVE_OPTIONS = {}; + +const PLUGIN_NAME = "RequireContextPlugin"; + +class RequireContextPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { contextModuleFactory, normalModuleFactory }) => { + compilation.dependencyFactories.set( + RequireContextDependency, + contextModuleFactory + ); + compilation.dependencyTemplates.set( + RequireContextDependency, + new RequireContextDependency.Template() + ); + + compilation.dependencyFactories.set( + ContextElementDependency, + normalModuleFactory + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if ( + parserOptions.requireContext !== undefined && + !parserOptions.requireContext + ) + return; + + new RequireContextDependencyParserPlugin().apply(parser); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + + contextModuleFactory.hooks.alternativeRequests.tap( + PLUGIN_NAME, + (items, options) => { + if (items.length === 0) return items; + + const finalResolveOptions = compiler.resolverFactory.get( + "normal", + cachedSetProperty( + options.resolveOptions || EMPTY_RESOLVE_OPTIONS, + "dependencyType", + /** @type {string} */ (options.category) + ) + ).options; + + let newItems; + if (!finalResolveOptions.fullySpecified) { + newItems = []; + for (const item of items) { + const { request, context } = item; + for (const ext of finalResolveOptions.extensions) { + if (request.endsWith(ext)) { + newItems.push({ + context, + request: request.slice(0, -ext.length) + }); + } + } + if (!finalResolveOptions.enforceExtension) { + newItems.push(item); + } + } + items = newItems; + + newItems = []; + for (const obj of items) { + const { request, context } = obj; + for (const mainFile of finalResolveOptions.mainFiles) { + if (request.endsWith(`/${mainFile}`)) { + newItems.push({ + context, + request: request.slice(0, -mainFile.length) + }); + newItems.push({ + context, + request: request.slice(0, -mainFile.length - 1) + }); + } + } + newItems.push(obj); + } + items = newItems; + } + + newItems = []; + for (const item of items) { + let hideOriginal = false; + for (const modulesItems of finalResolveOptions.modules) { + if (Array.isArray(modulesItems)) { + for (const dir of modulesItems) { + if (item.request.startsWith(`./${dir}/`)) { + newItems.push({ + context: item.context, + request: item.request.slice(dir.length + 3) + }); + hideOriginal = true; + } + } + } else { + const dir = modulesItems.replace(/\\/g, "/"); + const fullPath = + item.context.replace(/\\/g, "/") + item.request.slice(1); + if (fullPath.startsWith(dir)) { + newItems.push({ + context: item.context, + request: fullPath.slice(dir.length + 1) + }); + } + } + } + if (!hideOriginal) { + newItems.push(item); + } + } + return newItems; + } + ); + } + ); + } +} +module.exports = RequireContextPlugin; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js new file mode 100644 index 00000000000..6cf8294e5e7 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js @@ -0,0 +1,29 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ + +class RequireEnsureDependenciesBlock extends AsyncDependenciesBlock { + /** + * @param {ChunkGroupOptions & { entryOptions?: TODO }} chunkName chunk name + * @param {DependencyLocation} loc location info + */ + constructor(chunkName, loc) { + super(chunkName, loc, null); + } +} + +makeSerializable( + RequireEnsureDependenciesBlock, + "webpack/lib/dependencies/RequireEnsureDependenciesBlock" +); + +module.exports = RequireEnsureDependenciesBlock; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js new file mode 100644 index 00000000000..c81fada8b49 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js @@ -0,0 +1,138 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RequireEnsureDependenciesBlock = require("./RequireEnsureDependenciesBlock"); +const RequireEnsureDependency = require("./RequireEnsureDependency"); +const RequireEnsureItemDependency = require("./RequireEnsureItemDependency"); +const getFunctionExpression = require("./getFunctionExpression"); + +/** @typedef {import("../ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +module.exports = class RequireEnsureDependenciesBlockParserPlugin { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + parser.hooks.call + .for("require.ensure") + .tap("RequireEnsureDependenciesBlockParserPlugin", expr => { + let chunkName = null; + let errorExpressionArg = null; + let errorExpression = null; + switch (expr.arguments.length) { + case 4: { + const chunkNameExpr = parser.evaluateExpression(expr.arguments[3]); + if (!chunkNameExpr.isString()) return; + chunkName = chunkNameExpr.string; + } + // falls through + case 3: { + errorExpressionArg = expr.arguments[2]; + errorExpression = getFunctionExpression(errorExpressionArg); + + if (!errorExpression && !chunkName) { + const chunkNameExpr = parser.evaluateExpression( + expr.arguments[2] + ); + if (!chunkNameExpr.isString()) return; + chunkName = chunkNameExpr.string; + } + } + // falls through + case 2: { + const dependenciesExpr = parser.evaluateExpression( + expr.arguments[0] + ); + const dependenciesItems = + /** @type {BasicEvaluatedExpression[]} */ ( + dependenciesExpr.isArray() + ? dependenciesExpr.items + : [dependenciesExpr] + ); + const successExpressionArg = expr.arguments[1]; + const successExpression = + getFunctionExpression(successExpressionArg); + + if (successExpression) { + parser.walkExpressions(successExpression.expressions); + } + if (errorExpression) { + parser.walkExpressions(errorExpression.expressions); + } + + const depBlock = new RequireEnsureDependenciesBlock( + /** @type {ChunkGroupOptions & { entryOptions?: TODO }} */ + (chunkName), + /** @type {DependencyLocation} */ (expr.loc) + ); + const errorCallbackExists = + expr.arguments.length === 4 || + (!chunkName && expr.arguments.length === 3); + const dep = new RequireEnsureDependency( + /** @type {Range} */ (expr.range), + /** @type {Range} */ (expr.arguments[1].range), + errorCallbackExists && + /** @type {Range} */ (expr.arguments[2].range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + depBlock.addDependency(dep); + const old = parser.state.current; + parser.state.current = /** @type {TODO} */ (depBlock); + try { + let failed = false; + parser.inScope([], () => { + for (const ee of dependenciesItems) { + if (ee.isString()) { + const ensureDependency = new RequireEnsureItemDependency( + /** @type {string} */ (ee.string) + ); + ensureDependency.loc = + /** @type {DependencyLocation} */ + (expr.loc); + depBlock.addDependency(ensureDependency); + } else { + failed = true; + } + } + }); + if (failed) { + return; + } + if (successExpression) { + if (successExpression.fn.body.type === "BlockStatement") { + parser.walkStatement(successExpression.fn.body); + } else { + parser.walkExpression(successExpression.fn.body); + } + } + old.addBlock(depBlock); + } finally { + parser.state.current = old; + } + if (!successExpression) { + parser.walkExpression(successExpressionArg); + } + if (errorExpression) { + if (errorExpression.fn.body.type === "BlockStatement") { + parser.walkStatement(errorExpression.fn.body); + } else { + parser.walkExpression(errorExpression.fn.body); + } + } else if (errorExpressionArg) { + parser.walkExpression(errorExpressionArg); + } + return true; + } + } + }); + } +}; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependency.js b/webpack-lib/lib/dependencies/RequireEnsureDependency.js new file mode 100644 index 00000000000..4fcec7731ce --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireEnsureDependency.js @@ -0,0 +1,115 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class RequireEnsureDependency extends NullDependency { + /** + * @param {Range} range range + * @param {Range} contentRange content range + * @param {Range | false} errorHandlerRange error handler range + */ + constructor(range, contentRange, errorHandlerRange) { + super(); + + this.range = range; + this.contentRange = contentRange; + this.errorHandlerRange = errorHandlerRange; + } + + get type() { + return "require.ensure"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.range); + write(this.contentRange); + write(this.errorHandlerRange); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.range = read(); + this.contentRange = read(); + this.errorHandlerRange = read(); + + super.deserialize(context); + } +} + +makeSerializable( + RequireEnsureDependency, + "webpack/lib/dependencies/RequireEnsureDependency" +); + +RequireEnsureDependency.Template = class RequireEnsureDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, + { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } + ) { + const dep = /** @type {RequireEnsureDependency} */ (dependency); + const depBlock = /** @type {AsyncDependenciesBlock} */ ( + moduleGraph.getParentBlock(dep) + ); + const promise = runtimeTemplate.blockPromise({ + chunkGraph, + block: depBlock, + message: "require.ensure", + runtimeRequirements + }); + const range = dep.range; + const contentRange = dep.contentRange; + const errorHandlerRange = dep.errorHandlerRange; + source.replace(range[0], contentRange[0] - 1, `${promise}.then((`); + if (errorHandlerRange) { + source.replace( + contentRange[1], + errorHandlerRange[0] - 1, + `).bind(null, ${RuntimeGlobals.require}))['catch'](` + ); + source.replace(errorHandlerRange[1], range[1] - 1, ")"); + } else { + source.replace( + contentRange[1], + range[1] - 1, + `).bind(null, ${RuntimeGlobals.require}))['catch'](${RuntimeGlobals.uncaughtErrorHandler})` + ); + } + } +}; + +module.exports = RequireEnsureDependency; diff --git a/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js b/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js new file mode 100644 index 00000000000..f9a465a55c9 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js @@ -0,0 +1,36 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const NullDependency = require("./NullDependency"); + +class RequireEnsureItemDependency extends ModuleDependency { + /** + * @param {string} request the request string + */ + constructor(request) { + super(request); + } + + get type() { + return "require.ensure item"; + } + + get category() { + return "commonjs"; + } +} + +makeSerializable( + RequireEnsureItemDependency, + "webpack/lib/dependencies/RequireEnsureItemDependency" +); + +RequireEnsureItemDependency.Template = NullDependency.Template; + +module.exports = RequireEnsureItemDependency; diff --git a/webpack-lib/lib/dependencies/RequireEnsurePlugin.js b/webpack-lib/lib/dependencies/RequireEnsurePlugin.js new file mode 100644 index 00000000000..5a1dc31dbdd --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireEnsurePlugin.js @@ -0,0 +1,86 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RequireEnsureDependency = require("./RequireEnsureDependency"); +const RequireEnsureItemDependency = require("./RequireEnsureItemDependency"); + +const RequireEnsureDependenciesBlockParserPlugin = require("./RequireEnsureDependenciesBlockParserPlugin"); + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC +} = require("../ModuleTypeConstants"); +const { + evaluateToString, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "RequireEnsurePlugin"; + +class RequireEnsurePlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + RequireEnsureItemDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + RequireEnsureItemDependency, + new RequireEnsureItemDependency.Template() + ); + + compilation.dependencyTemplates.set( + RequireEnsureDependency, + new RequireEnsureDependency.Template() + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if ( + parserOptions.requireEnsure !== undefined && + !parserOptions.requireEnsure + ) + return; + + new RequireEnsureDependenciesBlockParserPlugin().apply(parser); + parser.hooks.evaluateTypeof + .for("require.ensure") + .tap(PLUGIN_NAME, evaluateToString("function")); + parser.hooks.typeof + .for("require.ensure") + .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); + } + ); + } +} +module.exports = RequireEnsurePlugin; diff --git a/webpack-lib/lib/dependencies/RequireHeaderDependency.js b/webpack-lib/lib/dependencies/RequireHeaderDependency.js new file mode 100644 index 00000000000..7bf75603593 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireHeaderDependency.js @@ -0,0 +1,70 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class RequireHeaderDependency extends NullDependency { + /** + * @param {Range} range range + */ + constructor(range) { + super(); + if (!Array.isArray(range)) throw new Error("range must be valid"); + this.range = range; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.range); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RequireHeaderDependency} RequireHeaderDependency + */ + static deserialize(context) { + const obj = new RequireHeaderDependency(context.read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + RequireHeaderDependency, + "webpack/lib/dependencies/RequireHeaderDependency" +); + +RequireHeaderDependency.Template = class RequireHeaderDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { runtimeRequirements }) { + const dep = /** @type {RequireHeaderDependency} */ (dependency); + runtimeRequirements.add(RuntimeGlobals.require); + source.replace(dep.range[0], dep.range[1] - 1, RuntimeGlobals.require); + } +}; + +module.exports = RequireHeaderDependency; diff --git a/webpack-lib/lib/dependencies/RequireIncludeDependency.js b/webpack-lib/lib/dependencies/RequireIncludeDependency.js new file mode 100644 index 00000000000..3a25e84a8ff --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireIncludeDependency.js @@ -0,0 +1,79 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const Template = require("../Template"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class RequireIncludeDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + + this.range = range; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + // This doesn't use any export + return Dependency.NO_EXPORTS_REFERENCED; + } + + get type() { + return "require.include"; + } + + get category() { + return "commonjs"; + } +} + +makeSerializable( + RequireIncludeDependency, + "webpack/lib/dependencies/RequireIncludeDependency" +); + +RequireIncludeDependency.Template = class RequireIncludeDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, { runtimeTemplate }) { + const dep = /** @type {RequireIncludeDependency} */ (dependency); + const comment = runtimeTemplate.outputOptions.pathinfo + ? Template.toComment( + `require.include ${runtimeTemplate.requestShortener.shorten( + dep.request + )}` + ) + : ""; + + source.replace(dep.range[0], dep.range[1] - 1, `undefined${comment}`); + } +}; + +module.exports = RequireIncludeDependency; diff --git a/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js b/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js new file mode 100644 index 00000000000..7b9de2c9324 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js @@ -0,0 +1,101 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); +const { + evaluateToString, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); +const makeSerializable = require("../util/makeSerializable"); +const RequireIncludeDependency = require("./RequireIncludeDependency"); + +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +module.exports = class RequireIncludeDependencyParserPlugin { + /** + * @param {boolean} warn true: warn about deprecation, false: don't warn + */ + constructor(warn) { + this.warn = warn; + } + + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + apply(parser) { + const { warn } = this; + parser.hooks.call + .for("require.include") + .tap("RequireIncludeDependencyParserPlugin", expr => { + if (expr.arguments.length !== 1) return; + const param = parser.evaluateExpression(expr.arguments[0]); + if (!param.isString()) return; + + if (warn) { + parser.state.module.addWarning( + new RequireIncludeDeprecationWarning( + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + + const dep = new RequireIncludeDependency( + /** @type {string} */ (param.string), + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.current.addDependency(dep); + return true; + }); + parser.hooks.evaluateTypeof + .for("require.include") + .tap("RequireIncludePlugin", expr => { + if (warn) { + parser.state.module.addWarning( + new RequireIncludeDeprecationWarning( + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + return evaluateToString("function")(expr); + }); + parser.hooks.typeof + .for("require.include") + .tap("RequireIncludePlugin", expr => { + if (warn) { + parser.state.module.addWarning( + new RequireIncludeDeprecationWarning( + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } + return toConstantDependency(parser, JSON.stringify("function"))(expr); + }); + } +}; + +class RequireIncludeDeprecationWarning extends WebpackError { + /** + * @param {DependencyLocation} loc location + */ + constructor(loc) { + super("require.include() is deprecated and will be removed soon."); + + this.name = "RequireIncludeDeprecationWarning"; + + this.loc = loc; + } +} + +makeSerializable( + RequireIncludeDeprecationWarning, + "webpack/lib/dependencies/RequireIncludeDependencyParserPlugin", + "RequireIncludeDeprecationWarning" +); diff --git a/webpack-lib/lib/dependencies/RequireIncludePlugin.js b/webpack-lib/lib/dependencies/RequireIncludePlugin.js new file mode 100644 index 00000000000..af5bd0215fd --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireIncludePlugin.js @@ -0,0 +1,62 @@ +/* + 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 +} = require("../ModuleTypeConstants"); +const RequireIncludeDependency = require("./RequireIncludeDependency"); +const RequireIncludeDependencyParserPlugin = require("./RequireIncludeDependencyParserPlugin"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ + +const PLUGIN_NAME = "RequireIncludePlugin"; + +class RequireIncludePlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + RequireIncludeDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + RequireIncludeDependency, + new RequireIncludeDependency.Template() + ); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.requireInclude === false) return; + const warn = parserOptions.requireInclude === undefined; + + new RequireIncludeDependencyParserPlugin(warn).apply(parser); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + } + ); + } +} +module.exports = RequireIncludePlugin; diff --git a/webpack-lib/lib/dependencies/RequireResolveContextDependency.js b/webpack-lib/lib/dependencies/RequireResolveContextDependency.js new file mode 100644 index 00000000000..dd82e922094 --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireResolveContextDependency.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const ContextDependency = require("./ContextDependency"); +const ContextDependencyTemplateAsId = require("./ContextDependencyTemplateAsId"); + +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ + +class RequireResolveContextDependency extends ContextDependency { + /** + * @param {ContextDependencyOptions} options options + * @param {Range} range range + * @param {Range} valueRange value range + * @param {TODO} context context + */ + constructor(options, range, valueRange, context) { + super(options, context); + + this.range = range; + this.valueRange = valueRange; + } + + get type() { + return "amd require context"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.range); + write(this.valueRange); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.range = read(); + this.valueRange = read(); + + super.deserialize(context); + } +} + +makeSerializable( + RequireResolveContextDependency, + "webpack/lib/dependencies/RequireResolveContextDependency" +); + +RequireResolveContextDependency.Template = ContextDependencyTemplateAsId; + +module.exports = RequireResolveContextDependency; diff --git a/webpack-lib/lib/dependencies/RequireResolveDependency.js b/webpack-lib/lib/dependencies/RequireResolveDependency.js new file mode 100644 index 00000000000..da0bd319b9d --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireResolveDependency.js @@ -0,0 +1,58 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); +const ModuleDependencyAsId = require("./ModuleDependencyTemplateAsId"); + +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class RequireResolveDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + * @param {string} [context] context + */ + constructor(request, range, context) { + super(request); + + this.range = range; + this._context = context; + } + + get type() { + return "require.resolve"; + } + + get category() { + return "commonjs"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + // This doesn't use any export + return Dependency.NO_EXPORTS_REFERENCED; + } +} + +makeSerializable( + RequireResolveDependency, + "webpack/lib/dependencies/RequireResolveDependency" +); + +RequireResolveDependency.Template = ModuleDependencyAsId; + +module.exports = RequireResolveDependency; diff --git a/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js b/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js new file mode 100644 index 00000000000..2c9524c98ee --- /dev/null +++ b/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js @@ -0,0 +1,81 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class RequireResolveHeaderDependency extends NullDependency { + /** + * @param {Range} range range + */ + constructor(range) { + super(); + + if (!Array.isArray(range)) throw new Error("range must be valid"); + + this.range = range; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.range); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RequireResolveHeaderDependency} RequireResolveHeaderDependency + */ + static deserialize(context) { + const obj = new RequireResolveHeaderDependency(context.read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + RequireResolveHeaderDependency, + "webpack/lib/dependencies/RequireResolveHeaderDependency" +); + +RequireResolveHeaderDependency.Template = class RequireResolveHeaderDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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 dep = /** @type {RequireResolveHeaderDependency} */ (dependency); + source.replace(dep.range[0], dep.range[1] - 1, "/*require.resolve*/"); + } + + /** + * @param {string} name name + * @param {RequireResolveHeaderDependency} dep dependency + * @param {ReplaceSource} source source + */ + applyAsTemplateArgument(name, dep, source) { + source.replace(dep.range[0], dep.range[1] - 1, "/*require.resolve*/"); + } +}; + +module.exports = RequireResolveHeaderDependency; diff --git a/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js b/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js new file mode 100644 index 00000000000..714567b7140 --- /dev/null +++ b/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js @@ -0,0 +1,85 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class RuntimeRequirementsDependency extends NullDependency { + /** + * @param {string[]} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super(); + this.runtimeRequirements = new Set(runtimeRequirements); + this._hashUpdate = undefined; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = `${Array.from(this.runtimeRequirements).join()}`; + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.runtimeRequirements); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.runtimeRequirements = read(); + super.deserialize(context); + } +} + +makeSerializable( + RuntimeRequirementsDependency, + "webpack/lib/dependencies/RuntimeRequirementsDependency" +); + +RuntimeRequirementsDependency.Template = class RuntimeRequirementsDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { runtimeRequirements }) { + const dep = /** @type {RuntimeRequirementsDependency} */ (dependency); + for (const req of dep.runtimeRequirements) { + runtimeRequirements.add(req); + } + } +}; + +module.exports = RuntimeRequirementsDependency; diff --git a/webpack-lib/lib/dependencies/StaticExportsDependency.js b/webpack-lib/lib/dependencies/StaticExportsDependency.js new file mode 100644 index 00000000000..d91b5e43da5 --- /dev/null +++ b/webpack-lib/lib/dependencies/StaticExportsDependency.js @@ -0,0 +1,74 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ExportSpec} ExportSpec */ +/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ + +class StaticExportsDependency extends NullDependency { + /** + * @param {string[] | true} exports export names + * @param {boolean} canMangle true, if mangling exports names is allowed + */ + constructor(exports, canMangle) { + super(); + this.exports = exports; + this.canMangle = canMangle; + } + + get type() { + return "static exports"; + } + + /** + * Returns the exported names + * @param {ModuleGraph} moduleGraph module graph + * @returns {ExportsSpec | undefined} export names + */ + getExports(moduleGraph) { + return { + exports: this.exports, + canMangle: this.canMangle, + dependencies: undefined + }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.exports); + write(this.canMangle); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.exports = read(); + this.canMangle = read(); + super.deserialize(context); + } +} + +makeSerializable( + StaticExportsDependency, + "webpack/lib/dependencies/StaticExportsDependency" +); + +module.exports = StaticExportsDependency; diff --git a/webpack-lib/lib/dependencies/SystemPlugin.js b/webpack-lib/lib/dependencies/SystemPlugin.js new file mode 100644 index 00000000000..367020d64a9 --- /dev/null +++ b/webpack-lib/lib/dependencies/SystemPlugin.js @@ -0,0 +1,168 @@ +/* + 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 +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const WebpackError = require("../WebpackError"); +const { + evaluateToString, + expressionIsUnsupported, + toConstantDependency +} = require("../javascript/JavascriptParserHelpers"); +const makeSerializable = require("../util/makeSerializable"); +const ConstDependency = require("./ConstDependency"); +const SystemRuntimeModule = require("./SystemRuntimeModule"); + +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const PLUGIN_NAME = "SystemPlugin"; + +class SystemPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.hooks.runtimeRequirementInModule + .for(RuntimeGlobals.system) + .tap(PLUGIN_NAME, (module, set) => { + set.add(RuntimeGlobals.requireScope); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.system) + .tap(PLUGIN_NAME, (chunk, set) => { + compilation.addRuntimeModule(chunk, new SystemRuntimeModule()); + }); + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const handler = (parser, parserOptions) => { + if (parserOptions.system === undefined || !parserOptions.system) { + return; + } + + /** + * @param {string} name name + */ + const setNotSupported = name => { + parser.hooks.evaluateTypeof + .for(name) + .tap(PLUGIN_NAME, evaluateToString("undefined")); + parser.hooks.expression + .for(name) + .tap( + PLUGIN_NAME, + expressionIsUnsupported( + parser, + `${name} is not supported by webpack.` + ) + ); + }; + + parser.hooks.typeof + .for("System.import") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("function")) + ); + parser.hooks.evaluateTypeof + .for("System.import") + .tap(PLUGIN_NAME, evaluateToString("function")); + parser.hooks.typeof + .for("System") + .tap( + PLUGIN_NAME, + toConstantDependency(parser, JSON.stringify("object")) + ); + parser.hooks.evaluateTypeof + .for("System") + .tap(PLUGIN_NAME, evaluateToString("object")); + + setNotSupported("System.set"); + setNotSupported("System.get"); + setNotSupported("System.register"); + + parser.hooks.expression.for("System").tap(PLUGIN_NAME, expr => { + const dep = new ConstDependency( + RuntimeGlobals.system, + /** @type {Range} */ (expr.range), + [RuntimeGlobals.system] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }); + + parser.hooks.call.for("System.import").tap(PLUGIN_NAME, expr => { + parser.state.module.addWarning( + new SystemImportDeprecationWarning( + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + + return parser.hooks.importCall.call({ + type: "ImportExpression", + source: + /** @type {import("estree").Literal} */ + (expr.arguments[0]), + loc: expr.loc, + range: expr.range, + options: null + }); + }); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, handler); + } + ); + } +} + +class SystemImportDeprecationWarning extends WebpackError { + /** + * @param {DependencyLocation} loc location + */ + constructor(loc) { + super( + "System.import() is deprecated and will be removed soon. Use import() instead.\n" + + "For more info visit https://webpack.js.org/guides/code-splitting/" + ); + + this.name = "SystemImportDeprecationWarning"; + + this.loc = loc; + } +} + +makeSerializable( + SystemImportDeprecationWarning, + "webpack/lib/dependencies/SystemPlugin", + "SystemImportDeprecationWarning" +); + +module.exports = SystemPlugin; +module.exports.SystemImportDeprecationWarning = SystemImportDeprecationWarning; diff --git a/webpack-lib/lib/dependencies/SystemRuntimeModule.js b/webpack-lib/lib/dependencies/SystemRuntimeModule.js new file mode 100644 index 00000000000..a7c3fba72f9 --- /dev/null +++ b/webpack-lib/lib/dependencies/SystemRuntimeModule.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +class SystemRuntimeModule extends RuntimeModule { + constructor() { + super("system"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return Template.asString([ + `${RuntimeGlobals.system} = {`, + Template.indent([ + "import: function () {", + Template.indent( + "throw new Error('System.import cannot be used indirectly');" + ), + "}" + ]), + "};" + ]); + } +} + +module.exports = SystemRuntimeModule; diff --git a/webpack-lib/lib/dependencies/URLDependency.js b/webpack-lib/lib/dependencies/URLDependency.js new file mode 100644 index 00000000000..0d5b0a58bfe --- /dev/null +++ b/webpack-lib/lib/dependencies/URLDependency.js @@ -0,0 +1,170 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RawDataUrlModule = require("../asset/RawDataUrlModule"); +const { + getDependencyUsedByExportsCondition +} = require("../optimize/InnerGraph"); +const makeSerializable = require("../util/makeSerializable"); +const memoize = require("../util/memoize"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +const getIgnoredRawDataUrlModule = memoize( + () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)") +); + +class URLDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {Range} range range of the arguments of new URL( |> ... <| ) + * @param {Range} outerRange range of the full |> new URL(...) <| + * @param {boolean=} relative use relative urls instead of absolute with base uri + */ + constructor(request, range, outerRange, relative) { + super(request); + this.range = range; + this.outerRange = outerRange; + this.relative = relative || false; + /** @type {Set | boolean | undefined} */ + this.usedByExports = undefined; + } + + get type() { + return "new URL()"; + } + + get category() { + return "url"; + } + + /** + * @param {ModuleGraph} moduleGraph module graph + * @returns {null | false | GetConditionFn} function to determine if the connection is active + */ + getCondition(moduleGraph) { + return getDependencyUsedByExportsCondition( + this, + this.usedByExports, + moduleGraph + ); + } + + /** + * @param {string} context context directory + * @returns {Module | null} a module + */ + createIgnoredModule(context) { + return getIgnoredRawDataUrlModule(); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.outerRange); + write(this.relative); + write(this.usedByExports); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.outerRange = read(); + this.relative = read(); + this.usedByExports = read(); + super.deserialize(context); + } +} + +URLDependency.Template = class URLDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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 { + chunkGraph, + moduleGraph, + runtimeRequirements, + runtimeTemplate, + runtime + } = templateContext; + const dep = /** @type {URLDependency} */ (dependency); + const connection = moduleGraph.getConnection(dep); + // Skip rendering depending when dependency is conditional + if (connection && !connection.isTargetActive(runtime)) { + source.replace( + dep.outerRange[0], + dep.outerRange[1] - 1, + "/* unused asset import */ undefined" + ); + return; + } + + runtimeRequirements.add(RuntimeGlobals.require); + + if (dep.relative) { + runtimeRequirements.add(RuntimeGlobals.relativeUrl); + source.replace( + dep.outerRange[0], + dep.outerRange[1] - 1, + `/* asset import */ new ${ + RuntimeGlobals.relativeUrl + }(${runtimeTemplate.moduleRaw({ + chunkGraph, + module: moduleGraph.getModule(dep), + request: dep.request, + runtimeRequirements, + weak: false + })})` + ); + } else { + runtimeRequirements.add(RuntimeGlobals.baseURI); + + source.replace( + dep.range[0], + dep.range[1] - 1, + `/* asset import */ ${runtimeTemplate.moduleRaw({ + chunkGraph, + module: moduleGraph.getModule(dep), + request: dep.request, + runtimeRequirements, + weak: false + })}, ${RuntimeGlobals.baseURI}` + ); + } + } +}; + +makeSerializable(URLDependency, "webpack/lib/dependencies/URLDependency"); + +module.exports = URLDependency; diff --git a/webpack-lib/lib/dependencies/URLPlugin.js b/webpack-lib/lib/dependencies/URLPlugin.js new file mode 100644 index 00000000000..0409f0689b6 --- /dev/null +++ b/webpack-lib/lib/dependencies/URLPlugin.js @@ -0,0 +1,208 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const { pathToFileURL } = require("url"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); +const { approve } = require("../javascript/JavascriptParserHelpers"); +const InnerGraph = require("../optimize/InnerGraph"); +const ConstDependency = require("./ConstDependency"); +const URLDependency = require("./URLDependency"); + +/** @typedef {import("estree").MemberExpression} MemberExpression */ +/** @typedef {import("estree").NewExpression} NewExpressionNode */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +const PLUGIN_NAME = "URLPlugin"; + +class URLPlugin { + /** + * @param {Compiler} compiler compiler + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set(URLDependency, normalModuleFactory); + compilation.dependencyTemplates.set( + URLDependency, + new URLDependency.Template() + ); + + /** + * @param {NormalModule} module module + * @returns {URL} file url + */ + const getUrl = module => pathToFileURL(module.resource); + + /** + * @param {Parser} parser parser parser + * @param {MemberExpression} arg arg + * @returns {boolean} true when it is `meta.url`, otherwise false + */ + const isMetaUrl = (parser, arg) => { + const chain = parser.extractMemberExpressionChain(arg); + + if ( + chain.members.length !== 1 || + chain.object.type !== "MetaProperty" || + chain.object.meta.name !== "import" || + chain.object.property.name !== "meta" || + chain.members[0] !== "url" + ) + return false; + + return true; + }; + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const parserCallback = (parser, parserOptions) => { + if (parserOptions.url === false) return; + const relative = parserOptions.url === "relative"; + + /** + * @param {NewExpressionNode} expr expression + * @returns {undefined | string} request + */ + const getUrlRequest = expr => { + if (expr.arguments.length !== 2) return; + + const [arg1, arg2] = expr.arguments; + + if ( + arg2.type !== "MemberExpression" || + arg1.type === "SpreadElement" + ) + return; + + if (!isMetaUrl(parser, arg2)) return; + + return parser.evaluateExpression(arg1).asString(); + }; + + parser.hooks.canRename.for("URL").tap(PLUGIN_NAME, approve); + parser.hooks.evaluateNewExpression + .for("URL") + .tap(PLUGIN_NAME, expr => { + const request = getUrlRequest(expr); + if (!request) return; + const url = new URL(request, getUrl(parser.state.module)); + + return new BasicEvaluatedExpression() + .setString(url.toString()) + .setRange(/** @type {Range} */ (expr.range)); + }); + parser.hooks.new.for("URL").tap(PLUGIN_NAME, _expr => { + const expr = /** @type {NewExpressionNode} */ (_expr); + const { options: importOptions, errors: commentErrors } = + parser.parseCommentOptions(/** @type {Range} */ (expr.range)); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + /** @type {DependencyLocation} */ (comment.loc) + ) + ); + } + } + + if (importOptions && importOptions.webpackIgnore !== undefined) { + if (typeof importOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + return; + } else if (importOptions.webpackIgnore) { + if (expr.arguments.length !== 2) return; + + const [, arg2] = expr.arguments; + + if ( + arg2.type !== "MemberExpression" || + !isMetaUrl(parser, arg2) + ) + return; + + const dep = new ConstDependency( + RuntimeGlobals.baseURI, + /** @type {Range} */ (arg2.range), + [RuntimeGlobals.baseURI] + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + + return true; + } + } + + const request = getUrlRequest(expr); + + if (!request) return; + + const [arg1, arg2] = expr.arguments; + const dep = new URLDependency( + request, + [ + /** @type {Range} */ (arg1.range)[0], + /** @type {Range} */ (arg2.range)[1] + ], + /** @type {Range} */ (expr.range), + relative + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.current.addDependency(dep); + InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); + return true; + }); + parser.hooks.isPure.for("NewExpression").tap(PLUGIN_NAME, _expr => { + const expr = /** @type {NewExpressionNode} */ (_expr); + const { callee } = expr; + if (callee.type !== "Identifier") return; + const calleeInfo = parser.getFreeInfoFromVariable(callee.name); + if (!calleeInfo || calleeInfo.name !== "URL") return; + + const request = getUrlRequest(expr); + + if (request) return true; + }); + }; + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, parserCallback); + + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, parserCallback); + } + ); + } +} + +module.exports = URLPlugin; diff --git a/webpack-lib/lib/dependencies/UnsupportedDependency.js b/webpack-lib/lib/dependencies/UnsupportedDependency.js new file mode 100644 index 00000000000..6796634c9b4 --- /dev/null +++ b/webpack-lib/lib/dependencies/UnsupportedDependency.js @@ -0,0 +1,82 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const NullDependency = require("./NullDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class UnsupportedDependency extends NullDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(); + + this.request = request; + this.range = range; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.request); + write(this.range); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.request = read(); + this.range = read(); + + super.deserialize(context); + } +} + +makeSerializable( + UnsupportedDependency, + "webpack/lib/dependencies/UnsupportedDependency" +); + +UnsupportedDependency.Template = class UnsupportedDependencyTemplate extends ( + NullDependency.Template +) { + /** + * @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, { runtimeTemplate }) { + const dep = /** @type {UnsupportedDependency} */ (dependency); + + source.replace( + dep.range[0], + dep.range[1], + runtimeTemplate.missingModule({ + request: dep.request + }) + ); + } +}; + +module.exports = UnsupportedDependency; diff --git a/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js b/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js new file mode 100644 index 00000000000..c2452ecd1c6 --- /dev/null +++ b/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js @@ -0,0 +1,93 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class WebAssemblyExportImportedDependency extends ModuleDependency { + /** + * @param {string} exportName export name + * @param {string} request request + * @param {string} name name + * @param {TODO} valueType value type + */ + constructor(exportName, request, name, valueType) { + super(request); + /** @type {string} */ + this.exportName = exportName; + /** @type {string} */ + this.name = name; + /** @type {string} */ + this.valueType = valueType; + } + + /** + * @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 Dependency.TRANSITIVE; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return [[this.name]]; + } + + get type() { + return "wasm export import"; + } + + get category() { + return "wasm"; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.exportName); + write(this.name); + write(this.valueType); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.exportName = read(); + this.name = read(); + this.valueType = read(); + + super.deserialize(context); + } +} + +makeSerializable( + WebAssemblyExportImportedDependency, + "webpack/lib/dependencies/WebAssemblyExportImportedDependency" +); + +module.exports = WebAssemblyExportImportedDependency; diff --git a/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js b/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js new file mode 100644 index 00000000000..de23f76f19a --- /dev/null +++ b/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js @@ -0,0 +1,108 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("../util/makeSerializable"); +const UnsupportedWebAssemblyFeatureError = require("../wasm-sync/UnsupportedWebAssemblyFeatureError"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("@webassemblyjs/ast").ModuleImportDescription} ModuleImportDescription */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class WebAssemblyImportDependency extends ModuleDependency { + /** + * @param {string} request the request + * @param {string} name the imported name + * @param {ModuleImportDescription} description the WASM ast node + * @param {false | string} onlyDirectImport if only direct imports are allowed + */ + constructor(request, name, description, onlyDirectImport) { + super(request); + /** @type {string} */ + this.name = name; + /** @type {ModuleImportDescription} */ + this.description = description; + /** @type {false | string} */ + this.onlyDirectImport = onlyDirectImport; + } + + get type() { + return "wasm import"; + } + + get category() { + return "wasm"; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return [[this.name]]; + } + + /** + * Returns errors + * @param {ModuleGraph} moduleGraph module graph + * @returns {WebpackError[] | null | undefined} errors + */ + getErrors(moduleGraph) { + const module = moduleGraph.getModule(this); + + if ( + this.onlyDirectImport && + module && + !module.type.startsWith("webassembly") + ) { + return [ + new UnsupportedWebAssemblyFeatureError( + `Import "${this.name}" from "${this.request}" with ${this.onlyDirectImport} can only be used for direct wasm to wasm dependencies` + ) + ]; + } + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + + write(this.name); + write(this.description); + write(this.onlyDirectImport); + + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + + this.name = read(); + this.description = read(); + this.onlyDirectImport = read(); + + super.deserialize(context); + } +} + +makeSerializable( + WebAssemblyImportDependency, + "webpack/lib/dependencies/WebAssemblyImportDependency" +); + +module.exports = WebAssemblyImportDependency; diff --git a/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js b/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js new file mode 100644 index 00000000000..0b308734ee7 --- /dev/null +++ b/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js @@ -0,0 +1,85 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const Template = require("../Template"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class WebpackIsIncludedDependency extends ModuleDependency { + /** + * @param {string} request the request string + * @param {Range} range location in source code + */ + constructor(request, range) { + super(request); + + this.weak = true; + this.range = range; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + // This doesn't use any export + return Dependency.NO_EXPORTS_REFERENCED; + } + + get type() { + return "__webpack_is_included__"; + } +} + +makeSerializable( + WebpackIsIncludedDependency, + "webpack/lib/dependencies/WebpackIsIncludedDependency" +); + +WebpackIsIncludedDependency.Template = class WebpackIsIncludedDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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, { runtimeTemplate, chunkGraph, moduleGraph }) { + const dep = /** @type {WebpackIsIncludedDependency} */ (dependency); + const connection = moduleGraph.getConnection(dep); + const included = connection + ? chunkGraph.getNumberOfModuleChunks(connection.module) > 0 + : false; + const comment = runtimeTemplate.outputOptions.pathinfo + ? Template.toComment( + `__webpack_is_included__ ${runtimeTemplate.requestShortener.shorten( + dep.request + )}` + ) + : ""; + + source.replace( + dep.range[0], + dep.range[1] - 1, + `${comment}${JSON.stringify(included)}` + ); + } +}; + +module.exports = WebpackIsIncludedDependency; diff --git a/webpack-lib/lib/dependencies/WorkerDependency.js b/webpack-lib/lib/dependencies/WorkerDependency.js new file mode 100644 index 00000000000..16693d1a4cb --- /dev/null +++ b/webpack-lib/lib/dependencies/WorkerDependency.js @@ -0,0 +1,133 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const ModuleDependency = require("./ModuleDependency"); + +/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ +/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../Entrypoint")} Entrypoint */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +class WorkerDependency extends ModuleDependency { + /** + * @param {string} request request + * @param {Range} range range + * @param {object} workerDependencyOptions options + * @param {string=} workerDependencyOptions.publicPath public path for the worker + */ + constructor(request, range, workerDependencyOptions) { + super(request); + this.range = range; + // If options are updated, don't forget to update the hash and serialization functions + this.options = workerDependencyOptions; + /** Cache the hash */ + this._hashUpdate = undefined; + } + + /** + * 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 {(string[] | ReferencedExport)[]} referenced exports + */ + getReferencedExports(moduleGraph, runtime) { + return Dependency.NO_EXPORTS_REFERENCED; + } + + get type() { + return "new Worker()"; + } + + get category() { + return "worker"; + } + + /** + * Update the hash + * @param {Hash} hash hash to be updated + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + if (this._hashUpdate === undefined) { + this._hashUpdate = JSON.stringify(this.options); + } + hash.update(this._hashUpdate); + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.options); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.options = read(); + super.deserialize(context); + } +} + +WorkerDependency.Template = class WorkerDependencyTemplate extends ( + ModuleDependency.Template +) { + /** + * @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 { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; + const dep = /** @type {WorkerDependency} */ (dependency); + const block = /** @type {AsyncDependenciesBlock} */ ( + moduleGraph.getParentBlock(dependency) + ); + const entrypoint = /** @type {Entrypoint} */ ( + chunkGraph.getBlockChunkGroup(block) + ); + const chunk = entrypoint.getEntrypointChunk(); + // We use the workerPublicPath option if provided, else we fallback to the RuntimeGlobal publicPath + const workerImportBaseUrl = dep.options.publicPath + ? `"${dep.options.publicPath}"` + : RuntimeGlobals.publicPath; + + runtimeRequirements.add(RuntimeGlobals.publicPath); + runtimeRequirements.add(RuntimeGlobals.baseURI); + runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename); + + source.replace( + dep.range[0], + dep.range[1] - 1, + `/* worker import */ ${workerImportBaseUrl} + ${ + RuntimeGlobals.getChunkScriptFilename + }(${JSON.stringify(chunk.id)}), ${RuntimeGlobals.baseURI}` + ); + } +}; + +makeSerializable(WorkerDependency, "webpack/lib/dependencies/WorkerDependency"); + +module.exports = WorkerDependency; diff --git a/webpack-lib/lib/dependencies/WorkerPlugin.js b/webpack-lib/lib/dependencies/WorkerPlugin.js new file mode 100644 index 00000000000..4da16c5c40b --- /dev/null +++ b/webpack-lib/lib/dependencies/WorkerPlugin.js @@ -0,0 +1,500 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { pathToFileURL } = require("url"); +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const CommentCompilationWarning = require("../CommentCompilationWarning"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); +const { equals } = require("../util/ArrayHelpers"); +const createHash = require("../util/createHash"); +const { contextify } = require("../util/identifier"); +const EnableWasmLoadingPlugin = require("../wasm/EnableWasmLoadingPlugin"); +const ConstDependency = require("./ConstDependency"); +const CreateScriptUrlDependency = require("./CreateScriptUrlDependency"); +const { + harmonySpecifierTag +} = require("./HarmonyImportDependencyParserPlugin"); +const WorkerDependency = require("./WorkerDependency"); + +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").Pattern} Pattern */ +/** @typedef {import("estree").Property} Property */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ +/** @typedef {import("../../declarations/WebpackOptions").ChunkLoading} ChunkLoading */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../../declarations/WebpackOptions").OutputModule} OutputModule */ +/** @typedef {import("../../declarations/WebpackOptions").WasmLoading} WasmLoading */ +/** @typedef {import("../../declarations/WebpackOptions").WorkerPublicPath} WorkerPublicPath */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser")} Parser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../util/createHash").Algorithm} Algorithm */ +/** @typedef {import("./HarmonyImportDependencyParserPlugin").HarmonySettings} HarmonySettings */ + +/** + * @param {NormalModule} module module + * @returns {string} url + */ +const getUrl = module => pathToFileURL(module.resource).toString(); + +const WorkerSpecifierTag = Symbol("worker specifier tag"); + +const DEFAULT_SYNTAX = [ + "Worker", + "SharedWorker", + "navigator.serviceWorker.register()", + "Worker from worker_threads" +]; + +/** @type {WeakMap} */ +const workerIndexMap = new WeakMap(); + +const PLUGIN_NAME = "WorkerPlugin"; + +class WorkerPlugin { + /** + * @param {ChunkLoading=} chunkLoading chunk loading + * @param {WasmLoading=} wasmLoading wasm loading + * @param {OutputModule=} module output module + * @param {WorkerPublicPath=} workerPublicPath worker public path + */ + constructor(chunkLoading, wasmLoading, module, workerPublicPath) { + this._chunkLoading = chunkLoading; + this._wasmLoading = wasmLoading; + this._module = module; + this._workerPublicPath = workerPublicPath; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + if (this._chunkLoading) { + new EnableChunkLoadingPlugin(this._chunkLoading).apply(compiler); + } + if (this._wasmLoading) { + new EnableWasmLoadingPlugin(this._wasmLoading).apply(compiler); + } + const cachedContextify = contextify.bindContextCache( + compiler.context, + compiler.root + ); + compiler.hooks.thisCompilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + WorkerDependency, + normalModuleFactory + ); + compilation.dependencyTemplates.set( + WorkerDependency, + new WorkerDependency.Template() + ); + compilation.dependencyTemplates.set( + CreateScriptUrlDependency, + new CreateScriptUrlDependency.Template() + ); + + /** + * @param {JavascriptParser} parser the parser + * @param {Expression} expr expression + * @returns {[BasicEvaluatedExpression, [number, number]] | void} parsed + */ + const parseModuleUrl = (parser, expr) => { + if ( + expr.type !== "NewExpression" || + expr.callee.type === "Super" || + expr.arguments.length !== 2 + ) + return; + const [arg1, arg2] = expr.arguments; + if (arg1.type === "SpreadElement") return; + if (arg2.type === "SpreadElement") return; + const callee = parser.evaluateExpression(expr.callee); + if (!callee.isIdentifier() || callee.identifier !== "URL") return; + const arg2Value = parser.evaluateExpression(arg2); + if ( + !arg2Value.isString() || + !(/** @type {string} */ (arg2Value.string).startsWith("file://")) || + arg2Value.string !== getUrl(parser.state.module) + ) { + return; + } + const arg1Value = parser.evaluateExpression(arg1); + return [ + arg1Value, + [ + /** @type {Range} */ (arg1.range)[0], + /** @type {Range} */ (arg2.range)[1] + ] + ]; + }; + + /** + * @param {JavascriptParser} parser the parser + * @param {ObjectExpression} expr expression + * @returns {{ expressions: Record, otherElements: (Property | SpreadElement)[], values: Record, spread: boolean, insertType: "comma" | "single", insertLocation: number }} parsed object + */ + const parseObjectExpression = (parser, expr) => { + /** @type {Record} */ + const values = {}; + /** @type {Record} */ + const expressions = {}; + /** @type {(Property | SpreadElement)[]} */ + const otherElements = []; + let spread = false; + for (const prop of expr.properties) { + if (prop.type === "SpreadElement") { + spread = true; + } else if ( + prop.type === "Property" && + !prop.method && + !prop.computed && + prop.key.type === "Identifier" + ) { + expressions[prop.key.name] = prop.value; + if (!prop.shorthand && !prop.value.type.endsWith("Pattern")) { + const value = parser.evaluateExpression( + /** @type {Expression} */ (prop.value) + ); + if (value.isCompileTimeValue()) + values[prop.key.name] = value.asCompileTimeValue(); + } + } else { + otherElements.push(prop); + } + } + const insertType = expr.properties.length > 0 ? "comma" : "single"; + const insertLocation = /** @type {Range} */ ( + expr.properties[expr.properties.length - 1].range + )[1]; + return { + expressions, + otherElements, + values, + spread, + insertType, + insertLocation + }; + }; + + /** + * @param {Parser} parser parser parser + * @param {JavascriptParserOptions} parserOptions parserOptions + * @returns {void} + */ + const parserPlugin = (parser, parserOptions) => { + if (parserOptions.worker === false) return; + const options = !Array.isArray(parserOptions.worker) + ? ["..."] + : parserOptions.worker; + /** + * @param {CallExpression} expr expression + * @returns {boolean | void} true when handled + */ + const handleNewWorker = expr => { + if (expr.arguments.length === 0 || expr.arguments.length > 2) + return; + const [arg1, arg2] = expr.arguments; + if (arg1.type === "SpreadElement") return; + if (arg2 && arg2.type === "SpreadElement") return; + const parsedUrl = parseModuleUrl(parser, arg1); + if (!parsedUrl) return; + const [url, range] = parsedUrl; + if (!url.isString()) return; + const { + expressions, + otherElements, + values: options, + spread: hasSpreadInOptions, + insertType, + insertLocation + } = arg2 && arg2.type === "ObjectExpression" + ? parseObjectExpression(parser, arg2) + : { + /** @type {Record} */ + expressions: {}, + otherElements: [], + /** @type {Record} */ + values: {}, + spread: false, + insertType: arg2 ? "spread" : "argument", + insertLocation: arg2 + ? /** @type {Range} */ (arg2.range) + : /** @type {Range} */ (arg1.range)[1] + }; + const { options: importOptions, errors: commentErrors } = + parser.parseCommentOptions(/** @type {Range} */ (expr.range)); + + if (commentErrors) { + for (const e of commentErrors) { + const { comment } = e; + parser.state.module.addWarning( + new CommentCompilationWarning( + `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, + /** @type {DependencyLocation} */ (comment.loc) + ) + ); + } + } + + /** @type {EntryOptions} */ + const entryOptions = {}; + + if (importOptions) { + if (importOptions.webpackIgnore !== undefined) { + if (typeof importOptions.webpackIgnore !== "boolean") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else if (importOptions.webpackIgnore) { + return false; + } + } + if (importOptions.webpackEntryOptions !== undefined) { + if ( + typeof importOptions.webpackEntryOptions !== "object" || + importOptions.webpackEntryOptions === null + ) { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackEntryOptions\` expected a object, but received: ${importOptions.webpackEntryOptions}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + Object.assign( + entryOptions, + importOptions.webpackEntryOptions + ); + } + } + if (importOptions.webpackChunkName !== undefined) { + if (typeof importOptions.webpackChunkName !== "string") { + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, + /** @type {DependencyLocation} */ (expr.loc) + ) + ); + } else { + entryOptions.name = importOptions.webpackChunkName; + } + } + } + + if ( + !Object.prototype.hasOwnProperty.call(entryOptions, "name") && + options && + typeof options.name === "string" + ) { + entryOptions.name = options.name; + } + + if (entryOptions.runtime === undefined) { + const i = workerIndexMap.get(parser.state) || 0; + workerIndexMap.set(parser.state, i + 1); + const name = `${cachedContextify( + parser.state.module.identifier() + )}|${i}`; + const hash = createHash( + /** @type {Algorithm} */ + (compilation.outputOptions.hashFunction) + ); + hash.update(name); + const digest = + /** @type {string} */ + (hash.digest(compilation.outputOptions.hashDigest)); + entryOptions.runtime = digest.slice( + 0, + compilation.outputOptions.hashDigestLength + ); + } + + const block = new AsyncDependenciesBlock({ + name: entryOptions.name, + entryOptions: { + chunkLoading: this._chunkLoading, + wasmLoading: this._wasmLoading, + ...entryOptions + } + }); + block.loc = expr.loc; + const dep = new WorkerDependency( + /** @type {string} */ (url.string), + range, + { + publicPath: this._workerPublicPath + } + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + block.addDependency(dep); + parser.state.module.addBlock(block); + + if (compilation.outputOptions.trustedTypes) { + const dep = new CreateScriptUrlDependency( + /** @type {Range} */ (expr.arguments[0].range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addDependency(dep); + } + + if (expressions.type) { + const expr = expressions.type; + if (options.type !== false) { + const dep = new ConstDependency( + this._module ? '"module"' : "undefined", + /** @type {Range} */ (expr.range) + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + /** @type {TODO} */ + (expressions).type = undefined; + } + } else if (insertType === "comma") { + if (this._module || hasSpreadInOptions) { + const dep = new ConstDependency( + `, type: ${this._module ? '"module"' : "undefined"}`, + insertLocation + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } + } else if (insertType === "spread") { + const dep1 = new ConstDependency( + "Object.assign({}, ", + /** @type {Range} */ (insertLocation)[0] + ); + const dep2 = new ConstDependency( + `, { type: ${this._module ? '"module"' : "undefined"} })`, + /** @type {Range} */ (insertLocation)[1] + ); + dep1.loc = /** @type {DependencyLocation} */ (expr.loc); + dep2.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep1); + parser.state.module.addPresentationalDependency(dep2); + } else if (insertType === "argument" && this._module) { + const dep = new ConstDependency( + ', { type: "module" }', + insertLocation + ); + dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + } + + parser.walkExpression(expr.callee); + for (const key of Object.keys(expressions)) { + if (expressions[key]) parser.walkExpression(expressions[key]); + } + for (const prop of otherElements) { + parser.walkProperty(prop); + } + if (insertType === "spread") { + parser.walkExpression(arg2); + } + + return true; + }; + /** + * @param {string} item item + */ + const processItem = item => { + if ( + item.startsWith("*") && + item.includes(".") && + item.endsWith("()") + ) { + const firstDot = item.indexOf("."); + const pattern = item.slice(1, firstDot); + const itemMembers = item.slice(firstDot + 1, -2); + + parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => { + if (decl.id.type === "Identifier" && decl.id.name === pattern) { + parser.tagVariable(decl.id.name, WorkerSpecifierTag); + return true; + } + }); + parser.hooks.pattern.for(pattern).tap(PLUGIN_NAME, pattern => { + parser.tagVariable(pattern.name, WorkerSpecifierTag); + return true; + }); + parser.hooks.callMemberChain + .for(WorkerSpecifierTag) + .tap(PLUGIN_NAME, (expression, members) => { + if (itemMembers !== members.join(".")) { + return; + } + + return handleNewWorker(expression); + }); + } else if (item.endsWith("()")) { + parser.hooks.call + .for(item.slice(0, -2)) + .tap(PLUGIN_NAME, handleNewWorker); + } else { + const match = /^(.+?)(\(\))?\s+from\s+(.+)$/.exec(item); + if (match) { + const ids = match[1].split("."); + const call = match[2]; + const source = match[3]; + (call ? parser.hooks.call : parser.hooks.new) + .for(harmonySpecifierTag) + .tap(PLUGIN_NAME, expr => { + const settings = /** @type {HarmonySettings} */ ( + parser.currentTagData + ); + if ( + !settings || + settings.source !== source || + !equals(settings.ids, ids) + ) { + return; + } + return handleNewWorker(expr); + }); + } else { + parser.hooks.new.for(item).tap(PLUGIN_NAME, handleNewWorker); + } + } + }; + for (const item of options) { + if (item === "...") { + for (const itemFromDefault of DEFAULT_SYNTAX) { + processItem(itemFromDefault); + } + } else processItem(item); + } + }; + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, parserPlugin); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, parserPlugin); + } + ); + } +} +module.exports = WorkerPlugin; diff --git a/webpack-lib/lib/dependencies/getFunctionExpression.js b/webpack-lib/lib/dependencies/getFunctionExpression.js new file mode 100644 index 00000000000..f4495b500ff --- /dev/null +++ b/webpack-lib/lib/dependencies/getFunctionExpression.js @@ -0,0 +1,64 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").FunctionExpression} FunctionExpression */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ + +/** + * @param {Expression | SpreadElement} expr expressions + * @returns {{fn: FunctionExpression | ArrowFunctionExpression, expressions: (Expression | SpreadElement)[], needThis: boolean | undefined } | undefined} function expression with additional information + */ +module.exports = expr => { + // + if ( + expr.type === "FunctionExpression" || + expr.type === "ArrowFunctionExpression" + ) { + return { + fn: expr, + expressions: [], + needThis: false + }; + } + + // .bind() + if ( + expr.type === "CallExpression" && + expr.callee.type === "MemberExpression" && + expr.callee.object.type === "FunctionExpression" && + expr.callee.property.type === "Identifier" && + expr.callee.property.name === "bind" && + expr.arguments.length === 1 + ) { + return { + fn: expr.callee.object, + expressions: [expr.arguments[0]], + needThis: undefined + }; + } + // (function(_this) {return })(this) (Coffeescript) + if ( + expr.type === "CallExpression" && + expr.callee.type === "FunctionExpression" && + expr.callee.body.type === "BlockStatement" && + expr.arguments.length === 1 && + expr.arguments[0].type === "ThisExpression" && + expr.callee.body.body && + expr.callee.body.body.length === 1 && + expr.callee.body.body[0].type === "ReturnStatement" && + expr.callee.body.body[0].argument && + expr.callee.body.body[0].argument.type === "FunctionExpression" + ) { + return { + fn: expr.callee.body.body[0].argument, + expressions: [], + needThis: true + }; + } +}; diff --git a/webpack-lib/lib/dependencies/processExportInfo.js b/webpack-lib/lib/dependencies/processExportInfo.js new file mode 100644 index 00000000000..9bafcae635a --- /dev/null +++ b/webpack-lib/lib/dependencies/processExportInfo.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); + +/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** @typedef {string[][]} ReferencedExports */ + +/** + * @param {RuntimeSpec} runtime the runtime + * @param {ReferencedExports} referencedExports list of referenced exports, will be added to + * @param {string[]} prefix export prefix + * @param {ExportInfo=} exportInfo the export info + * @param {boolean} defaultPointsToSelf when true, using default will reference itself + * @param {Set} alreadyVisited already visited export info (to handle circular reexports) + */ +const processExportInfo = ( + runtime, + referencedExports, + prefix, + exportInfo, + defaultPointsToSelf = false, + alreadyVisited = new Set() +) => { + if (!exportInfo) { + referencedExports.push(prefix); + return; + } + const used = exportInfo.getUsed(runtime); + if (used === UsageState.Unused) return; + if (alreadyVisited.has(exportInfo)) { + referencedExports.push(prefix); + return; + } + alreadyVisited.add(exportInfo); + if ( + used !== UsageState.OnlyPropertiesUsed || + !exportInfo.exportsInfo || + exportInfo.exportsInfo.otherExportsInfo.getUsed(runtime) !== + UsageState.Unused + ) { + alreadyVisited.delete(exportInfo); + referencedExports.push(prefix); + return; + } + const exportsInfo = exportInfo.exportsInfo; + for (const exportInfo of exportsInfo.orderedExports) { + processExportInfo( + runtime, + referencedExports, + defaultPointsToSelf && exportInfo.name === "default" + ? prefix + : prefix.concat(exportInfo.name), + exportInfo, + false, + alreadyVisited + ); + } + alreadyVisited.delete(exportInfo); +}; +module.exports = processExportInfo; diff --git a/webpack-lib/lib/electron/ElectronTargetPlugin.js b/webpack-lib/lib/electron/ElectronTargetPlugin.js new file mode 100644 index 00000000000..e8c4e844a87 --- /dev/null +++ b/webpack-lib/lib/electron/ElectronTargetPlugin.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ExternalsPlugin = require("../ExternalsPlugin"); + +/** @typedef {import("../Compiler")} Compiler */ + +class ElectronTargetPlugin { + /** + * @param {"main" | "preload" | "renderer"=} context in main, preload or renderer context? + */ + constructor(context) { + this._context = context; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + new ExternalsPlugin("node-commonjs", [ + "clipboard", + "crash-reporter", + "electron", + "ipc", + "native-image", + "original-fs", + "screen", + "shell" + ]).apply(compiler); + switch (this._context) { + case "main": + new ExternalsPlugin("node-commonjs", [ + "app", + "auto-updater", + "browser-window", + "content-tracing", + "dialog", + "global-shortcut", + "ipc-main", + "menu", + "menu-item", + "power-monitor", + "power-save-blocker", + "protocol", + "session", + "tray", + "web-contents" + ]).apply(compiler); + break; + case "preload": + case "renderer": + new ExternalsPlugin("node-commonjs", [ + "desktop-capturer", + "ipc-renderer", + "remote", + "web-frame" + ]).apply(compiler); + break; + } + } +} + +module.exports = ElectronTargetPlugin; diff --git a/webpack-lib/lib/errors/BuildCycleError.js b/webpack-lib/lib/errors/BuildCycleError.js new file mode 100644 index 00000000000..a235fcebbe4 --- /dev/null +++ b/webpack-lib/lib/errors/BuildCycleError.js @@ -0,0 +1,27 @@ +/* + 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 BuildCycleError extends WebpackError { + /** + * Creates an instance of ModuleDependencyError. + * @param {Module} module the module starting the cycle + */ + constructor(module) { + super( + "There is a circular build dependency, which makes it impossible to create this module" + ); + + this.name = "BuildCycleError"; + this.module = module; + } +} + +module.exports = BuildCycleError; diff --git a/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js b/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js new file mode 100644 index 00000000000..30a275fa432 --- /dev/null +++ b/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js @@ -0,0 +1,30 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +class ExportWebpackRequireRuntimeModule extends RuntimeModule { + constructor() { + super("export webpack runtime", RuntimeModule.STAGE_ATTACH); + } + + /** + * @returns {boolean} true, if the runtime module should get it's own scope + */ + shouldIsolate() { + return false; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return `export default ${RuntimeGlobals.require};`; + } +} + +module.exports = ExportWebpackRequireRuntimeModule; diff --git a/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js b/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js new file mode 100644 index 00000000000..abf6481351b --- /dev/null +++ b/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js @@ -0,0 +1,218 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const { RuntimeGlobals } = require(".."); +const HotUpdateChunk = require("../HotUpdateChunk"); +const Template = require("../Template"); +const { getAllChunks } = require("../javascript/ChunkHelpers"); +const { + chunkHasJs, + getCompilationHooks, + getChunkFilenameTemplate +} = require("../javascript/JavascriptModulesPlugin"); +const { updateHashForEntryStartup } = require("../javascript/StartupHelpers"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Entrypoint")} Entrypoint */ + +class ModuleChunkFormatPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "ModuleChunkFormatPlugin", + compilation => { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + "ModuleChunkFormatPlugin", + (chunk, set) => { + if (chunk.hasRuntime()) return; + if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) { + set.add(RuntimeGlobals.require); + set.add(RuntimeGlobals.startupEntrypoint); + set.add(RuntimeGlobals.externalInstallChunk); + } + } + ); + const hooks = getCompilationHooks(compilation); + hooks.renderChunk.tap( + "ModuleChunkFormatPlugin", + (modules, renderContext) => { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + const hotUpdateChunk = + chunk instanceof HotUpdateChunk ? chunk : null; + const source = new ConcatSource(); + if (hotUpdateChunk) { + throw new Error( + "HMR is not implemented for module chunk format yet" + ); + } else { + source.add( + `export const __webpack_id__ = ${JSON.stringify(chunk.id)};\n` + ); + source.add( + `export const __webpack_ids__ = ${JSON.stringify(chunk.ids)};\n` + ); + source.add("export const __webpack_modules__ = "); + source.add(modules); + source.add(";\n"); + const runtimeModules = + chunkGraph.getChunkRuntimeModulesInOrder(chunk); + if (runtimeModules.length > 0) { + source.add("export const __webpack_runtime__ =\n"); + source.add( + Template.renderChunkRuntimeModules( + runtimeModules, + renderContext + ) + ); + } + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + if (entries.length > 0) { + const runtimeChunk = + /** @type {Entrypoint[][]} */ + (entries)[0][1].getRuntimeChunk(); + const currentOutputName = compilation + .getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ) + .replace(/^\/+/g, "") + .split("/"); + + /** + * @param {Chunk} chunk the chunk + * @returns {string} the relative path + */ + const getRelativePath = chunk => { + const baseOutputName = currentOutputName.slice(); + const chunkOutputName = compilation + .getPath( + getChunkFilenameTemplate( + chunk, + compilation.outputOptions + ), + { + chunk, + contentHashType: "javascript" + } + ) + .replace(/^\/+/g, "") + .split("/"); + + // remove common parts except filename + while ( + baseOutputName.length > 1 && + chunkOutputName.length > 1 && + baseOutputName[0] === chunkOutputName[0] + ) { + baseOutputName.shift(); + chunkOutputName.shift(); + } + const last = chunkOutputName.join("/"); + // create final path + return ( + getUndoPath(baseOutputName.join("/"), last, true) + last + ); + }; + + const entrySource = new ConcatSource(); + entrySource.add(source); + entrySource.add(";\n\n// load runtime\n"); + entrySource.add( + `import ${RuntimeGlobals.require} from ${JSON.stringify( + getRelativePath(/** @type {Chunk} */ (runtimeChunk)) + )};\n` + ); + + const startupSource = new ConcatSource(); + startupSource.add( + `var __webpack_exec__ = ${runtimeTemplate.returningFunction( + `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`, + "moduleId" + )}\n` + ); + + const loadedChunks = new Set(); + let index = 0; + for (let i = 0; i < entries.length; i++) { + const [module, entrypoint] = entries[i]; + const final = i + 1 === entries.length; + const moduleId = chunkGraph.getModuleId(module); + const chunks = getAllChunks( + /** @type {Entrypoint} */ (entrypoint), + /** @type {Chunk} */ (runtimeChunk), + undefined + ); + for (const chunk of chunks) { + if ( + loadedChunks.has(chunk) || + !chunkHasJs(chunk, chunkGraph) + ) + continue; + loadedChunks.add(chunk); + startupSource.add( + `import * as __webpack_chunk_${index}__ from ${JSON.stringify( + getRelativePath(chunk) + )};\n` + ); + startupSource.add( + `${RuntimeGlobals.externalInstallChunk}(__webpack_chunk_${index}__);\n` + ); + index++; + } + startupSource.add( + `${ + final ? `var ${RuntimeGlobals.exports} = ` : "" + }__webpack_exec__(${JSON.stringify(moduleId)});\n` + ); + } + + entrySource.add( + hooks.renderStartup.call( + startupSource, + entries[entries.length - 1][0], + { + ...renderContext, + inlined: false + } + ) + ); + return entrySource; + } + } + return source; + } + ); + hooks.chunkHash.tap( + "ModuleChunkFormatPlugin", + (chunk, hash, { chunkGraph, runtimeTemplate }) => { + if (chunk.hasRuntime()) return; + hash.update("ModuleChunkFormatPlugin"); + hash.update("1"); + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + updateHashForEntryStartup(hash, chunkGraph, entries, chunk); + } + ); + } + ); + } +} + +module.exports = ModuleChunkFormatPlugin; diff --git a/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js b/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js new file mode 100644 index 00000000000..b88533a8363 --- /dev/null +++ b/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js @@ -0,0 +1,87 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const ExportWebpackRequireRuntimeModule = require("./ExportWebpackRequireRuntimeModule"); +const ModuleChunkLoadingRuntimeModule = require("./ModuleChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +class ModuleChunkLoadingPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "ModuleChunkLoadingPlugin", + compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk chunk to check + * @returns {boolean} true, when the plugin is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === "import"; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk to check + * @param {Set} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.hasOwnProperty); + compilation.addRuntimeModule( + chunk, + new ModuleChunkLoadingRuntimeModule(set) + ); + }; + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("ModuleChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.baseURI) + .tap("ModuleChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.externalInstallChunk) + .tap("ModuleChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.onChunksLoaded) + .tap("ModuleChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.externalInstallChunk) + .tap("ModuleChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + compilation.addRuntimeModule( + chunk, + new ExportWebpackRequireRuntimeModule() + ); + }); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("ModuleChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.getChunkScriptFilename); + }); + } + ); + } +} + +module.exports = ModuleChunkLoadingPlugin; diff --git a/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js b/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..69dd231f97f --- /dev/null +++ b/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js @@ -0,0 +1,351 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + getChunkFilenameTemplate, + chunkHasJs +} = require("../javascript/JavascriptModulesPlugin"); +const { getInitialChunkIds } = require("../javascript/StartupHelpers"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../../declarations/WebpackOptions").Environment} Environment */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +/** + * @typedef {object} JsonpCompilationPluginHooks + * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload + * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class ModuleChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {Compilation} compilation the compilation + * @returns {JsonpCompilationPluginHooks} 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 = { + linkPreload: new SyncWaterfallHook(["source", "chunk"]), + linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("import chunk loading", RuntimeModule.STAGE_ATTACH); + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @private + * @param {Chunk} chunk chunk + * @param {string} rootOutputDir root output directory + * @returns {string} generated code + */ + _generateBaseUri(chunk, rootOutputDir) { + const options = chunk.getEntryOptions(); + if (options && options.baseUri) { + return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; + } + const compilation = /** @type {Compilation} */ (this.compilation); + const { + outputOptions: { importMetaName } + } = compilation; + return `${RuntimeGlobals.baseURI} = new URL(${JSON.stringify( + rootOutputDir + )}, ${importMetaName}.url);`; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const environment = + /** @type {Environment} */ + (compilation.outputOptions.environment); + const { + runtimeTemplate, + outputOptions: { importFunctionName, crossOriginLoading } + } = compilation; + const fn = RuntimeGlobals.ensureChunkHandlers; + const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI); + const withExternalInstallChunk = this._runtimeRequirements.has( + RuntimeGlobals.externalInstallChunk + ); + const withLoading = this._runtimeRequirements.has( + RuntimeGlobals.ensureChunkHandlers + ); + const withOnChunkLoad = this._runtimeRequirements.has( + RuntimeGlobals.onChunksLoaded + ); + const withHmr = this._runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const { linkPreload, linkPrefetch } = + ModuleChunkLoadingRuntimeModule.getCompilationHooks(compilation); + const isNeutralPlatform = runtimeTemplate.isNeutralPlatform(); + const withPrefetch = + (environment.document || isNeutralPlatform) && + this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && + chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs); + const withPreload = + (environment.document || isNeutralPlatform) && + this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && + chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs); + const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); + const hasJsMatcher = compileBooleanMatcher(conditionMap); + const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); + + const outputName = compilation.getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ); + const rootOutputDir = getUndoPath( + outputName, + /** @type {string} */ (compilation.outputOptions.path), + true + ); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_module` + : undefined; + + return Template.asString([ + withBaseURI + ? this._generateBaseUri(chunk, rootOutputDir) + : "// no baseURI", + "", + "// object to store loaded and loading chunks", + "// undefined = chunk not loaded, null = chunk preloaded/prefetched", + "// [resolve, Promise] = chunk loading, 0 = chunk loaded", + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( + ",\n" + ) + ), + "};", + "", + withLoading || withExternalInstallChunk + ? `var installChunk = ${runtimeTemplate.basicFunction("data", [ + runtimeTemplate.destructureObject( + ["__webpack_ids__", "__webpack_modules__", "__webpack_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_modules__) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(__webpack_modules__, moduleId)) {`, + Template.indent( + `${RuntimeGlobals.moduleFactories}[moduleId] = __webpack_modules__[moduleId];` + ), + "}" + ]), + "}", + `if(__webpack_runtime__) __webpack_runtime__(${RuntimeGlobals.require});`, + "for(;i < __webpack_ids__.length; i++) {", + Template.indent([ + "chunkId = __webpack_ids__[i];", + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`, + Template.indent("installedChunks[chunkId][0]();"), + "}", + "installedChunks[__webpack_ids__[i]] = 0;" + ]), + "}", + withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" + ])}` + : "// no install chunk", + "", + withLoading + ? Template.asString([ + `${fn}.j = ${runtimeTemplate.basicFunction( + "chunkId, promises", + hasJsMatcher !== false + ? Template.indent([ + "// import() chunk loading for javascript", + `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + "", + '// a Promise means "currently loading".', + "if(installedChunkData) {", + Template.indent([ + "promises.push(installedChunkData[1]);" + ]), + "} else {", + Template.indent([ + hasJsMatcher === true + ? "if(true) { // all chunks have JS" + : `if(${hasJsMatcher("chunkId")}) {`, + Template.indent([ + "// setup Promise in chunk cache", + `var promise = ${importFunctionName}(${JSON.stringify( + rootOutputDir + )} + ${ + RuntimeGlobals.getChunkScriptFilename + }(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction( + "e", + [ + "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;", + "throw e;" + ] + )});`, + `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction( + "installedChunkData = installedChunks[chunkId] = [resolve]", + "resolve" + )})])`, + "promises.push(installedChunkData[1] = promise);" + ]), + hasJsMatcher === true + ? "}" + : "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ]) + : Template.indent(["installedChunks[chunkId] = 0;"]) + )};` + ]) + : "// no chunk on demand loading", + "", + withPrefetch && hasJsMatcher !== false + ? `${ + RuntimeGlobals.prefetchChunkHandlers + }.j = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + isNeutralPlatform + ? "if (typeof document === 'undefined') return;" + : "", + linkPrefetch.call( + Template.asString([ + "var link = document.createElement('link');", + crossOriginLoading + ? `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + : "", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "prefetch";', + 'link.as = "script";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);` + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no prefetching", + "", + withPreload && hasJsMatcher !== false + ? `${ + RuntimeGlobals.preloadChunkHandlers + }.j = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + isNeutralPlatform + ? "if (typeof document === 'undefined') return;" + : "", + linkPreload.call( + Template.asString([ + "var link = document.createElement('link');", + "link.charset = 'utf-8';", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "modulepreload";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + ), + "}" + ]) + : "" + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no preloaded", + "", + withExternalInstallChunk + ? Template.asString([ + `${RuntimeGlobals.externalInstallChunk} = installChunk;` + ]) + : "// no external install chunk", + "", + withOnChunkLoad + ? `${ + RuntimeGlobals.onChunksLoaded + }.j = ${runtimeTemplate.returningFunction( + "installedChunks[chunkId] === 0", + "chunkId" + )};` + : "// no on chunks loaded" + ]); + } +} + +module.exports = ModuleChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/formatLocation.js b/webpack-lib/lib/formatLocation.js new file mode 100644 index 00000000000..780d4a475ca --- /dev/null +++ b/webpack-lib/lib/formatLocation.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("./Dependency").SourcePosition} SourcePosition */ + +/** + * @param {SourcePosition} pos position + * @returns {string} formatted position + */ +const formatPosition = pos => { + if (pos && typeof pos === "object") { + if ("line" in pos && "column" in pos) { + return `${pos.line}:${pos.column}`; + } else if ("line" in pos) { + return `${pos.line}:?`; + } + } + return ""; +}; + +/** + * @param {DependencyLocation} loc location + * @returns {string} formatted location + */ +const formatLocation = loc => { + if (loc && typeof loc === "object") { + if ("start" in loc && loc.start && "end" in loc && loc.end) { + if ( + typeof loc.start === "object" && + typeof loc.start.line === "number" && + typeof loc.end === "object" && + typeof loc.end.line === "number" && + typeof loc.end.column === "number" && + loc.start.line === loc.end.line + ) { + return `${formatPosition(loc.start)}-${loc.end.column}`; + } else if ( + typeof loc.start === "object" && + typeof loc.start.line === "number" && + typeof loc.start.column !== "number" && + typeof loc.end === "object" && + typeof loc.end.line === "number" && + typeof loc.end.column !== "number" + ) { + return `${loc.start.line}-${loc.end.line}`; + } + return `${formatPosition(loc.start)}-${formatPosition(loc.end)}`; + } + if ("start" in loc && loc.start) { + return formatPosition(loc.start); + } + if ("name" in loc && "index" in loc) { + return `${loc.name}[${loc.index}]`; + } + if ("name" in loc) { + return loc.name; + } + } + return ""; +}; + +module.exports = formatLocation; diff --git a/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js b/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js new file mode 100644 index 00000000000..e9fec891700 --- /dev/null +++ b/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js @@ -0,0 +1,407 @@ +// @ts-nocheck +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +var $interceptModuleExecution$ = undefined; +var $moduleCache$ = undefined; +// eslint-disable-next-line no-unused-vars +var $hmrModuleData$ = undefined; +/** @type {() => Promise} */ +var $hmrDownloadManifest$ = undefined; +var $hmrDownloadUpdateHandlers$ = undefined; +var $hmrInvalidateModuleHandlers$ = undefined; +var __webpack_require__ = undefined; + +module.exports = function () { + var currentModuleData = {}; + var installedModules = $moduleCache$; + + // module and require creation + var currentChildModule; + var currentParents = []; + + // status + var registeredStatusHandlers = []; + var currentStatus = "idle"; + + // while downloading + var blockingPromises = 0; + var blockingPromisesWaiting = []; + + // The update info + var currentUpdateApplyHandlers; + var queuedInvalidatedModules; + + $hmrModuleData$ = currentModuleData; + + $interceptModuleExecution$.push(function (options) { + var module = options.module; + var require = createRequire(options.require, options.id); + module.hot = createModuleHotObject(options.id, module); + module.parents = currentParents; + module.children = []; + currentParents = []; + options.require = require; + }); + + $hmrDownloadUpdateHandlers$ = {}; + $hmrInvalidateModuleHandlers$ = {}; + + function createRequire(require, moduleId) { + var me = installedModules[moduleId]; + if (!me) return require; + var fn = function (request) { + if (me.hot.active) { + if (installedModules[request]) { + var parents = installedModules[request].parents; + if (parents.indexOf(moduleId) === -1) { + parents.push(moduleId); + } + } else { + currentParents = [moduleId]; + currentChildModule = request; + } + if (me.children.indexOf(request) === -1) { + me.children.push(request); + } + } else { + console.warn( + "[HMR] unexpected require(" + + request + + ") from disposed module " + + moduleId + ); + currentParents = []; + } + return require(request); + }; + var createPropertyDescriptor = function (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") { + Object.defineProperty(fn, name, createPropertyDescriptor(name)); + } + } + fn.e = function (chunkId, fetchPriority) { + return trackBlockingPromise(require.e(chunkId, fetchPriority)); + }; + return fn; + } + + function createModuleHotObject(moduleId, me) { + var _main = currentChildModule !== moduleId; + var hot = { + // private stuff + _acceptedDependencies: {}, + _acceptedErrorHandlers: {}, + _declinedDependencies: {}, + _selfAccepted: false, + _selfDeclined: false, + _selfInvalidated: false, + _disposeHandlers: [], + _main: _main, + _requireSelf: function () { + currentParents = me.parents.slice(); + currentChildModule = _main ? undefined : moduleId; + __webpack_require__(moduleId); + }, + + // Module API + active: true, + accept: function (dep, callback, errorHandler) { + if (dep === undefined) hot._selfAccepted = true; + else if (typeof dep === "function") hot._selfAccepted = dep; + else if (typeof dep === "object" && dep !== null) { + for (var i = 0; i < dep.length; i++) { + hot._acceptedDependencies[dep[i]] = callback || function () {}; + hot._acceptedErrorHandlers[dep[i]] = errorHandler; + } + } else { + hot._acceptedDependencies[dep] = callback || function () {}; + hot._acceptedErrorHandlers[dep] = errorHandler; + } + }, + decline: function (dep) { + if (dep === undefined) hot._selfDeclined = true; + else if (typeof dep === "object" && dep !== null) + 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); + }, + invalidate: function () { + this._selfInvalidated = true; + switch (currentStatus) { + case "idle": + currentUpdateApplyHandlers = []; + Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { + $hmrInvalidateModuleHandlers$[key]( + moduleId, + currentUpdateApplyHandlers + ); + }); + setStatus("ready"); + break; + case "ready": + Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { + $hmrInvalidateModuleHandlers$[key]( + moduleId, + currentUpdateApplyHandlers + ); + }); + break; + case "prepare": + case "check": + case "dispose": + case "apply": + (queuedInvalidatedModules = queuedInvalidatedModules || []).push( + moduleId + ); + break; + default: + // ignore requests in error states + break; + } + }, + + // Management API + check: hotCheck, + apply: hotApply, + status: function (l) { + if (!l) return currentStatus; + registeredStatusHandlers.push(l); + }, + addStatusHandler: function (l) { + registeredStatusHandlers.push(l); + }, + removeStatusHandler: function (l) { + var idx = registeredStatusHandlers.indexOf(l); + if (idx >= 0) registeredStatusHandlers.splice(idx, 1); + }, + + // inherit from previous dispose call + data: currentModuleData[moduleId] + }; + currentChildModule = undefined; + return hot; + } + + function setStatus(newStatus) { + currentStatus = newStatus; + var results = []; + + for (var i = 0; i < registeredStatusHandlers.length; i++) + results[i] = registeredStatusHandlers[i].call(null, newStatus); + + return Promise.all(results).then(function () {}); + } + + function unblock() { + if (--blockingPromises === 0) { + setStatus("ready").then(function () { + if (blockingPromises === 0) { + var list = blockingPromisesWaiting; + blockingPromisesWaiting = []; + for (var i = 0; i < list.length; i++) { + list[i](); + } + } + }); + } + } + + function trackBlockingPromise(promise) { + switch (currentStatus) { + case "ready": + setStatus("prepare"); + /* fallthrough */ + case "prepare": + blockingPromises++; + promise.then(unblock, unblock); + return promise; + default: + return promise; + } + } + + function waitForBlockingPromises(fn) { + if (blockingPromises === 0) return fn(); + return new Promise(function (resolve) { + blockingPromisesWaiting.push(function () { + resolve(fn()); + }); + }); + } + + function hotCheck(applyOnUpdate) { + if (currentStatus !== "idle") { + throw new Error("check() is only allowed in idle status"); + } + return setStatus("check") + .then($hmrDownloadManifest$) + .then(function (update) { + if (!update) { + return setStatus(applyInvalidatedModules() ? "ready" : "idle").then( + function () { + return null; + } + ); + } + + return setStatus("prepare").then(function () { + var updatedModules = []; + currentUpdateApplyHandlers = []; + + return Promise.all( + Object.keys($hmrDownloadUpdateHandlers$).reduce(function ( + promises, + key + ) { + $hmrDownloadUpdateHandlers$[key]( + update.c, + update.r, + update.m, + promises, + currentUpdateApplyHandlers, + updatedModules + ); + return promises; + }, []) + ).then(function () { + return waitForBlockingPromises(function () { + if (applyOnUpdate) { + return internalApply(applyOnUpdate); + } + return setStatus("ready").then(function () { + return updatedModules; + }); + }); + }); + }); + }); + } + + function hotApply(options) { + if (currentStatus !== "ready") { + return Promise.resolve().then(function () { + throw new Error( + "apply() is only allowed in ready status (state: " + + currentStatus + + ")" + ); + }); + } + return internalApply(options); + } + + function internalApply(options) { + options = options || {}; + + applyInvalidatedModules(); + + var results = currentUpdateApplyHandlers.map(function (handler) { + return handler(options); + }); + currentUpdateApplyHandlers = undefined; + + var errors = results + .map(function (r) { + return r.error; + }) + .filter(Boolean); + + if (errors.length > 0) { + return setStatus("abort").then(function () { + throw errors[0]; + }); + } + + // Now in "dispose" phase + var disposePromise = setStatus("dispose"); + + results.forEach(function (result) { + if (result.dispose) result.dispose(); + }); + + // Now in "apply" phase + var applyPromise = setStatus("apply"); + + var error; + var reportError = function (err) { + if (!error) error = err; + }; + + var outdatedModules = []; + results.forEach(function (result) { + if (result.apply) { + var modules = result.apply(reportError); + if (modules) { + for (var i = 0; i < modules.length; i++) { + outdatedModules.push(modules[i]); + } + } + } + }); + + return Promise.all([disposePromise, applyPromise]).then(function () { + // handle errors in accept handlers and self accepted module load + if (error) { + return setStatus("fail").then(function () { + throw error; + }); + } + + if (queuedInvalidatedModules) { + return internalApply(options).then(function (list) { + outdatedModules.forEach(function (moduleId) { + if (list.indexOf(moduleId) < 0) list.push(moduleId); + }); + return list; + }); + } + + return setStatus("idle").then(function () { + return outdatedModules; + }); + }); + } + + function applyInvalidatedModules() { + if (queuedInvalidatedModules) { + if (!currentUpdateApplyHandlers) currentUpdateApplyHandlers = []; + Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { + queuedInvalidatedModules.forEach(function (moduleId) { + $hmrInvalidateModuleHandlers$[key]( + moduleId, + currentUpdateApplyHandlers + ); + }); + }); + queuedInvalidatedModules = undefined; + return true; + } + } +}; diff --git a/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js b/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js new file mode 100644 index 00000000000..b1a254b6db9 --- /dev/null +++ b/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +class HotModuleReplacementRuntimeModule extends RuntimeModule { + constructor() { + super("hot module replacement", RuntimeModule.STAGE_BASIC); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return Template.getFunctionContent( + require("./HotModuleReplacement.runtime.js") + ) + .replace( + /\$interceptModuleExecution\$/g, + RuntimeGlobals.interceptModuleExecution + ) + .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) + .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) + .replace(/\$hmrDownloadManifest\$/g, RuntimeGlobals.hmrDownloadManifest) + .replace( + /\$hmrInvalidateModuleHandlers\$/g, + RuntimeGlobals.hmrInvalidateModuleHandlers + ) + .replace( + /\$hmrDownloadUpdateHandlers\$/g, + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + } +} + +module.exports = HotModuleReplacementRuntimeModule; diff --git a/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js b/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js new file mode 100644 index 00000000000..ad26d8772c1 --- /dev/null +++ b/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js @@ -0,0 +1,461 @@ +// @ts-nocheck +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +var $installedChunks$ = undefined; +var $loadUpdateChunk$ = undefined; +var $moduleCache$ = undefined; +var $moduleFactories$ = undefined; +var $ensureChunkHandlers$ = undefined; +var $hasOwnProperty$ = undefined; +var $hmrModuleData$ = undefined; +var $hmrDownloadUpdateHandlers$ = undefined; +var $hmrInvalidateModuleHandlers$ = undefined; +var __webpack_require__ = undefined; + +module.exports = function () { + var currentUpdateChunks; + var currentUpdate; + var currentUpdateRemovedChunks; + var currentUpdateRuntime; + function applyHandler(options) { + if ($ensureChunkHandlers$) delete $ensureChunkHandlers$.$key$Hmr; + currentUpdateChunks = undefined; + function getAffectedModuleEffects(updateModuleId) { + var outdatedModules = [updateModuleId]; + var outdatedDependencies = {}; + + var queue = outdatedModules.map(function (id) { + return { + chain: [id], + id: id + }; + }); + while (queue.length > 0) { + var queueItem = queue.pop(); + var moduleId = queueItem.id; + var chain = queueItem.chain; + var module = $moduleCache$[moduleId]; + if ( + !module || + (module.hot._selfAccepted && !module.hot._selfInvalidated) + ) + 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 = $moduleCache$[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(module) { + console.warn( + "[HMR] unexpected require(" + module.id + ") to disposed module" + ); + }; + + for (var moduleId in currentUpdate) { + if ($hasOwnProperty$(currentUpdate, moduleId)) { + var newModuleFactory = currentUpdate[moduleId]; + /** @type {TODO} */ + var result = newModuleFactory + ? getAffectedModuleEffects(moduleId) + : { + type: "disposed", + moduleId: moduleId + }; + /** @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) { + return { + error: abortError + }; + } + if (doApply) { + appliedUpdate[moduleId] = newModuleFactory; + addAllToSet(outdatedModules, result.outdatedModules); + for (moduleId in result.outdatedDependencies) { + if ($hasOwnProperty$(result.outdatedDependencies, moduleId)) { + if (!outdatedDependencies[moduleId]) + outdatedDependencies[moduleId] = []; + addAllToSet( + outdatedDependencies[moduleId], + result.outdatedDependencies[moduleId] + ); + } + } + } + if (doDispose) { + addAllToSet(outdatedModules, [result.moduleId]); + appliedUpdate[moduleId] = warnUnexpectedRequire; + } + } + } + currentUpdate = undefined; + + // Store self accepted outdated modules to require them later by the module system + var outdatedSelfAcceptedModules = []; + for (var j = 0; j < outdatedModules.length; j++) { + var outdatedModuleId = outdatedModules[j]; + var module = $moduleCache$[outdatedModuleId]; + if ( + module && + (module.hot._selfAccepted || module.hot._main) && + // removed self-accepted modules should not be required + appliedUpdate[outdatedModuleId] !== warnUnexpectedRequire && + // when called invalidate self-accepting is not possible + !module.hot._selfInvalidated + ) { + outdatedSelfAcceptedModules.push({ + module: outdatedModuleId, + require: module.hot._requireSelf, + errorHandler: module.hot._selfAccepted + }); + } + } + + var moduleOutdatedDependencies; + + return { + dispose: function () { + currentUpdateRemovedChunks.forEach(function (chunkId) { + delete $installedChunks$[chunkId]; + }); + currentUpdateRemovedChunks = undefined; + + var idx; + var queue = outdatedModules.slice(); + while (queue.length > 0) { + var moduleId = queue.pop(); + var module = $moduleCache$[moduleId]; + if (!module) continue; + + var data = {}; + + // Call dispose handlers + var disposeHandlers = module.hot._disposeHandlers; + for (j = 0; j < disposeHandlers.length; j++) { + disposeHandlers[j].call(null, data); + } + $hmrModuleData$[moduleId] = data; + + // disable module (this disables requires from this module) + module.hot.active = false; + + // remove module from cache + delete $moduleCache$[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 = $moduleCache$[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; + for (var outdatedModuleId in outdatedDependencies) { + if ($hasOwnProperty$(outdatedDependencies, outdatedModuleId)) { + module = $moduleCache$[outdatedModuleId]; + if (module) { + moduleOutdatedDependencies = + outdatedDependencies[outdatedModuleId]; + for (j = 0; j < moduleOutdatedDependencies.length; j++) { + dependency = moduleOutdatedDependencies[j]; + idx = module.children.indexOf(dependency); + if (idx >= 0) module.children.splice(idx, 1); + } + } + } + } + }, + apply: function (reportError) { + // insert new code + for (var updateModuleId in appliedUpdate) { + if ($hasOwnProperty$(appliedUpdate, updateModuleId)) { + $moduleFactories$[updateModuleId] = appliedUpdate[updateModuleId]; + } + } + + // run new runtime modules + for (var i = 0; i < currentUpdateRuntime.length; i++) { + currentUpdateRuntime[i](__webpack_require__); + } + + // call accept handlers + for (var outdatedModuleId in outdatedDependencies) { + if ($hasOwnProperty$(outdatedDependencies, outdatedModuleId)) { + var module = $moduleCache$[outdatedModuleId]; + if (module) { + moduleOutdatedDependencies = + outdatedDependencies[outdatedModuleId]; + var callbacks = []; + var errorHandlers = []; + var dependenciesForCallbacks = []; + for (var j = 0; j < moduleOutdatedDependencies.length; j++) { + var dependency = moduleOutdatedDependencies[j]; + var acceptCallback = + module.hot._acceptedDependencies[dependency]; + var errorHandler = + module.hot._acceptedErrorHandlers[dependency]; + if (acceptCallback) { + if (callbacks.indexOf(acceptCallback) !== -1) continue; + callbacks.push(acceptCallback); + errorHandlers.push(errorHandler); + dependenciesForCallbacks.push(dependency); + } + } + for (var k = 0; k < callbacks.length; k++) { + try { + callbacks[k].call(null, moduleOutdatedDependencies); + } catch (err) { + if (typeof errorHandlers[k] === "function") { + try { + errorHandlers[k](err, { + moduleId: outdatedModuleId, + dependencyId: dependenciesForCallbacks[k] + }); + } catch (err2) { + if (options.onErrored) { + options.onErrored({ + type: "accept-error-handler-errored", + moduleId: outdatedModuleId, + dependencyId: dependenciesForCallbacks[k], + error: err2, + originalError: err + }); + } + if (!options.ignoreErrored) { + reportError(err2); + reportError(err); + } + } + } else { + if (options.onErrored) { + options.onErrored({ + type: "accept-errored", + moduleId: outdatedModuleId, + dependencyId: dependenciesForCallbacks[k], + error: err + }); + } + if (!options.ignoreErrored) { + reportError(err); + } + } + } + } + } + } + } + + // Load self accepted modules + for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) { + var item = outdatedSelfAcceptedModules[o]; + var moduleId = item.module; + try { + item.require(moduleId); + } catch (err) { + if (typeof item.errorHandler === "function") { + try { + item.errorHandler(err, { + moduleId: moduleId, + module: $moduleCache$[moduleId] + }); + } catch (err1) { + if (options.onErrored) { + options.onErrored({ + type: "self-accept-error-handler-errored", + moduleId: moduleId, + error: err1, + originalError: err + }); + } + if (!options.ignoreErrored) { + reportError(err1); + reportError(err); + } + } + } else { + if (options.onErrored) { + options.onErrored({ + type: "self-accept-errored", + moduleId: moduleId, + error: err + }); + } + if (!options.ignoreErrored) { + reportError(err); + } + } + } + } + + return outdatedModules; + } + }; + } + $hmrInvalidateModuleHandlers$.$key$ = function (moduleId, applyHandlers) { + if (!currentUpdate) { + currentUpdate = {}; + currentUpdateRuntime = []; + currentUpdateRemovedChunks = []; + applyHandlers.push(applyHandler); + } + if (!$hasOwnProperty$(currentUpdate, moduleId)) { + currentUpdate[moduleId] = $moduleFactories$[moduleId]; + } + }; + $hmrDownloadUpdateHandlers$.$key$ = function ( + chunkIds, + removedChunks, + removedModules, + promises, + applyHandlers, + updatedModulesList + ) { + applyHandlers.push(applyHandler); + currentUpdateChunks = {}; + currentUpdateRemovedChunks = removedChunks; + currentUpdate = removedModules.reduce(function (obj, key) { + obj[key] = false; + return obj; + }, {}); + currentUpdateRuntime = []; + chunkIds.forEach(function (chunkId) { + if ( + $hasOwnProperty$($installedChunks$, chunkId) && + $installedChunks$[chunkId] !== undefined + ) { + promises.push($loadUpdateChunk$(chunkId, updatedModulesList)); + currentUpdateChunks[chunkId] = true; + } else { + currentUpdateChunks[chunkId] = false; + } + }); + if ($ensureChunkHandlers$) { + $ensureChunkHandlers$.$key$Hmr = function (chunkId, promises) { + if ( + currentUpdateChunks && + $hasOwnProperty$(currentUpdateChunks, chunkId) && + !currentUpdateChunks[chunkId] + ) { + promises.push($loadUpdateChunk$(chunkId)); + currentUpdateChunks[chunkId] = true; + } + }; + } + }; +}; diff --git a/webpack-lib/lib/hmr/LazyCompilationPlugin.js b/webpack-lib/lib/hmr/LazyCompilationPlugin.js new file mode 100644 index 00000000000..083cefb31fc --- /dev/null +++ b/webpack-lib/lib/hmr/LazyCompilationPlugin.js @@ -0,0 +1,455 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const Dependency = require("../Dependency"); +const Module = require("../Module"); +const ModuleFactory = require("../ModuleFactory"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const { + WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency"); +const { registerNotSerializable } = require("../util/serialization"); + +/** @typedef {import("../../declarations/WebpackOptions")} WebpackOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../dependencies/HarmonyImportDependency")} HarmonyImportDependency */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ + +/** @typedef {{ client: string, data: string, active: boolean }} ModuleResult */ + +/** + * @typedef {object} BackendApi + * @property {function(function((Error | null)=) : void): void} dispose + * @property {function(Module): ModuleResult} module + */ + +const HMR_DEPENDENCY_TYPES = new Set([ + "import.meta.webpackHot.accept", + "import.meta.webpackHot.decline", + "module.hot.accept", + "module.hot.decline" +]); + +/** + * @param {undefined|string|RegExp|Function} test test option + * @param {Module} module the module + * @returns {boolean | null | string} true, if the module should be selected + */ +const checkTest = (test, module) => { + if (test === undefined) return true; + if (typeof test === "function") { + return test(module); + } + if (typeof test === "string") { + const name = module.nameForCondition(); + return name && name.startsWith(test); + } + if (test instanceof RegExp) { + const name = module.nameForCondition(); + return name && test.test(name); + } + return false; +}; + +class LazyCompilationDependency extends Dependency { + /** + * @param {LazyCompilationProxyModule} proxyModule proxy module + */ + constructor(proxyModule) { + super(); + this.proxyModule = proxyModule; + } + + get category() { + return "esm"; + } + + get type() { + return "lazy import()"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return this.proxyModule.originalModule.identifier(); + } +} + +registerNotSerializable(LazyCompilationDependency); + +class LazyCompilationProxyModule extends Module { + /** + * @param {string} context context + * @param {Module} originalModule an original module + * @param {string} request request + * @param {ModuleResult["client"]} client client + * @param {ModuleResult["data"]} data data + * @param {ModuleResult["active"]} active true when active, otherwise false + */ + constructor(context, originalModule, request, client, data, active) { + super( + WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY, + context, + originalModule.layer + ); + this.originalModule = originalModule; + this.request = request; + this.client = client; + this.data = data; + this.active = active; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY}|${this.originalModule.identifier()}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY} ${this.originalModule.readableIdentifier( + requestShortener + )}`; + } + + /** + * 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) { + super.updateCacheModule(module); + const m = /** @type {LazyCompilationProxyModule} */ (module); + this.originalModule = m.originalModule; + this.request = m.request; + this.client = m.client; + this.data = m.data; + this.active = m.active; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return `${this.originalModule.libIdent( + options + )}!${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY}`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + callback(null, !this.buildInfo || this.buildInfo.active !== this.active); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildInfo = { + active: this.active + }; + /** @type {BuildMeta} */ + this.buildMeta = {}; + this.clearDependenciesAndBlocks(); + const dep = new CommonJsRequireDependency(this.client); + this.addDependency(dep); + if (this.active) { + const dep = new LazyCompilationDependency(this); + const block = new AsyncDependenciesBlock({}); + block.addDependency(dep); + this.addBlock(block); + } + callback(); + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + /** + * @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 200; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ runtimeTemplate, chunkGraph, moduleGraph }) { + const sources = new Map(); + const runtimeRequirements = new Set(); + runtimeRequirements.add(RuntimeGlobals.module); + const clientDep = /** @type {CommonJsRequireDependency} */ ( + this.dependencies[0] + ); + const clientModule = moduleGraph.getModule(clientDep); + const block = this.blocks[0]; + const client = Template.asString([ + `var client = ${runtimeTemplate.moduleExports({ + module: clientModule, + chunkGraph, + request: clientDep.userRequest, + runtimeRequirements + })}`, + `var data = ${JSON.stringify(this.data)};` + ]); + const keepActive = Template.asString([ + `var dispose = client.keepAlive({ data: data, active: ${JSON.stringify( + Boolean(block) + )}, module: module, onError: onError });` + ]); + let source; + if (block) { + const dep = block.dependencies[0]; + const module = /** @type {Module} */ (moduleGraph.getModule(dep)); + source = Template.asString([ + client, + `module.exports = ${runtimeTemplate.moduleNamespacePromise({ + chunkGraph, + block, + module, + request: this.request, + strict: false, // TODO this should be inherited from the original module + message: "import()", + runtimeRequirements + })};`, + "if (module.hot) {", + Template.indent([ + "module.hot.accept();", + `module.hot.accept(${JSON.stringify( + chunkGraph.getModuleId(module) + )}, function() { module.hot.invalidate(); });`, + "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });", + "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);" + ]), + "}", + "function onError() { /* ignore */ }", + keepActive + ]); + } else { + source = Template.asString([ + client, + "var resolveSelf, onError;", + "module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });", + "if (module.hot) {", + Template.indent([ + "module.hot.accept();", + "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", + "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });" + ]), + "}", + keepActive + ]); + } + sources.set("javascript", new RawSource(source)); + return { + sources, + runtimeRequirements + }; + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + super.updateHash(hash, context); + hash.update(this.active ? "active" : ""); + hash.update(JSON.stringify(this.data)); + } +} + +registerNotSerializable(LazyCompilationProxyModule); + +class LazyCompilationDependencyFactory extends ModuleFactory { + constructor() { + super(); + } + + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const dependency = /** @type {LazyCompilationDependency} */ ( + data.dependencies[0] + ); + callback(null, { + module: dependency.proxyModule.originalModule + }); + } +} + +/** + * @callback BackendHandler + * @param {Compiler} compiler compiler + * @param {function(Error | null, BackendApi=): void} callback callback + * @returns {void} + */ + +/** + * @callback PromiseBackendHandler + * @param {Compiler} compiler compiler + * @returns {Promise} backend + */ + +class LazyCompilationPlugin { + /** + * @param {object} options options + * @param {BackendHandler | PromiseBackendHandler} options.backend the backend + * @param {boolean} options.entries true, when entries are lazy compiled + * @param {boolean} options.imports true, when import() modules are lazy compiled + * @param {RegExp | string | (function(Module): boolean) | undefined} options.test additional filter for lazy compiled entrypoint modules + */ + constructor({ backend, entries, imports, test }) { + this.backend = backend; + this.entries = entries; + this.imports = imports; + this.test = test; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + /** @type {BackendApi} */ + let backend; + compiler.hooks.beforeCompile.tapAsync( + "LazyCompilationPlugin", + (params, callback) => { + if (backend !== undefined) return callback(); + const promise = this.backend(compiler, (err, result) => { + if (err) return callback(err); + backend = /** @type {BackendApi} */ (result); + callback(); + }); + if (promise && promise.then) { + promise.then(b => { + backend = b; + callback(); + }, callback); + } + } + ); + compiler.hooks.thisCompilation.tap( + "LazyCompilationPlugin", + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.module.tap( + "LazyCompilationPlugin", + (originalModule, createData, resolveData) => { + if ( + resolveData.dependencies.every(dep => + HMR_DEPENDENCY_TYPES.has(dep.type) + ) + ) { + // for HMR only resolving, try to determine if the HMR accept/decline refers to + // an import() or not + const hmrDep = resolveData.dependencies[0]; + const originModule = + /** @type {Module} */ + (compilation.moduleGraph.getParentModule(hmrDep)); + const isReferringToDynamicImport = originModule.blocks.some( + block => + block.dependencies.some( + dep => + dep.type === "import()" && + /** @type {HarmonyImportDependency} */ (dep).request === + hmrDep.request + ) + ); + if (!isReferringToDynamicImport) return; + } else if ( + !resolveData.dependencies.every( + dep => + HMR_DEPENDENCY_TYPES.has(dep.type) || + (this.imports && + (dep.type === "import()" || + dep.type === "import() context element")) || + (this.entries && dep.type === "entry") + ) + ) + return; + if ( + /webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client|webpack-hot-middleware[/\\]client/.test( + resolveData.request + ) || + !checkTest(this.test, originalModule) + ) + return; + const moduleInfo = backend.module(originalModule); + if (!moduleInfo) return; + const { client, data, active } = moduleInfo; + + return new LazyCompilationProxyModule( + compiler.context, + originalModule, + resolveData.request, + client, + data, + active + ); + } + ); + compilation.dependencyFactories.set( + LazyCompilationDependency, + new LazyCompilationDependencyFactory() + ); + } + ); + compiler.hooks.shutdown.tapAsync("LazyCompilationPlugin", callback => { + backend.dispose(callback); + }); + } +} + +module.exports = LazyCompilationPlugin; diff --git a/webpack-lib/lib/hmr/lazyCompilationBackend.js b/webpack-lib/lib/hmr/lazyCompilationBackend.js new file mode 100644 index 00000000000..383a16dd499 --- /dev/null +++ b/webpack-lib/lib/hmr/lazyCompilationBackend.js @@ -0,0 +1,161 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("http").IncomingMessage} IncomingMessage */ +/** @typedef {import("http").RequestListener} RequestListener */ +/** @typedef {import("http").ServerOptions} HttpServerOptions */ +/** @typedef {import("http").ServerResponse} ServerResponse */ +/** @typedef {import("https").ServerOptions} HttpsServerOptions */ +/** @typedef {import("net").AddressInfo} AddressInfo */ +/** @typedef {import("net").Server} Server */ +/** @typedef {import("../../declarations/WebpackOptions").LazyCompilationDefaultBackendOptions} LazyCompilationDefaultBackendOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("./LazyCompilationPlugin").BackendApi} BackendApi */ +/** @typedef {import("./LazyCompilationPlugin").BackendHandler} BackendHandler */ + +/** + * @param {Omit & { client: NonNullable}} options additional options for the backend + * @returns {BackendHandler} backend + */ +module.exports = options => (compiler, callback) => { + const logger = compiler.getInfrastructureLogger("LazyCompilationBackend"); + const activeModules = new Map(); + const prefix = "/lazy-compilation-using-"; + + const isHttps = + options.protocol === "https" || + (typeof options.server === "object" && + ("key" in options.server || "pfx" in options.server)); + + const createServer = + typeof options.server === "function" + ? options.server + : (() => { + const http = isHttps ? require("https") : require("http"); + return http.createServer.bind( + http, + /** @type {HttpServerOptions | HttpsServerOptions} */ + (options.server) + ); + })(); + /** @type {function(Server): void} */ + const listen = + typeof options.listen === "function" + ? options.listen + : server => { + let listen = options.listen; + if (typeof listen === "object" && !("port" in listen)) + listen = { ...listen, port: undefined }; + server.listen(listen); + }; + + const protocol = options.protocol || (isHttps ? "https" : "http"); + + /** @type {RequestListener} */ + const requestListener = (req, res) => { + if (req.url === undefined) return; + const keys = req.url.slice(prefix.length).split("@"); + req.socket.on("close", () => { + setTimeout(() => { + for (const key of keys) { + const oldValue = activeModules.get(key) || 0; + activeModules.set(key, oldValue - 1); + if (oldValue === 1) { + logger.log( + `${key} is no longer in use. Next compilation will skip this module.` + ); + } + } + }, 120000); + }); + req.socket.setNoDelay(true); + res.writeHead(200, { + "content-type": "text/event-stream", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "*", + "Access-Control-Allow-Headers": "*" + }); + res.write("\n"); + let moduleActivated = false; + for (const key of keys) { + const oldValue = activeModules.get(key) || 0; + activeModules.set(key, oldValue + 1); + if (oldValue === 0) { + logger.log(`${key} is now in use and will be compiled.`); + moduleActivated = true; + } + } + if (moduleActivated && compiler.watching) compiler.watching.invalidate(); + }; + + const server = /** @type {Server} */ (createServer()); + server.on("request", requestListener); + + let isClosing = false; + /** @type {Set} */ + const sockets = new Set(); + server.on("connection", socket => { + sockets.add(socket); + socket.on("close", () => { + sockets.delete(socket); + }); + if (isClosing) socket.destroy(); + }); + server.on("clientError", e => { + if (e.message !== "Server is disposing") logger.warn(e); + }); + + server.on( + "listening", + /** + * @param {Error} err error + * @returns {void} + */ + err => { + if (err) return callback(err); + const _addr = server.address(); + if (typeof _addr === "string") + throw new Error("addr must not be a string"); + const addr = /** @type {AddressInfo} */ (_addr); + const urlBase = + addr.address === "::" || addr.address === "0.0.0.0" + ? `${protocol}://localhost:${addr.port}` + : addr.family === "IPv6" + ? `${protocol}://[${addr.address}]:${addr.port}` + : `${protocol}://${addr.address}:${addr.port}`; + logger.log( + `Server-Sent-Events server for lazy compilation open at ${urlBase}.` + ); + callback(null, { + dispose(callback) { + isClosing = true; + // Removing the listener is a workaround for a memory leak in node.js + server.off("request", requestListener); + server.close(err => { + callback(err); + }); + for (const socket of sockets) { + socket.destroy(new Error("Server is disposing")); + } + }, + module(originalModule) { + const key = `${encodeURIComponent( + originalModule.identifier().replace(/\\/g, "/").replace(/@/g, "_") + ).replace(/%(2F|3A|24|26|2B|2C|3B|3D)/g, decodeURIComponent)}`; + const active = activeModules.get(key) > 0; + return { + client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`, + data: key, + active + }; + } + }); + } + ); + listen(server); +}; diff --git a/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js b/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js new file mode 100644 index 00000000000..e0922b43be8 --- /dev/null +++ b/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js @@ -0,0 +1,89 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { find } = require("../util/SetHelpers"); +const { + compareModulesByPreOrderIndexOrIdentifier, + compareModulesByPostOrderIndexOrIdentifier +} = require("../util/comparators"); + +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} ChunkModuleIdRangePluginOptions + * @property {string} name the chunk name + * @property {("index" | "index2" | "preOrderIndex" | "postOrderIndex")=} order order + * @property {number=} start start id + * @property {number=} end end id + */ + +class ChunkModuleIdRangePlugin { + /** + * @param {ChunkModuleIdRangePluginOptions} options options object + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap("ChunkModuleIdRangePlugin", compilation => { + const moduleGraph = compilation.moduleGraph; + compilation.hooks.moduleIds.tap("ChunkModuleIdRangePlugin", modules => { + const chunkGraph = compilation.chunkGraph; + const chunk = find( + compilation.chunks, + chunk => chunk.name === options.name + ); + if (!chunk) { + throw new Error( + `ChunkModuleIdRangePlugin: Chunk with name '${options.name}"' was not found` + ); + } + + let chunkModules; + if (options.order) { + let cmpFn; + switch (options.order) { + case "index": + case "preOrderIndex": + cmpFn = compareModulesByPreOrderIndexOrIdentifier(moduleGraph); + break; + case "index2": + case "postOrderIndex": + cmpFn = compareModulesByPostOrderIndexOrIdentifier(moduleGraph); + break; + default: + throw new Error( + "ChunkModuleIdRangePlugin: unexpected value of order" + ); + } + chunkModules = chunkGraph.getOrderedChunkModules(chunk, cmpFn); + } else { + chunkModules = Array.from(modules) + .filter(m => chunkGraph.isModuleInChunk(m, chunk)) + .sort(compareModulesByPreOrderIndexOrIdentifier(moduleGraph)); + } + + let currentId = options.start || 0; + for (let i = 0; i < chunkModules.length; i++) { + const m = chunkModules[i]; + if (m.needId && chunkGraph.getModuleId(m) === null) { + chunkGraph.setModuleId(m, currentId++); + } + if (options.end && currentId > options.end) break; + } + }); + }); + } +} +module.exports = ChunkModuleIdRangePlugin; diff --git a/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js b/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js new file mode 100644 index 00000000000..735bc5f166a --- /dev/null +++ b/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js @@ -0,0 +1,77 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const { compareChunksNatural } = require("../util/comparators"); +const { + getFullChunkName, + getUsedChunkIds, + assignDeterministicIds +} = require("./IdHelpers"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +/** + * @typedef {object} DeterministicChunkIdsPluginOptions + * @property {string=} context context for ids + * @property {number=} maxLength maximum length of ids + */ + +class DeterministicChunkIdsPlugin { + /** + * @param {DeterministicChunkIdsPluginOptions} [options] options + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DeterministicChunkIdsPlugin", + compilation => { + compilation.hooks.chunkIds.tap( + "DeterministicChunkIdsPlugin", + chunks => { + const chunkGraph = compilation.chunkGraph; + const context = this.options.context + ? this.options.context + : compiler.context; + const maxLength = this.options.maxLength || 3; + + const compareNatural = compareChunksNatural(chunkGraph); + + const usedIds = getUsedChunkIds(compilation); + assignDeterministicIds( + Array.from(chunks).filter(chunk => chunk.id === null), + chunk => + getFullChunkName(chunk, chunkGraph, context, compiler.root), + compareNatural, + (chunk, id) => { + const size = usedIds.size; + usedIds.add(`${id}`); + if (size === usedIds.size) return false; + chunk.id = id; + chunk.ids = [id]; + return true; + }, + [10 ** maxLength], + 10, + usedIds.size + ); + } + ); + } + ); + } +} + +module.exports = DeterministicChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js b/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js new file mode 100644 index 00000000000..8cf07e082c3 --- /dev/null +++ b/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js @@ -0,0 +1,97 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const { + compareModulesByPreOrderIndexOrIdentifier +} = require("../util/comparators"); +const { + getUsedModuleIdsAndModules, + getFullModuleName, + assignDeterministicIds +} = require("./IdHelpers"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +/** + * @typedef {object} DeterministicModuleIdsPluginOptions + * @property {string=} context context relative to which module identifiers are computed + * @property {function(Module): boolean=} test selector function for modules + * @property {number=} maxLength maximum id length in digits (used as starting point) + * @property {number=} salt hash salt for ids + * @property {boolean=} fixedLength do not increase the maxLength to find an optimal id space size + * @property {boolean=} failOnConflict throw an error when id conflicts occur (instead of rehashing) + */ + +class DeterministicModuleIdsPlugin { + /** + * @param {DeterministicModuleIdsPluginOptions} [options] options + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DeterministicModuleIdsPlugin", + compilation => { + compilation.hooks.moduleIds.tap("DeterministicModuleIdsPlugin", () => { + const chunkGraph = compilation.chunkGraph; + const context = this.options.context + ? this.options.context + : compiler.context; + const maxLength = this.options.maxLength || 3; + const failOnConflict = this.options.failOnConflict || false; + const fixedLength = this.options.fixedLength || false; + const salt = this.options.salt || 0; + let conflicts = 0; + + const [usedIds, modules] = getUsedModuleIdsAndModules( + compilation, + this.options.test + ); + assignDeterministicIds( + modules, + module => getFullModuleName(module, context, compiler.root), + failOnConflict + ? () => 0 + : compareModulesByPreOrderIndexOrIdentifier( + compilation.moduleGraph + ), + (module, id) => { + const size = usedIds.size; + usedIds.add(`${id}`); + if (size === usedIds.size) { + conflicts++; + return false; + } + chunkGraph.setModuleId(module, id); + return true; + }, + [10 ** maxLength], + fixedLength ? 0 : 10, + usedIds.size, + salt + ); + if (failOnConflict && conflicts) + throw new Error( + `Assigning deterministic module ids has lead to ${conflicts} conflict${ + conflicts > 1 ? "s" : "" + }.\nIncrease the 'maxLength' to increase the id space and make conflicts less likely (recommended when there are many conflicts or application is expected to grow), or add an 'salt' number to try another hash starting value in the same id space (recommended when there is only a single conflict).` + ); + }); + } + ); + } +} + +module.exports = DeterministicModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/HashedModuleIdsPlugin.js b/webpack-lib/lib/ids/HashedModuleIdsPlugin.js new file mode 100644 index 00000000000..e3891a4699e --- /dev/null +++ b/webpack-lib/lib/ids/HashedModuleIdsPlugin.js @@ -0,0 +1,88 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { + compareModulesByPreOrderIndexOrIdentifier +} = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const createHash = require("../util/createHash"); +const { + getUsedModuleIdsAndModules, + getFullModuleName +} = require("./IdHelpers"); + +/** @typedef {import("../../declarations/plugins/HashedModuleIdsPlugin").HashedModuleIdsPluginOptions} HashedModuleIdsPluginOptions */ +/** @typedef {import("../Compiler")} Compiler */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/HashedModuleIdsPlugin.check.js"), + () => require("../../schemas/plugins/HashedModuleIdsPlugin.json"), + { + name: "Hashed Module Ids Plugin", + baseDataPath: "options" + } +); + +class HashedModuleIdsPlugin { + /** + * @param {HashedModuleIdsPluginOptions=} options options object + */ + constructor(options = {}) { + validate(options); + + /** @type {HashedModuleIdsPluginOptions} */ + this.options = { + context: undefined, + hashFunction: "md4", + hashDigest: "base64", + hashDigestLength: 4, + ...options + }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap("HashedModuleIdsPlugin", compilation => { + compilation.hooks.moduleIds.tap("HashedModuleIdsPlugin", () => { + const chunkGraph = compilation.chunkGraph; + const context = this.options.context + ? this.options.context + : compiler.context; + + const [usedIds, modules] = getUsedModuleIdsAndModules(compilation); + const modulesInNaturalOrder = modules.sort( + compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph) + ); + for (const module of modulesInNaturalOrder) { + const ident = getFullModuleName(module, context, compiler.root); + const hash = createHash( + /** @type {NonNullable} */ ( + options.hashFunction + ) + ); + hash.update(ident || ""); + const hashId = /** @type {string} */ ( + hash.digest(options.hashDigest) + ); + let len = options.hashDigestLength; + while (usedIds.has(hashId.slice(0, len))) + /** @type {number} */ (len)++; + const moduleId = hashId.slice(0, len); + chunkGraph.setModuleId(module, moduleId); + usedIds.add(moduleId); + } + }); + }); + } +} + +module.exports = HashedModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/IdHelpers.js b/webpack-lib/lib/ids/IdHelpers.js new file mode 100644 index 00000000000..78cdaef0508 --- /dev/null +++ b/webpack-lib/lib/ids/IdHelpers.js @@ -0,0 +1,473 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const createHash = require("../util/createHash"); +const { makePathsRelative } = require("../util/identifier"); +const numberHash = require("../util/numberHash"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {typeof import("../util/Hash")} Hash */ + +/** + * @param {string} str string to hash + * @param {number} len max length of the hash + * @param {string | Hash} hashFunction hash function to use + * @returns {string} hash + */ +const getHash = (str, len, hashFunction) => { + const hash = createHash(hashFunction); + hash.update(str); + const digest = /** @type {string} */ (hash.digest("hex")); + return digest.slice(0, len); +}; + +/** + * @param {string} str the string + * @returns {string} string prefixed by an underscore if it is a number + */ +const avoidNumber = str => { + // max length of a number is 21 chars, bigger numbers a written as "...e+xx" + if (str.length > 21) return str; + const firstChar = str.charCodeAt(0); + // skip everything that doesn't look like a number + // charCodes: "-": 45, "1": 49, "9": 57 + if (firstChar < 49) { + if (firstChar !== 45) return str; + } else if (firstChar > 57) { + return str; + } + if (str === String(Number(str))) { + return `_${str}`; + } + return str; +}; + +/** + * @param {string} request the request + * @returns {string} id representation + */ +const requestToId = request => + request.replace(/^(\.\.?\/)+/, "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_"); +module.exports.requestToId = requestToId; + +/** + * @param {string} string the string + * @param {string} delimiter separator for string and hash + * @param {string | Hash} hashFunction hash function to use + * @returns {string} string with limited max length to 100 chars + */ +const shortenLongString = (string, delimiter, hashFunction) => { + if (string.length < 100) return string; + return ( + string.slice(0, 100 - 6 - delimiter.length) + + delimiter + + getHash(string, 6, hashFunction) + ); +}; + +/** + * @param {Module} module the module + * @param {string} context context directory + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} short module name + */ +const getShortModuleName = (module, context, associatedObjectForCache) => { + const libIdent = module.libIdent({ context, associatedObjectForCache }); + if (libIdent) return avoidNumber(libIdent); + const nameForCondition = module.nameForCondition(); + if (nameForCondition) + return avoidNumber( + makePathsRelative(context, nameForCondition, associatedObjectForCache) + ); + return ""; +}; +module.exports.getShortModuleName = getShortModuleName; + +/** + * @param {string} shortName the short name + * @param {Module} module the module + * @param {string} context context directory + * @param {string | Hash} hashFunction hash function to use + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} long module name + */ +const getLongModuleName = ( + shortName, + module, + context, + hashFunction, + associatedObjectForCache +) => { + const fullName = getFullModuleName(module, context, associatedObjectForCache); + return `${shortName}?${getHash(fullName, 4, hashFunction)}`; +}; +module.exports.getLongModuleName = getLongModuleName; + +/** + * @param {Module} module the module + * @param {string} context context directory + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} full module name + */ +const getFullModuleName = (module, context, associatedObjectForCache) => + makePathsRelative(context, module.identifier(), associatedObjectForCache); +module.exports.getFullModuleName = getFullModuleName; + +/** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {string} context context directory + * @param {string} delimiter delimiter for names + * @param {string | Hash} hashFunction hash function to use + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} short chunk name + */ +const getShortChunkName = ( + chunk, + chunkGraph, + context, + delimiter, + hashFunction, + associatedObjectForCache +) => { + const modules = chunkGraph.getChunkRootModules(chunk); + const shortModuleNames = modules.map(m => + requestToId(getShortModuleName(m, context, associatedObjectForCache)) + ); + chunk.idNameHints.sort(); + const chunkName = Array.from(chunk.idNameHints) + .concat(shortModuleNames) + .filter(Boolean) + .join(delimiter); + return shortenLongString(chunkName, delimiter, hashFunction); +}; +module.exports.getShortChunkName = getShortChunkName; + +/** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {string} context context directory + * @param {string} delimiter delimiter for names + * @param {string | Hash} hashFunction hash function to use + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} short chunk name + */ +const getLongChunkName = ( + chunk, + chunkGraph, + context, + delimiter, + hashFunction, + associatedObjectForCache +) => { + const modules = chunkGraph.getChunkRootModules(chunk); + const shortModuleNames = modules.map(m => + requestToId(getShortModuleName(m, context, associatedObjectForCache)) + ); + const longModuleNames = modules.map(m => + requestToId( + getLongModuleName("", m, context, hashFunction, associatedObjectForCache) + ) + ); + chunk.idNameHints.sort(); + const chunkName = Array.from(chunk.idNameHints) + .concat(shortModuleNames, longModuleNames) + .filter(Boolean) + .join(delimiter); + return shortenLongString(chunkName, delimiter, hashFunction); +}; +module.exports.getLongChunkName = getLongChunkName; + +/** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {string} context context directory + * @param {object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} full chunk name + */ +const getFullChunkName = ( + chunk, + chunkGraph, + context, + associatedObjectForCache +) => { + if (chunk.name) return chunk.name; + const modules = chunkGraph.getChunkRootModules(chunk); + const fullModuleNames = modules.map(m => + makePathsRelative(context, m.identifier(), associatedObjectForCache) + ); + return fullModuleNames.join(); +}; +module.exports.getFullChunkName = getFullChunkName; + +/** + * @template K + * @template V + * @param {Map} map a map from key to values + * @param {K} key key + * @param {V} value value + * @returns {void} + */ +const addToMapOfItems = (map, key, value) => { + let array = map.get(key); + if (array === undefined) { + array = []; + map.set(key, array); + } + array.push(value); +}; + +/** + * @param {Compilation} compilation the compilation + * @param {function(Module): boolean=} filter filter modules + * @returns {[Set, Module[]]} used module ids as strings and modules without id matching the filter + */ +const getUsedModuleIdsAndModules = (compilation, filter) => { + const chunkGraph = compilation.chunkGraph; + + const modules = []; + + /** @type {Set} */ + const usedIds = new Set(); + if (compilation.usedModuleIds) { + for (const id of compilation.usedModuleIds) { + usedIds.add(String(id)); + } + } + + for (const module of compilation.modules) { + if (!module.needId) continue; + const moduleId = chunkGraph.getModuleId(module); + if (moduleId !== null) { + usedIds.add(String(moduleId)); + } else if ( + (!filter || filter(module)) && + chunkGraph.getNumberOfModuleChunks(module) !== 0 + ) { + modules.push(module); + } + } + + return [usedIds, modules]; +}; +module.exports.getUsedModuleIdsAndModules = getUsedModuleIdsAndModules; + +/** + * @param {Compilation} compilation the compilation + * @returns {Set} used chunk ids as strings + */ +const getUsedChunkIds = compilation => { + /** @type {Set} */ + const usedIds = new Set(); + if (compilation.usedChunkIds) { + for (const id of compilation.usedChunkIds) { + usedIds.add(String(id)); + } + } + + for (const chunk of compilation.chunks) { + const chunkId = chunk.id; + if (chunkId !== null) { + usedIds.add(String(chunkId)); + } + } + + return usedIds; +}; +module.exports.getUsedChunkIds = getUsedChunkIds; + +/** + * @template T + * @param {Iterable} items list of items to be named + * @param {function(T): string} getShortName get a short name for an item + * @param {function(T, string): string} getLongName get a long name for an item + * @param {function(T, T): -1|0|1} comparator order of items + * @param {Set} usedIds already used ids, will not be assigned + * @param {function(T, string): void} assignName assign a name to an item + * @returns {T[]} list of items without a name + */ +const assignNames = ( + items, + getShortName, + getLongName, + comparator, + usedIds, + assignName +) => { + /** @type {Map} */ + const nameToItems = new Map(); + + for (const item of items) { + const name = getShortName(item); + addToMapOfItems(nameToItems, name, item); + } + + /** @type {Map} */ + const nameToItems2 = new Map(); + + for (const [name, items] of nameToItems) { + if (items.length > 1 || !name) { + for (const item of items) { + const longName = getLongName(item, name); + addToMapOfItems(nameToItems2, longName, item); + } + } else { + addToMapOfItems(nameToItems2, name, items[0]); + } + } + + /** @type {T[]} */ + const unnamedItems = []; + + for (const [name, items] of nameToItems2) { + if (!name) { + for (const item of items) { + unnamedItems.push(item); + } + } else if (items.length === 1 && !usedIds.has(name)) { + assignName(items[0], name); + usedIds.add(name); + } else { + items.sort(comparator); + let i = 0; + for (const item of items) { + while (nameToItems2.has(name + i) && usedIds.has(name + i)) i++; + assignName(item, name + i); + usedIds.add(name + i); + i++; + } + } + } + + unnamedItems.sort(comparator); + return unnamedItems; +}; +module.exports.assignNames = assignNames; + +/** + * @template T + * @param {T[]} items list of items to be named + * @param {function(T): string} getName get a name for an item + * @param {function(T, T): -1|0|1} comparator order of items + * @param {function(T, number): boolean} assignId assign an id to an item + * @param {number[]} ranges usable ranges for ids + * @param {number} expandFactor factor to create more ranges + * @param {number} extraSpace extra space to allocate, i. e. when some ids are already used + * @param {number} salt salting number to initialize hashing + * @returns {void} + */ +const assignDeterministicIds = ( + items, + getName, + comparator, + assignId, + ranges = [10], + expandFactor = 10, + extraSpace = 0, + salt = 0 +) => { + items.sort(comparator); + + // max 5% fill rate + const optimalRange = Math.min( + items.length * 20 + extraSpace, + Number.MAX_SAFE_INTEGER + ); + + let i = 0; + let range = ranges[i]; + while (range < optimalRange) { + i++; + if (i < ranges.length) { + range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER); + } else if (expandFactor) { + range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER); + } else { + break; + } + } + + for (const item of items) { + const ident = getName(item); + let id; + let i = salt; + do { + id = numberHash(ident + i++, range); + } while (!assignId(item, id)); + } +}; +module.exports.assignDeterministicIds = assignDeterministicIds; + +/** + * @param {Set} usedIds used ids + * @param {Iterable} modules the modules + * @param {Compilation} compilation the compilation + * @returns {void} + */ +const assignAscendingModuleIds = (usedIds, modules, compilation) => { + const chunkGraph = compilation.chunkGraph; + + let nextId = 0; + let assignId; + if (usedIds.size > 0) { + /** + * @param {Module} module the module + */ + assignId = module => { + if (chunkGraph.getModuleId(module) === null) { + while (usedIds.has(String(nextId))) nextId++; + chunkGraph.setModuleId(module, nextId++); + } + }; + } else { + /** + * @param {Module} module the module + */ + assignId = module => { + if (chunkGraph.getModuleId(module) === null) { + chunkGraph.setModuleId(module, nextId++); + } + }; + } + for (const module of modules) { + assignId(module); + } +}; +module.exports.assignAscendingModuleIds = assignAscendingModuleIds; + +/** + * @param {Iterable} chunks the chunks + * @param {Compilation} compilation the compilation + * @returns {void} + */ +const assignAscendingChunkIds = (chunks, compilation) => { + const usedIds = getUsedChunkIds(compilation); + + let nextId = 0; + if (usedIds.size > 0) { + for (const chunk of chunks) { + if (chunk.id === null) { + while (usedIds.has(String(nextId))) nextId++; + chunk.id = nextId; + chunk.ids = [nextId]; + nextId++; + } + } + } else { + for (const chunk of chunks) { + if (chunk.id === null) { + chunk.id = nextId; + chunk.ids = [nextId]; + nextId++; + } + } + } +}; +module.exports.assignAscendingChunkIds = assignAscendingChunkIds; diff --git a/webpack-lib/lib/ids/NamedChunkIdsPlugin.js b/webpack-lib/lib/ids/NamedChunkIdsPlugin.js new file mode 100644 index 00000000000..f55a5875a7f --- /dev/null +++ b/webpack-lib/lib/ids/NamedChunkIdsPlugin.js @@ -0,0 +1,93 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareChunksNatural } = require("../util/comparators"); +const { + getShortChunkName, + getLongChunkName, + assignNames, + getUsedChunkIds, + assignAscendingChunkIds +} = require("./IdHelpers"); + +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} Output */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +/** + * @typedef {object} NamedChunkIdsPluginOptions + * @property {string} [context] context + * @property {string} [delimiter] delimiter + */ + +class NamedChunkIdsPlugin { + /** + * @param {NamedChunkIdsPluginOptions=} options options + */ + constructor(options) { + this.delimiter = (options && options.delimiter) || "-"; + this.context = options && options.context; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("NamedChunkIdsPlugin", compilation => { + const hashFunction = + /** @type {NonNullable} */ + (compilation.outputOptions.hashFunction); + compilation.hooks.chunkIds.tap("NamedChunkIdsPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + const context = this.context ? this.context : compiler.context; + const delimiter = this.delimiter; + + const unnamedChunks = assignNames( + Array.from(chunks).filter(chunk => { + if (chunk.name) { + chunk.id = chunk.name; + chunk.ids = [chunk.name]; + } + return chunk.id === null; + }), + chunk => + getShortChunkName( + chunk, + chunkGraph, + context, + delimiter, + hashFunction, + compiler.root + ), + chunk => + getLongChunkName( + chunk, + chunkGraph, + context, + delimiter, + hashFunction, + compiler.root + ), + compareChunksNatural(chunkGraph), + getUsedChunkIds(compilation), + (chunk, name) => { + chunk.id = name; + chunk.ids = [name]; + } + ); + if (unnamedChunks.length > 0) { + assignAscendingChunkIds(unnamedChunks, compilation); + } + }); + }); + } +} + +module.exports = NamedChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/NamedModuleIdsPlugin.js b/webpack-lib/lib/ids/NamedModuleIdsPlugin.js new file mode 100644 index 00000000000..9656b8d917e --- /dev/null +++ b/webpack-lib/lib/ids/NamedModuleIdsPlugin.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareModulesByIdentifier } = require("../util/comparators"); +const { + getShortModuleName, + getLongModuleName, + assignNames, + getUsedModuleIdsAndModules, + assignAscendingModuleIds +} = require("./IdHelpers"); + +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} Output */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +/** + * @typedef {object} NamedModuleIdsPluginOptions + * @property {string} [context] context + */ + +class NamedModuleIdsPlugin { + /** + * @param {NamedModuleIdsPluginOptions} [options] options + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { root } = compiler; + compiler.hooks.compilation.tap("NamedModuleIdsPlugin", compilation => { + const hashFunction = + /** @type {NonNullable} */ + (compilation.outputOptions.hashFunction); + compilation.hooks.moduleIds.tap("NamedModuleIdsPlugin", () => { + const chunkGraph = compilation.chunkGraph; + const context = this.options.context + ? this.options.context + : compiler.context; + + const [usedIds, modules] = getUsedModuleIdsAndModules(compilation); + const unnamedModules = assignNames( + modules, + m => getShortModuleName(m, context, root), + (m, shortName) => + getLongModuleName(shortName, m, context, hashFunction, root), + compareModulesByIdentifier, + usedIds, + (m, name) => chunkGraph.setModuleId(m, name) + ); + if (unnamedModules.length > 0) { + assignAscendingModuleIds(usedIds, unnamedModules, compilation); + } + }); + }); + } +} + +module.exports = NamedModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js b/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js new file mode 100644 index 00000000000..5329ac51faf --- /dev/null +++ b/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareChunksNatural } = require("../util/comparators"); +const { assignAscendingChunkIds } = require("./IdHelpers"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +class NaturalChunkIdsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("NaturalChunkIdsPlugin", compilation => { + compilation.hooks.chunkIds.tap("NaturalChunkIdsPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + const compareNatural = compareChunksNatural(chunkGraph); + const chunksInNaturalOrder = Array.from(chunks).sort(compareNatural); + assignAscendingChunkIds(chunksInNaturalOrder, compilation); + }); + }); + } +} + +module.exports = NaturalChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js b/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js new file mode 100644 index 00000000000..962bcff38fd --- /dev/null +++ b/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js @@ -0,0 +1,39 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Florent Cailhol @ooflorent +*/ + +"use strict"; + +const { + compareModulesByPreOrderIndexOrIdentifier +} = require("../util/comparators"); +const { + assignAscendingModuleIds, + getUsedModuleIdsAndModules +} = require("./IdHelpers"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +class NaturalModuleIdsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("NaturalModuleIdsPlugin", compilation => { + compilation.hooks.moduleIds.tap("NaturalModuleIdsPlugin", modules => { + const [usedIds, modulesInNaturalOrder] = + getUsedModuleIdsAndModules(compilation); + modulesInNaturalOrder.sort( + compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph) + ); + assignAscendingModuleIds(usedIds, modulesInNaturalOrder, compilation); + }); + }); + } +} + +module.exports = NaturalModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js b/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js new file mode 100644 index 00000000000..b0acac363ec --- /dev/null +++ b/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js @@ -0,0 +1,84 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareChunksNatural } = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const { assignAscendingChunkIds } = require("./IdHelpers"); + +/** @typedef {import("../../declarations/plugins/ids/OccurrenceChunkIdsPlugin").OccurrenceChunkIdsPluginOptions} OccurrenceChunkIdsPluginOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/ids/OccurrenceChunkIdsPlugin.check.js"), + () => require("../../schemas/plugins/ids/OccurrenceChunkIdsPlugin.json"), + { + name: "Occurrence Order Chunk Ids Plugin", + baseDataPath: "options" + } +); + +class OccurrenceChunkIdsPlugin { + /** + * @param {OccurrenceChunkIdsPluginOptions=} options options object + */ + constructor(options = {}) { + validate(options); + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const prioritiseInitial = this.options.prioritiseInitial; + compiler.hooks.compilation.tap("OccurrenceChunkIdsPlugin", compilation => { + compilation.hooks.chunkIds.tap("OccurrenceChunkIdsPlugin", chunks => { + const chunkGraph = compilation.chunkGraph; + + /** @type {Map} */ + const occursInInitialChunksMap = new Map(); + + const compareNatural = compareChunksNatural(chunkGraph); + + for (const c of chunks) { + let occurs = 0; + for (const chunkGroup of c.groupsIterable) { + for (const parent of chunkGroup.parentsIterable) { + if (parent.isInitial()) occurs++; + } + } + occursInInitialChunksMap.set(c, occurs); + } + + const chunksInOccurrenceOrder = Array.from(chunks).sort((a, b) => { + if (prioritiseInitial) { + const aEntryOccurs = + /** @type {number} */ + (occursInInitialChunksMap.get(a)); + const bEntryOccurs = + /** @type {number} */ + (occursInInitialChunksMap.get(b)); + if (aEntryOccurs > bEntryOccurs) return -1; + if (aEntryOccurs < bEntryOccurs) return 1; + } + const aOccurs = a.getNumberOfGroups(); + const bOccurs = b.getNumberOfGroups(); + if (aOccurs > bOccurs) return -1; + if (aOccurs < bOccurs) return 1; + return compareNatural(a, b); + }); + assignAscendingChunkIds(chunksInOccurrenceOrder, compilation); + }); + }); + } +} + +module.exports = OccurrenceChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js b/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js new file mode 100644 index 00000000000..71fb2ce047a --- /dev/null +++ b/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js @@ -0,0 +1,159 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { + compareModulesByPreOrderIndexOrIdentifier +} = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const { + assignAscendingModuleIds, + getUsedModuleIdsAndModules +} = require("./IdHelpers"); + +/** @typedef {import("../../declarations/plugins/ids/OccurrenceModuleIdsPlugin").OccurrenceModuleIdsPluginOptions} OccurrenceModuleIdsPluginOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/ids/OccurrenceModuleIdsPlugin.check.js"), + () => require("../../schemas/plugins/ids/OccurrenceModuleIdsPlugin.json"), + { + name: "Occurrence Order Module Ids Plugin", + baseDataPath: "options" + } +); + +class OccurrenceModuleIdsPlugin { + /** + * @param {OccurrenceModuleIdsPluginOptions=} options options object + */ + constructor(options = {}) { + validate(options); + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const prioritiseInitial = this.options.prioritiseInitial; + compiler.hooks.compilation.tap("OccurrenceModuleIdsPlugin", compilation => { + const moduleGraph = compilation.moduleGraph; + + compilation.hooks.moduleIds.tap("OccurrenceModuleIdsPlugin", () => { + const chunkGraph = compilation.chunkGraph; + + const [usedIds, modulesInOccurrenceOrder] = + getUsedModuleIdsAndModules(compilation); + + const occursInInitialChunksMap = new Map(); + const occursInAllChunksMap = new Map(); + + const initialChunkChunkMap = new Map(); + const entryCountMap = new Map(); + for (const m of modulesInOccurrenceOrder) { + let initial = 0; + let entry = 0; + for (const c of chunkGraph.getModuleChunksIterable(m)) { + if (c.canBeInitial()) initial++; + if (chunkGraph.isEntryModuleInChunk(m, c)) entry++; + } + initialChunkChunkMap.set(m, initial); + entryCountMap.set(m, entry); + } + + /** + * @param {Module} module module + * @returns {number} count of occurs + */ + const countOccursInEntry = module => { + let sum = 0; + for (const [ + originModule, + connections + ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { + if (!originModule) continue; + if (!connections.some(c => c.isTargetActive(undefined))) continue; + sum += initialChunkChunkMap.get(originModule) || 0; + } + return sum; + }; + + /** + * @param {Module} module module + * @returns {number} count of occurs + */ + const countOccurs = module => { + let sum = 0; + for (const [ + originModule, + connections + ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { + if (!originModule) continue; + const chunkModules = + chunkGraph.getNumberOfModuleChunks(originModule); + for (const c of connections) { + if (!c.isTargetActive(undefined)) continue; + if (!c.dependency) continue; + const factor = c.dependency.getNumberOfIdOccurrences(); + if (factor === 0) continue; + sum += factor * chunkModules; + } + } + return sum; + }; + + if (prioritiseInitial) { + for (const m of modulesInOccurrenceOrder) { + const result = + countOccursInEntry(m) + + initialChunkChunkMap.get(m) + + entryCountMap.get(m); + occursInInitialChunksMap.set(m, result); + } + } + + for (const m of modulesInOccurrenceOrder) { + const result = + countOccurs(m) + + chunkGraph.getNumberOfModuleChunks(m) + + entryCountMap.get(m); + occursInAllChunksMap.set(m, result); + } + + const naturalCompare = compareModulesByPreOrderIndexOrIdentifier( + compilation.moduleGraph + ); + + modulesInOccurrenceOrder.sort((a, b) => { + if (prioritiseInitial) { + const aEntryOccurs = occursInInitialChunksMap.get(a); + const bEntryOccurs = occursInInitialChunksMap.get(b); + if (aEntryOccurs > bEntryOccurs) return -1; + if (aEntryOccurs < bEntryOccurs) return 1; + } + const aOccurs = occursInAllChunksMap.get(a); + const bOccurs = occursInAllChunksMap.get(b); + if (aOccurs > bOccurs) return -1; + if (aOccurs < bOccurs) return 1; + return naturalCompare(a, b); + }); + + assignAscendingModuleIds( + usedIds, + modulesInOccurrenceOrder, + compilation + ); + }); + }); + } +} + +module.exports = OccurrenceModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/SyncModuleIdsPlugin.js b/webpack-lib/lib/ids/SyncModuleIdsPlugin.js new file mode 100644 index 00000000000..aa837624e94 --- /dev/null +++ b/webpack-lib/lib/ids/SyncModuleIdsPlugin.js @@ -0,0 +1,146 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { WebpackError } = require(".."); +const { getUsedModuleIdsAndModules } = require("./IdHelpers"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ + +const plugin = "SyncModuleIdsPlugin"; + +class SyncModuleIdsPlugin { + /** + * @param {object} options options + * @param {string} options.path path to file + * @param {string=} options.context context for module names + * @param {function(Module): boolean} options.test selector for modules + * @param {"read" | "create" | "merge" | "update"=} options.mode operation mode (defaults to merge) + */ + constructor({ path, context, test, mode }) { + this._path = path; + this._context = context; + this._test = test || (() => true); + const readAndWrite = !mode || mode === "merge" || mode === "update"; + this._read = readAndWrite || mode === "read"; + this._write = readAndWrite || mode === "create"; + this._prune = mode === "update"; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + /** @type {Map} */ + let data; + let dataChanged = false; + if (this._read) { + compiler.hooks.readRecords.tapAsync(plugin, callback => { + const fs = + /** @type {IntermediateFileSystem} */ + (compiler.intermediateFileSystem); + fs.readFile(this._path, (err, buffer) => { + if (err) { + if (err.code !== "ENOENT") { + return callback(err); + } + return callback(); + } + const json = JSON.parse(/** @type {Buffer} */ (buffer).toString()); + data = new Map(); + for (const key of Object.keys(json)) { + data.set(key, json[key]); + } + dataChanged = false; + return callback(); + }); + }); + } + if (this._write) { + compiler.hooks.emitRecords.tapAsync(plugin, callback => { + if (!data || !dataChanged) return callback(); + /** @type {{[key: string]: string | number}} */ + const json = {}; + const sorted = Array.from(data).sort(([a], [b]) => (a < b ? -1 : 1)); + for (const [key, value] of sorted) { + json[key] = value; + } + const fs = + /** @type {IntermediateFileSystem} */ + (compiler.intermediateFileSystem); + fs.writeFile(this._path, JSON.stringify(json), callback); + }); + } + compiler.hooks.thisCompilation.tap(plugin, compilation => { + const associatedObjectForCache = compiler.root; + const context = this._context || compiler.context; + if (this._read) { + compilation.hooks.reviveModules.tap(plugin, (_1, _2) => { + if (!data) return; + const { chunkGraph } = compilation; + const [usedIds, modules] = getUsedModuleIdsAndModules( + compilation, + this._test + ); + for (const module of modules) { + const name = module.libIdent({ + context, + associatedObjectForCache + }); + if (!name) continue; + const id = data.get(name); + const idAsString = `${id}`; + if (usedIds.has(idAsString)) { + const err = new WebpackError( + `SyncModuleIdsPlugin: Unable to restore id '${id}' from '${this._path}' as it's already used.` + ); + err.module = module; + compilation.errors.push(err); + } + chunkGraph.setModuleId(module, /** @type {string | number} */ (id)); + usedIds.add(idAsString); + } + }); + } + if (this._write) { + compilation.hooks.recordModules.tap(plugin, modules => { + const { chunkGraph } = compilation; + let oldData = data; + if (!oldData) { + oldData = data = new Map(); + } else if (this._prune) { + data = new Map(); + } + for (const module of modules) { + if (this._test(module)) { + const name = module.libIdent({ + context, + associatedObjectForCache + }); + if (!name) continue; + const id = chunkGraph.getModuleId(module); + if (id === null) continue; + const oldId = oldData.get(name); + if (oldId !== id) { + dataChanged = true; + } else if (data === oldData) { + continue; + } + data.set(name, id); + } + } + if (data.size !== oldData.size) dataChanged = true; + }); + } + }); + } +} + +module.exports = SyncModuleIdsPlugin; diff --git a/webpack-lib/lib/index.js b/webpack-lib/lib/index.js new file mode 100644 index 00000000000..1e6b8bfd4c7 --- /dev/null +++ b/webpack-lib/lib/index.js @@ -0,0 +1,641 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const memoize = require("./util/memoize"); + +/** @typedef {import("../declarations/WebpackOptions").Entry} Entry */ +/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} EntryNormalized */ +/** @typedef {import("../declarations/WebpackOptions").EntryObject} EntryObject */ +/** @typedef {import("../declarations/WebpackOptions").ExternalItemFunctionData} ExternalItemFunctionData */ +/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectKnown} ExternalItemObjectKnown */ +/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectUnknown} ExternalItemObjectUnknown */ +/** @typedef {import("../declarations/WebpackOptions").ExternalItemValue} ExternalItemValue */ +/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ +/** @typedef {import("../declarations/WebpackOptions").FileCacheOptions} FileCacheOptions */ +/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../declarations/WebpackOptions").MemoryCacheOptions} MemoryCacheOptions */ +/** @typedef {import("../declarations/WebpackOptions").ModuleOptions} ModuleOptions */ +/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetCondition} RuleSetCondition */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetConditionAbsolute} RuleSetConditionAbsolute */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetUse} RuleSetUse */ +/** @typedef {import("../declarations/WebpackOptions").RuleSetUseItem} RuleSetUseItem */ +/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} Configuration */ +/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ +/** @typedef {import("./ChunkGroup")} ChunkGroup */ +/** @typedef {import("./Compilation").Asset} Asset */ +/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("./Compilation").EntryOptions} EntryOptions */ +/** @typedef {import("./Compilation").PathData} PathData */ +/** @typedef {import("./Compiler").AssetEmittedInfo} AssetEmittedInfo */ +/** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ +/** @typedef {import("./MultiStats")} MultiStats */ +/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */ +/** @typedef {import("./Parser").ParserState} ParserState */ +/** @typedef {import("./ResolverFactory").ResolvePluginInstance} ResolvePluginInstance */ +/** @typedef {import("./ResolverFactory").Resolver} Resolver */ +/** @typedef {import("./Watching")} Watching */ +/** @typedef {import("./cli").Argument} Argument */ +/** @typedef {import("./cli").Problem} Problem */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkOrigin} StatsChunkOrigin */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsError} StatsError */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsLogging} StatsLogging */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsLoggingEntry} StatsLoggingEntry */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModule} StatsModule */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */ +/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */ +/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */ + +/** + * @template {Function} T + * @param {function(): T} factory factory function + * @returns {T} function + */ +const lazyFunction = factory => { + const fac = memoize(factory); + const f = /** @type {any} */ ( + /** + * @param {...any} args args + * @returns {T} result + */ + (...args) => fac()(...args) + ); + return /** @type {T} */ (f); +}; + +/** + * @template A + * @template B + * @param {A} obj input a + * @param {B} exports input b + * @returns {A & B} merged + */ +const mergeExports = (obj, exports) => { + const descriptors = Object.getOwnPropertyDescriptors(exports); + for (const name of Object.keys(descriptors)) { + const descriptor = descriptors[name]; + if (descriptor.get) { + const fn = descriptor.get; + Object.defineProperty(obj, name, { + configurable: false, + enumerable: true, + get: memoize(fn) + }); + } else if (typeof descriptor.value === "object") { + Object.defineProperty(obj, name, { + configurable: false, + enumerable: true, + writable: false, + value: mergeExports({}, descriptor.value) + }); + } else { + throw new Error( + "Exposed values must be either a getter or an nested object" + ); + } + } + return /** @type {A & B} */ (Object.freeze(obj)); +}; + +const fn = lazyFunction(() => require("./webpack")); +module.exports = mergeExports(fn, { + get webpack() { + return require("./webpack"); + }, + /** + * @returns {function(Configuration | Configuration[]): void} validate fn + */ + get validate() { + const webpackOptionsSchemaCheck = require("../schemas/WebpackOptions.check.js"); + const getRealValidate = memoize( + /** + * @returns {function(Configuration | Configuration[]): void} validate fn + */ + () => { + const validateSchema = require("./validateSchema"); + const webpackOptionsSchema = require("../schemas/WebpackOptions.json"); + return options => validateSchema(webpackOptionsSchema, options); + } + ); + return options => { + if (!webpackOptionsSchemaCheck(/** @type {TODO} */ (options))) + getRealValidate()(options); + }; + }, + get validateSchema() { + const validateSchema = require("./validateSchema"); + return validateSchema; + }, + get version() { + return /** @type {string} */ (require("../package.json").version); + }, + + get cli() { + return require("./cli"); + }, + get AutomaticPrefetchPlugin() { + return require("./AutomaticPrefetchPlugin"); + }, + get AsyncDependenciesBlock() { + return require("./AsyncDependenciesBlock"); + }, + get BannerPlugin() { + return require("./BannerPlugin"); + }, + get Cache() { + return require("./Cache"); + }, + get Chunk() { + return require("./Chunk"); + }, + get ChunkGraph() { + return require("./ChunkGraph"); + }, + get CleanPlugin() { + return require("./CleanPlugin"); + }, + get Compilation() { + return require("./Compilation"); + }, + get Compiler() { + return require("./Compiler"); + }, + get ConcatenationScope() { + return require("./ConcatenationScope"); + }, + get ContextExclusionPlugin() { + return require("./ContextExclusionPlugin"); + }, + get ContextReplacementPlugin() { + return require("./ContextReplacementPlugin"); + }, + get DefinePlugin() { + return require("./DefinePlugin"); + }, + get DelegatedPlugin() { + return require("./DelegatedPlugin"); + }, + get Dependency() { + return require("./Dependency"); + }, + get DllPlugin() { + return require("./DllPlugin"); + }, + get DllReferencePlugin() { + return require("./DllReferencePlugin"); + }, + get DynamicEntryPlugin() { + return require("./DynamicEntryPlugin"); + }, + get EntryOptionPlugin() { + return require("./EntryOptionPlugin"); + }, + get EntryPlugin() { + return require("./EntryPlugin"); + }, + get EnvironmentPlugin() { + return require("./EnvironmentPlugin"); + }, + get EvalDevToolModulePlugin() { + return require("./EvalDevToolModulePlugin"); + }, + get EvalSourceMapDevToolPlugin() { + return require("./EvalSourceMapDevToolPlugin"); + }, + get ExternalModule() { + return require("./ExternalModule"); + }, + get ExternalsPlugin() { + return require("./ExternalsPlugin"); + }, + get Generator() { + return require("./Generator"); + }, + get HotUpdateChunk() { + return require("./HotUpdateChunk"); + }, + get HotModuleReplacementPlugin() { + return require("./HotModuleReplacementPlugin"); + }, + get InitFragment() { + return require("./InitFragment"); + }, + get IgnorePlugin() { + return require("./IgnorePlugin"); + }, + get JavascriptModulesPlugin() { + return util.deprecate( + () => require("./javascript/JavascriptModulesPlugin"), + "webpack.JavascriptModulesPlugin has moved to webpack.javascript.JavascriptModulesPlugin", + "DEP_WEBPACK_JAVASCRIPT_MODULES_PLUGIN" + )(); + }, + get LibManifestPlugin() { + return require("./LibManifestPlugin"); + }, + get LibraryTemplatePlugin() { + return util.deprecate( + () => require("./LibraryTemplatePlugin"), + "webpack.LibraryTemplatePlugin is deprecated and has been replaced by compilation.outputOptions.library or compilation.addEntry + passing a library option", + "DEP_WEBPACK_LIBRARY_TEMPLATE_PLUGIN" + )(); + }, + get LoaderOptionsPlugin() { + return require("./LoaderOptionsPlugin"); + }, + get LoaderTargetPlugin() { + return require("./LoaderTargetPlugin"); + }, + get Module() { + return require("./Module"); + }, + get ModuleFilenameHelpers() { + return require("./ModuleFilenameHelpers"); + }, + get ModuleGraph() { + return require("./ModuleGraph"); + }, + get ModuleGraphConnection() { + return require("./ModuleGraphConnection"); + }, + get NoEmitOnErrorsPlugin() { + return require("./NoEmitOnErrorsPlugin"); + }, + get NormalModule() { + return require("./NormalModule"); + }, + get NormalModuleReplacementPlugin() { + return require("./NormalModuleReplacementPlugin"); + }, + get MultiCompiler() { + return require("./MultiCompiler"); + }, + get OptimizationStages() { + return require("./OptimizationStages"); + }, + get Parser() { + return require("./Parser"); + }, + get PlatformPlugin() { + return require("./PlatformPlugin"); + }, + get PrefetchPlugin() { + return require("./PrefetchPlugin"); + }, + get ProgressPlugin() { + return require("./ProgressPlugin"); + }, + get ProvidePlugin() { + return require("./ProvidePlugin"); + }, + get RuntimeGlobals() { + return require("./RuntimeGlobals"); + }, + get RuntimeModule() { + return require("./RuntimeModule"); + }, + get SingleEntryPlugin() { + return util.deprecate( + () => require("./EntryPlugin"), + "SingleEntryPlugin was renamed to EntryPlugin", + "DEP_WEBPACK_SINGLE_ENTRY_PLUGIN" + )(); + }, + get SourceMapDevToolPlugin() { + return require("./SourceMapDevToolPlugin"); + }, + get Stats() { + return require("./Stats"); + }, + get Template() { + return require("./Template"); + }, + get UsageState() { + return require("./ExportsInfo").UsageState; + }, + get WatchIgnorePlugin() { + return require("./WatchIgnorePlugin"); + }, + get WebpackError() { + return require("./WebpackError"); + }, + get WebpackOptionsApply() { + return require("./WebpackOptionsApply"); + }, + get WebpackOptionsDefaulter() { + return util.deprecate( + () => require("./WebpackOptionsDefaulter"), + "webpack.WebpackOptionsDefaulter is deprecated and has been replaced by webpack.config.getNormalizedWebpackOptions and webpack.config.applyWebpackOptionsDefaults", + "DEP_WEBPACK_OPTIONS_DEFAULTER" + )(); + }, + // TODO webpack 6 deprecate + get WebpackOptionsValidationError() { + return require("schema-utils").ValidationError; + }, + get ValidationError() { + return require("schema-utils").ValidationError; + }, + + cache: { + get MemoryCachePlugin() { + return require("./cache/MemoryCachePlugin"); + } + }, + + config: { + get getNormalizedWebpackOptions() { + return require("./config/normalization").getNormalizedWebpackOptions; + }, + get applyWebpackOptionsDefaults() { + return require("./config/defaults").applyWebpackOptionsDefaults; + } + }, + + dependencies: { + get ModuleDependency() { + return require("./dependencies/ModuleDependency"); + }, + get HarmonyImportDependency() { + return require("./dependencies/HarmonyImportDependency"); + }, + get ConstDependency() { + return require("./dependencies/ConstDependency"); + }, + get NullDependency() { + return require("./dependencies/NullDependency"); + } + }, + + ids: { + get ChunkModuleIdRangePlugin() { + return require("./ids/ChunkModuleIdRangePlugin"); + }, + get NaturalModuleIdsPlugin() { + return require("./ids/NaturalModuleIdsPlugin"); + }, + get OccurrenceModuleIdsPlugin() { + return require("./ids/OccurrenceModuleIdsPlugin"); + }, + get NamedModuleIdsPlugin() { + return require("./ids/NamedModuleIdsPlugin"); + }, + get DeterministicChunkIdsPlugin() { + return require("./ids/DeterministicChunkIdsPlugin"); + }, + get DeterministicModuleIdsPlugin() { + return require("./ids/DeterministicModuleIdsPlugin"); + }, + get NamedChunkIdsPlugin() { + return require("./ids/NamedChunkIdsPlugin"); + }, + get OccurrenceChunkIdsPlugin() { + return require("./ids/OccurrenceChunkIdsPlugin"); + }, + get HashedModuleIdsPlugin() { + return require("./ids/HashedModuleIdsPlugin"); + } + }, + + javascript: { + get EnableChunkLoadingPlugin() { + return require("./javascript/EnableChunkLoadingPlugin"); + }, + get JavascriptModulesPlugin() { + return require("./javascript/JavascriptModulesPlugin"); + }, + get JavascriptParser() { + return require("./javascript/JavascriptParser"); + } + }, + + optimize: { + get AggressiveMergingPlugin() { + return require("./optimize/AggressiveMergingPlugin"); + }, + get AggressiveSplittingPlugin() { + return util.deprecate( + () => require("./optimize/AggressiveSplittingPlugin"), + "AggressiveSplittingPlugin is deprecated in favor of SplitChunksPlugin", + "DEP_WEBPACK_AGGRESSIVE_SPLITTING_PLUGIN" + )(); + }, + get InnerGraph() { + return require("./optimize/InnerGraph"); + }, + get LimitChunkCountPlugin() { + return require("./optimize/LimitChunkCountPlugin"); + }, + get MergeDuplicateChunksPlugin() { + return require("./optimize/MergeDuplicateChunksPlugin.js"); + }, + get MinChunkSizePlugin() { + return require("./optimize/MinChunkSizePlugin"); + }, + get ModuleConcatenationPlugin() { + return require("./optimize/ModuleConcatenationPlugin"); + }, + get RealContentHashPlugin() { + return require("./optimize/RealContentHashPlugin"); + }, + get RuntimeChunkPlugin() { + return require("./optimize/RuntimeChunkPlugin"); + }, + get SideEffectsFlagPlugin() { + return require("./optimize/SideEffectsFlagPlugin"); + }, + get SplitChunksPlugin() { + return require("./optimize/SplitChunksPlugin"); + } + }, + + runtime: { + get GetChunkFilenameRuntimeModule() { + return require("./runtime/GetChunkFilenameRuntimeModule"); + }, + get LoadScriptRuntimeModule() { + return require("./runtime/LoadScriptRuntimeModule"); + } + }, + + prefetch: { + get ChunkPrefetchPreloadPlugin() { + return require("./prefetch/ChunkPrefetchPreloadPlugin"); + } + }, + + web: { + get FetchCompileWasmPlugin() { + return require("./web/FetchCompileWasmPlugin"); + }, + get FetchCompileAsyncWasmPlugin() { + return require("./web/FetchCompileAsyncWasmPlugin"); + }, + get JsonpChunkLoadingRuntimeModule() { + return require("./web/JsonpChunkLoadingRuntimeModule"); + }, + get JsonpTemplatePlugin() { + return require("./web/JsonpTemplatePlugin"); + }, + get CssLoadingRuntimeModule() { + return require("./css/CssLoadingRuntimeModule"); + } + }, + + esm: { + get ModuleChunkLoadingRuntimeModule() { + return require("./esm/ModuleChunkLoadingRuntimeModule"); + } + }, + + webworker: { + get WebWorkerTemplatePlugin() { + return require("./webworker/WebWorkerTemplatePlugin"); + } + }, + + node: { + get NodeEnvironmentPlugin() { + return require("./node/NodeEnvironmentPlugin"); + }, + get NodeSourcePlugin() { + return require("./node/NodeSourcePlugin"); + }, + get NodeTargetPlugin() { + return require("./node/NodeTargetPlugin"); + }, + get NodeTemplatePlugin() { + return require("./node/NodeTemplatePlugin"); + }, + get ReadFileCompileWasmPlugin() { + return require("./node/ReadFileCompileWasmPlugin"); + }, + get ReadFileCompileAsyncWasmPlugin() { + return require("./node/ReadFileCompileAsyncWasmPlugin"); + } + }, + + electron: { + get ElectronTargetPlugin() { + return require("./electron/ElectronTargetPlugin"); + } + }, + + wasm: { + get AsyncWebAssemblyModulesPlugin() { + return require("./wasm-async/AsyncWebAssemblyModulesPlugin"); + }, + get EnableWasmLoadingPlugin() { + return require("./wasm/EnableWasmLoadingPlugin"); + } + }, + + css: { + get CssModulesPlugin() { + return require("./css/CssModulesPlugin"); + } + }, + + library: { + get AbstractLibraryPlugin() { + return require("./library/AbstractLibraryPlugin"); + }, + get EnableLibraryPlugin() { + return require("./library/EnableLibraryPlugin"); + } + }, + + container: { + get ContainerPlugin() { + return require("./container/ContainerPlugin"); + }, + get ContainerReferencePlugin() { + return require("./container/ContainerReferencePlugin"); + }, + get ModuleFederationPlugin() { + return require("./container/ModuleFederationPlugin"); + }, + get scope() { + return require("./container/options").scope; + } + }, + + sharing: { + get ConsumeSharedPlugin() { + return require("./sharing/ConsumeSharedPlugin"); + }, + get ProvideSharedPlugin() { + return require("./sharing/ProvideSharedPlugin"); + }, + get SharePlugin() { + return require("./sharing/SharePlugin"); + }, + get scope() { + return require("./container/options").scope; + } + }, + + debug: { + get ProfilingPlugin() { + return require("./debug/ProfilingPlugin"); + } + }, + + util: { + get createHash() { + return require("./util/createHash"); + }, + get comparators() { + return require("./util/comparators"); + }, + get runtime() { + return require("./util/runtime"); + }, + get serialization() { + return require("./util/serialization"); + }, + get cleverMerge() { + return require("./util/cleverMerge").cachedCleverMerge; + }, + get LazySet() { + return require("./util/LazySet"); + }, + get compileBooleanMatcher() { + return require("./util/compileBooleanMatcher"); + } + }, + + get sources() { + return require("webpack-sources"); + }, + + experiments: { + schemes: { + get HttpUriPlugin() { + return require("./schemes/HttpUriPlugin"); + } + }, + ids: { + get SyncModuleIdsPlugin() { + return require("./ids/SyncModuleIdsPlugin"); + } + } + } +}); diff --git a/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js b/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js new file mode 100644 index 00000000000..1bb04abaffb --- /dev/null +++ b/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js @@ -0,0 +1,156 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource, PrefixSource, RawSource } = require("webpack-sources"); +const { RuntimeGlobals } = require(".."); +const HotUpdateChunk = require("../HotUpdateChunk"); +const Template = require("../Template"); +const { getCompilationHooks } = require("./JavascriptModulesPlugin"); +const { + generateEntryStartup, + updateHashForEntryStartup +} = require("./StartupHelpers"); + +/** @typedef {import("../Compiler")} Compiler */ + +class ArrayPushCallbackChunkFormatPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "ArrayPushCallbackChunkFormatPlugin", + compilation => { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + "ArrayPushCallbackChunkFormatPlugin", + (chunk, set, { chunkGraph }) => { + if (chunk.hasRuntime()) return; + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { + set.add(RuntimeGlobals.onChunksLoaded); + set.add(RuntimeGlobals.exports); + set.add(RuntimeGlobals.require); + } + set.add(RuntimeGlobals.chunkCallback); + } + ); + const hooks = getCompilationHooks(compilation); + hooks.renderChunk.tap( + "ArrayPushCallbackChunkFormatPlugin", + (modules, renderContext) => { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + const hotUpdateChunk = + chunk instanceof HotUpdateChunk ? chunk : null; + const globalObject = runtimeTemplate.globalObject; + const source = new ConcatSource(); + const runtimeModules = + chunkGraph.getChunkRuntimeModulesInOrder(chunk); + if (hotUpdateChunk) { + const hotUpdateGlobal = + runtimeTemplate.outputOptions.hotUpdateGlobal; + source.add( + `${globalObject}[${JSON.stringify(hotUpdateGlobal)}](` + ); + source.add(`${JSON.stringify(chunk.id)},`); + source.add(modules); + if (runtimeModules.length > 0) { + source.add(",\n"); + const runtimePart = Template.renderChunkRuntimeModules( + runtimeModules, + renderContext + ); + source.add(runtimePart); + } + source.add(")"); + } else { + const chunkLoadingGlobal = + runtimeTemplate.outputOptions.chunkLoadingGlobal; + source.add( + `(${globalObject}[${JSON.stringify( + chunkLoadingGlobal + )}] = ${globalObject}[${JSON.stringify( + chunkLoadingGlobal + )}] || []).push([` + ); + source.add(`${JSON.stringify(chunk.ids)},`); + source.add(modules); + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + if (runtimeModules.length > 0 || entries.length > 0) { + const runtime = new ConcatSource( + `${ + runtimeTemplate.supportsArrowFunction() + ? `${RuntimeGlobals.require} =>` + : `function(${RuntimeGlobals.require})` + } { // webpackRuntimeModules\n` + ); + if (runtimeModules.length > 0) { + runtime.add( + Template.renderRuntimeModules(runtimeModules, { + ...renderContext, + codeGenerationResults: compilation.codeGenerationResults + }) + ); + } + if (entries.length > 0) { + const startupSource = new RawSource( + generateEntryStartup( + chunkGraph, + runtimeTemplate, + entries, + chunk, + true + ) + ); + runtime.add( + hooks.renderStartup.call( + startupSource, + entries[entries.length - 1][0], + { + ...renderContext, + inlined: false + } + ) + ); + if ( + chunkGraph + .getChunkRuntimeRequirements(chunk) + .has(RuntimeGlobals.returnExportsFromRuntime) + ) { + runtime.add(`return ${RuntimeGlobals.exports};\n`); + } + } + runtime.add("}\n"); + source.add(",\n"); + source.add(new PrefixSource("/******/ ", runtime)); + } + source.add("])"); + } + return source; + } + ); + hooks.chunkHash.tap( + "ArrayPushCallbackChunkFormatPlugin", + (chunk, hash, { chunkGraph, runtimeTemplate }) => { + if (chunk.hasRuntime()) return; + hash.update( + `ArrayPushCallbackChunkFormatPlugin1${runtimeTemplate.outputOptions.chunkLoadingGlobal}${runtimeTemplate.outputOptions.hotUpdateGlobal}${runtimeTemplate.globalObject}` + ); + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + updateHashForEntryStartup(hash, chunkGraph, entries, chunk); + } + ); + } + ); + } +} + +module.exports = ArrayPushCallbackChunkFormatPlugin; diff --git a/webpack-lib/lib/javascript/BasicEvaluatedExpression.js b/webpack-lib/lib/javascript/BasicEvaluatedExpression.js new file mode 100644 index 00000000000..05dd14cd194 --- /dev/null +++ b/webpack-lib/lib/javascript/BasicEvaluatedExpression.js @@ -0,0 +1,594 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("./JavascriptParser").Range} Range */ +/** @typedef {import("./JavascriptParser").VariableInfoInterface} VariableInfoInterface */ + +const TypeUnknown = 0; +const TypeUndefined = 1; +const TypeNull = 2; +const TypeString = 3; +const TypeNumber = 4; +const TypeBoolean = 5; +const TypeRegExp = 6; +const TypeConditional = 7; +const TypeArray = 8; +const TypeConstArray = 9; +const TypeIdentifier = 10; +const TypeWrapped = 11; +const TypeTemplateString = 12; +const TypeBigInt = 13; + +class BasicEvaluatedExpression { + constructor() { + this.type = TypeUnknown; + /** @type {Range | undefined} */ + this.range = undefined; + /** @type {boolean} */ + this.falsy = false; + /** @type {boolean} */ + this.truthy = false; + /** @type {boolean | undefined} */ + this.nullish = undefined; + /** @type {boolean} */ + this.sideEffects = true; + /** @type {boolean | undefined} */ + this.bool = undefined; + /** @type {number | undefined} */ + this.number = undefined; + /** @type {bigint | undefined} */ + this.bigint = undefined; + /** @type {RegExp | undefined} */ + this.regExp = undefined; + /** @type {string | undefined} */ + this.string = undefined; + /** @type {BasicEvaluatedExpression[] | undefined} */ + this.quasis = undefined; + /** @type {BasicEvaluatedExpression[] | undefined} */ + this.parts = undefined; + /** @type {any[] | undefined} */ + this.array = undefined; + /** @type {BasicEvaluatedExpression[] | undefined} */ + this.items = undefined; + /** @type {BasicEvaluatedExpression[] | undefined} */ + this.options = undefined; + /** @type {BasicEvaluatedExpression | undefined | null} */ + this.prefix = undefined; + /** @type {BasicEvaluatedExpression | undefined | null} */ + this.postfix = undefined; + /** @type {BasicEvaluatedExpression[] | undefined} */ + this.wrappedInnerExpressions = undefined; + /** @type {string | VariableInfoInterface | undefined} */ + this.identifier = undefined; + /** @type {string | VariableInfoInterface | undefined} */ + this.rootInfo = undefined; + /** @type {(() => string[]) | undefined} */ + this.getMembers = undefined; + /** @type {(() => boolean[]) | undefined} */ + this.getMembersOptionals = undefined; + /** @type {(() => Range[]) | undefined} */ + this.getMemberRanges = undefined; + /** @type {Node | undefined} */ + this.expression = undefined; + } + + isUnknown() { + return this.type === TypeUnknown; + } + + isNull() { + return this.type === TypeNull; + } + + isUndefined() { + return this.type === TypeUndefined; + } + + isString() { + return this.type === TypeString; + } + + isNumber() { + return this.type === TypeNumber; + } + + isBigInt() { + return this.type === TypeBigInt; + } + + 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; + } + + /** + * Is expression a primitive or an object type value? + * @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined + */ + isPrimitiveType() { + switch (this.type) { + case TypeUndefined: + case TypeNull: + case TypeString: + case TypeNumber: + case TypeBoolean: + case TypeBigInt: + case TypeWrapped: + case TypeTemplateString: + return true; + case TypeRegExp: + case TypeArray: + case TypeConstArray: + return false; + default: + return undefined; + } + } + + /** + * Is expression a runtime or compile-time value? + * @returns {boolean} true: compile time value, false: runtime value + */ + isCompileTimeValue() { + switch (this.type) { + case TypeUndefined: + case TypeNull: + case TypeString: + case TypeNumber: + case TypeBoolean: + case TypeRegExp: + case TypeConstArray: + case TypeBigInt: + return true; + default: + return false; + } + } + + /** + * Gets the compile-time value of the expression + * @returns {any} the javascript value + */ + asCompileTimeValue() { + switch (this.type) { + case TypeUndefined: + return; + case TypeNull: + return null; + case TypeString: + return this.string; + case TypeNumber: + return this.number; + case TypeBoolean: + return this.bool; + case TypeRegExp: + return this.regExp; + case TypeConstArray: + return this.array; + case TypeBigInt: + return this.bigint; + default: + throw new Error( + "asCompileTimeValue must only be called for compile-time values" + ); + } + } + + isTruthy() { + return this.truthy; + } + + isFalsy() { + return this.falsy; + } + + isNullish() { + return this.nullish; + } + + /** + * Can this expression have side effects? + * @returns {boolean} false: never has side effects + */ + couldHaveSideEffects() { + return this.sideEffects; + } + + /** + * Creates a boolean representation of this evaluated expression. + * @returns {boolean | undefined} true: truthy, false: falsy, undefined: unknown + */ + asBool() { + if (this.truthy) return true; + if (this.falsy || this.nullish) return false; + if (this.isBoolean()) return this.bool; + if (this.isNull()) return false; + if (this.isUndefined()) return false; + if (this.isString()) return this.string !== ""; + if (this.isNumber()) return this.number !== 0; + if (this.isBigInt()) return this.bigint !== BigInt(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()) { + const str = this.asString(); + if (typeof str === "string") return str !== ""; + } + } + + /** + * Creates a nullish coalescing representation of this evaluated expression. + * @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown + */ + asNullish() { + const nullish = this.isNullish(); + + if (nullish === true || this.isNull() || this.isUndefined()) return true; + + if (nullish === false) return false; + if (this.isTruthy()) return false; + if (this.isBoolean()) return false; + if (this.isString()) return false; + if (this.isNumber()) return false; + if (this.isBigInt()) return false; + if (this.isRegExp()) return false; + if (this.isArray()) return false; + if (this.isConstArray()) return false; + if (this.isTemplateString()) return false; + if (this.isRegExp()) return false; + } + + /** + * Creates a string representation of this evaluated expression. + * @returns {string | undefined} the string representation or undefined if not possible + */ + asString() { + if (this.isBoolean()) return `${this.bool}`; + if (this.isNull()) return "null"; + if (this.isUndefined()) return "undefined"; + if (this.isString()) return this.string; + if (this.isNumber()) return `${this.number}`; + if (this.isBigInt()) return `${this.bigint}`; + if (this.isRegExp()) return `${this.regExp}`; + if (this.isArray()) { + const array = []; + for (const item of /** @type {BasicEvaluatedExpression[]} */ ( + this.items + )) { + const itemStr = item.asString(); + if (itemStr === undefined) return; + array.push(itemStr); + } + return `${array}`; + } + if (this.isConstArray()) return `${this.array}`; + if (this.isTemplateString()) { + let str = ""; + for (const part of /** @type {BasicEvaluatedExpression[]} */ ( + this.parts + )) { + const partStr = part.asString(); + if (partStr === undefined) return; + str += partStr; + } + return str; + } + } + + /** + * @param {string} string value + * @returns {BasicEvaluatedExpression} basic evaluated expression + */ + setString(string) { + this.type = TypeString; + this.string = string; + this.sideEffects = false; + return this; + } + + setUndefined() { + this.type = TypeUndefined; + this.sideEffects = false; + return this; + } + + setNull() { + this.type = TypeNull; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a number + * @param {number} number number to set + * @returns {this} this + */ + setNumber(number) { + this.type = TypeNumber; + this.number = number; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a BigInt + * @param {bigint} bigint bigint to set + * @returns {this} this + */ + setBigInt(bigint) { + this.type = TypeBigInt; + this.bigint = bigint; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a boolean + * @param {boolean} bool boolean to set + * @returns {this} this + */ + setBoolean(bool) { + this.type = TypeBoolean; + this.bool = bool; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a regular expression + * @param {RegExp} regExp regular expression to set + * @returns {this} this + */ + setRegExp(regExp) { + this.type = TypeRegExp; + this.regExp = regExp; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a particular identifier and its members. + * @param {string | VariableInfoInterface} identifier identifier to set + * @param {string | VariableInfoInterface} rootInfo root info + * @param {() => string[]} getMembers members + * @param {() => boolean[]=} getMembersOptionals optional members + * @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions + * @returns {this} this + */ + setIdentifier( + identifier, + rootInfo, + getMembers, + getMembersOptionals, + getMemberRanges + ) { + this.type = TypeIdentifier; + this.identifier = identifier; + this.rootInfo = rootInfo; + this.getMembers = getMembers; + this.getMembersOptionals = getMembersOptionals; + this.getMemberRanges = getMemberRanges; + this.sideEffects = true; + return this; + } + + /** + * Wraps an array of expressions with a prefix and postfix expression. + * @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions + * @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions + * @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped + * @returns {this} this + */ + setWrapped(prefix, postfix, innerExpressions) { + this.type = TypeWrapped; + this.prefix = prefix; + this.postfix = postfix; + this.wrappedInnerExpressions = innerExpressions; + this.sideEffects = true; + return this; + } + + /** + * Stores the options of a conditional expression. + * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set + * @returns {this} this + */ + setOptions(options) { + this.type = TypeConditional; + this.options = options; + this.sideEffects = true; + return this; + } + + /** + * Adds options to a conditional expression. + * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added + * @returns {this} this + */ + addOptions(options) { + if (!this.options) { + this.type = TypeConditional; + this.options = []; + this.sideEffects = true; + } + for (const item of options) { + this.options.push(item); + } + return this; + } + + /** + * Set's the value of this expression to an array of expressions. + * @param {BasicEvaluatedExpression[]} items expressions to set + * @returns {this} this + */ + setItems(items) { + this.type = TypeArray; + this.items = items; + this.sideEffects = items.some(i => i.couldHaveSideEffects()); + return this; + } + + /** + * Set's the value of this expression to an array of strings. + * @param {string[]} array array to set + * @returns {this} this + */ + setArray(array) { + this.type = TypeConstArray; + this.array = array; + this.sideEffects = false; + return this; + } + + /** + * Set's the value of this expression to a processed/unprocessed template string. Used + * for evaluating TemplateLiteral expressions in the JavaScript Parser. + * @param {BasicEvaluatedExpression[]} quasis template string quasis + * @param {BasicEvaluatedExpression[]} parts template string parts + * @param {"cooked" | "raw"} kind template string kind + * @returns {this} this + */ + setTemplateString(quasis, parts, kind) { + this.type = TypeTemplateString; + this.quasis = quasis; + this.parts = parts; + this.templateStringKind = kind; + this.sideEffects = parts.some(p => p.sideEffects); + return this; + } + + setTruthy() { + this.falsy = false; + this.truthy = true; + this.nullish = false; + return this; + } + + setFalsy() { + this.falsy = true; + this.truthy = false; + return this; + } + + /** + * Set's the value of the expression to nullish. + * @param {boolean} value true, if the expression is nullish + * @returns {this} this + */ + setNullish(value) { + this.nullish = value; + + if (value) return this.setFalsy(); + + return this; + } + + /** + * Set's the range for the expression. + * @param {[number, number]} range range to set + * @returns {this} this + */ + setRange(range) { + this.range = range; + return this; + } + + /** + * Set whether or not the expression has side effects. + * @param {boolean} sideEffects true, if the expression has side effects + * @returns {this} this + */ + setSideEffects(sideEffects = true) { + this.sideEffects = sideEffects; + return this; + } + + /** + * Set the expression node for the expression. + * @param {Node | undefined} expression expression + * @returns {this} this + */ + setExpression(expression) { + this.expression = expression; + return this; + } +} + +/** + * @param {string} flags regexp flags + * @returns {boolean} is valid flags + */ +BasicEvaluatedExpression.isValidRegExpFlags = flags => { + const len = flags.length; + + if (len === 0) return true; + if (len > 4) return false; + + // cspell:word gimy + let remaining = 0b0000; // bit per RegExp flag: gimy + + for (let i = 0; i < len; i++) + switch (flags.charCodeAt(i)) { + case 103 /* g */: + if (remaining & 0b1000) return false; + remaining |= 0b1000; + break; + case 105 /* i */: + if (remaining & 0b0100) return false; + remaining |= 0b0100; + break; + case 109 /* m */: + if (remaining & 0b0010) return false; + remaining |= 0b0010; + break; + case 121 /* y */: + if (remaining & 0b0001) return false; + remaining |= 0b0001; + break; + default: + return false; + } + + return true; +}; + +module.exports = BasicEvaluatedExpression; diff --git a/webpack-lib/lib/javascript/ChunkHelpers.js b/webpack-lib/lib/javascript/ChunkHelpers.js new file mode 100644 index 00000000000..f2e8a12a996 --- /dev/null +++ b/webpack-lib/lib/javascript/ChunkHelpers.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Entrypoint = require("../Entrypoint"); + +/** @typedef {import("../Chunk")} Chunk */ + +/** + * @param {Entrypoint} entrypoint a chunk group + * @param {(Chunk | null)=} excludedChunk1 current chunk which is excluded + * @param {(Chunk | null)=} excludedChunk2 runtime chunk which is excluded + * @returns {Set} chunks + */ +const getAllChunks = (entrypoint, excludedChunk1, excludedChunk2) => { + const queue = new Set([entrypoint]); + const chunks = new Set(); + for (const entrypoint of queue) { + for (const chunk of entrypoint.chunks) { + if (chunk === excludedChunk1) continue; + if (chunk === excludedChunk2) continue; + chunks.add(chunk); + } + for (const parent of entrypoint.parentsIterable) { + if (parent instanceof Entrypoint) queue.add(parent); + } + } + return chunks; +}; +module.exports.getAllChunks = getAllChunks; diff --git a/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js b/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js new file mode 100644 index 00000000000..75384ab9a50 --- /dev/null +++ b/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js @@ -0,0 +1,175 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource, RawSource } = require("webpack-sources"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const { getUndoPath } = require("../util/identifier"); +const { + getChunkFilenameTemplate, + getCompilationHooks +} = require("./JavascriptModulesPlugin"); +const { + generateEntryStartup, + updateHashForEntryStartup +} = require("./StartupHelpers"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Entrypoint")} Entrypoint */ + +class CommonJsChunkFormatPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "CommonJsChunkFormatPlugin", + compilation => { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + "CommonJsChunkFormatPlugin", + (chunk, set, { chunkGraph }) => { + if (chunk.hasRuntime()) return; + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { + set.add(RuntimeGlobals.require); + set.add(RuntimeGlobals.startupEntrypoint); + set.add(RuntimeGlobals.externalInstallChunk); + } + } + ); + const hooks = getCompilationHooks(compilation); + hooks.renderChunk.tap( + "CommonJsChunkFormatPlugin", + (modules, renderContext) => { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + const source = new ConcatSource(); + source.add(`exports.id = ${JSON.stringify(chunk.id)};\n`); + source.add(`exports.ids = ${JSON.stringify(chunk.ids)};\n`); + source.add("exports.modules = "); + source.add(modules); + source.add(";\n"); + const runtimeModules = + chunkGraph.getChunkRuntimeModulesInOrder(chunk); + if (runtimeModules.length > 0) { + source.add("exports.runtime =\n"); + source.add( + Template.renderChunkRuntimeModules( + runtimeModules, + renderContext + ) + ); + } + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + if (entries.length > 0) { + const runtimeChunk = + /** @type {Entrypoint} */ + (entries[0][1]).getRuntimeChunk(); + const currentOutputName = compilation + .getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ) + .replace(/^\/+/g, "") + .split("/"); + const runtimeOutputName = compilation + .getPath( + getChunkFilenameTemplate( + /** @type {Chunk} */ + (runtimeChunk), + compilation.outputOptions + ), + { + chunk: /** @type {Chunk} */ (runtimeChunk), + contentHashType: "javascript" + } + ) + .replace(/^\/+/g, "") + .split("/"); + + // remove common parts + while ( + currentOutputName.length > 1 && + runtimeOutputName.length > 1 && + currentOutputName[0] === runtimeOutputName[0] + ) { + currentOutputName.shift(); + runtimeOutputName.shift(); + } + const last = runtimeOutputName.join("/"); + // create final path + const runtimePath = + getUndoPath(currentOutputName.join("/"), last, true) + last; + + const entrySource = new ConcatSource(); + entrySource.add( + `(${ + runtimeTemplate.supportsArrowFunction() + ? "() => " + : "function() " + }{\n` + ); + entrySource.add("var exports = {};\n"); + entrySource.add(source); + entrySource.add(";\n\n// load runtime\n"); + entrySource.add( + `var ${RuntimeGlobals.require} = require(${JSON.stringify( + runtimePath + )});\n` + ); + entrySource.add( + `${RuntimeGlobals.externalInstallChunk}(exports);\n` + ); + const startupSource = new RawSource( + generateEntryStartup( + chunkGraph, + runtimeTemplate, + entries, + chunk, + false + ) + ); + entrySource.add( + hooks.renderStartup.call( + startupSource, + entries[entries.length - 1][0], + { + ...renderContext, + inlined: false + } + ) + ); + entrySource.add("\n})()"); + return entrySource; + } + return source; + } + ); + hooks.chunkHash.tap( + "CommonJsChunkFormatPlugin", + (chunk, hash, { chunkGraph }) => { + if (chunk.hasRuntime()) return; + hash.update("CommonJsChunkFormatPlugin"); + hash.update("1"); + const entries = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) + ); + updateHashForEntryStartup(hash, chunkGraph, entries, chunk); + } + ); + } + ); + } +} + +module.exports = CommonJsChunkFormatPlugin; diff --git a/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js b/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js new file mode 100644 index 00000000000..4e7263a5309 --- /dev/null +++ b/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js @@ -0,0 +1,121 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").ChunkLoadingType} ChunkLoadingType */ +/** @typedef {import("../Compiler")} Compiler */ + +/** @type {WeakMap>} */ +const enabledTypes = new WeakMap(); + +/** + * @param {Compiler} compiler compiler + * @returns {Set} enabled types + */ +const getEnabledTypes = compiler => { + let set = enabledTypes.get(compiler); + if (set === undefined) { + set = new Set(); + enabledTypes.set(compiler, set); + } + return set; +}; + +class EnableChunkLoadingPlugin { + /** + * @param {ChunkLoadingType} type library type that should be available + */ + constructor(type) { + this.type = type; + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {ChunkLoadingType} type type of library + * @returns {void} + */ + static setEnabled(compiler, type) { + getEnabledTypes(compiler).add(type); + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {ChunkLoadingType} type type of library + * @returns {void} + */ + static checkEnabled(compiler, type) { + if (!getEnabledTypes(compiler).has(type)) { + throw new Error( + `Chunk loading type "${type}" is not enabled. ` + + "EnableChunkLoadingPlugin need to be used to enable this type of chunk loading. " + + 'This usually happens through the "output.enabledChunkLoadingTypes" option. ' + + 'If you are using a function as entry which sets "chunkLoading", you need to add all potential chunk loading types to "output.enabledChunkLoadingTypes". ' + + `These types are enabled: ${Array.from( + getEnabledTypes(compiler) + ).join(", ")}` + ); + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { type } = this; + + // Only enable once + const enabled = getEnabledTypes(compiler); + if (enabled.has(type)) return; + enabled.add(type); + + if (typeof type === "string") { + switch (type) { + case "jsonp": { + const JsonpChunkLoadingPlugin = require("../web/JsonpChunkLoadingPlugin"); + new JsonpChunkLoadingPlugin().apply(compiler); + break; + } + case "import-scripts": { + const ImportScriptsChunkLoadingPlugin = require("../webworker/ImportScriptsChunkLoadingPlugin"); + new ImportScriptsChunkLoadingPlugin().apply(compiler); + break; + } + case "require": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin"); + new CommonJsChunkLoadingPlugin({ + asyncChunkLoading: false + }).apply(compiler); + break; + } + case "async-node": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin"); + new CommonJsChunkLoadingPlugin({ + asyncChunkLoading: true + }).apply(compiler); + break; + } + case "import": + case "universal": { + const ModuleChunkLoadingPlugin = require("../esm/ModuleChunkLoadingPlugin"); + new ModuleChunkLoadingPlugin().apply(compiler); + break; + } + default: + throw new Error(`Unsupported chunk loading type ${type}. +Plugins which provide custom chunk loading types must call EnableChunkLoadingPlugin.setEnabled(compiler, type) to disable this error.`); + } + } else { + // TODO support plugin instances here + // apply them to the compiler + } + } +} + +module.exports = EnableChunkLoadingPlugin; diff --git a/webpack-lib/lib/javascript/JavascriptGenerator.js b/webpack-lib/lib/javascript/JavascriptGenerator.js new file mode 100644 index 00000000000..6bba9756b39 --- /dev/null +++ b/webpack-lib/lib/javascript/JavascriptGenerator.js @@ -0,0 +1,255 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const { RawSource, ReplaceSource } = require("webpack-sources"); +const Generator = require("../Generator"); +const InitFragment = require("../InitFragment"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../DependenciesBlock")} DependenciesBlock */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplate")} DependencyTemplate */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +// TODO: clean up this file +// replace with newer constructs + +const deprecatedGetInitFragments = util.deprecate( + /** + * @param {DependencyTemplate} template template + * @param {Dependency} dependency dependency + * @param {DependencyTemplateContext} templateContext template context + * @returns {InitFragment[]} init fragments + */ + (template, dependency, templateContext) => + /** @type {DependencyTemplate & { getInitFragments: function(Dependency, DependencyTemplateContext): InitFragment[] }} */ + (template).getInitFragments(dependency, templateContext), + "DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)", + "DEP_WEBPACK_JAVASCRIPT_GENERATOR_GET_INIT_FRAGMENTS" +); + +class JavascriptGenerator extends Generator { + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return JS_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + const originalSource = module.originalSource(); + if (!originalSource) { + return 39; + } + return originalSource.size(); + } + + /** + * @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) { + // Only harmony modules are valid for optimization + if ( + !module.buildMeta || + module.buildMeta.exportsType !== "namespace" || + module.presentationalDependencies === undefined || + !module.presentationalDependencies.some( + d => d instanceof HarmonyCompatibilityDependency + ) + ) { + return "Module is not an ECMAScript module"; + } + + // Some expressions are not compatible with module concatenation + // because they may produce unexpected results. The plugin bails out + // if some were detected upfront. + if (module.buildInfo && module.buildInfo.moduleConcatenationBailout) { + return `Module uses ${module.buildInfo.moduleConcatenationBailout}`; + } + } + + /** + * @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 originalSource = module.originalSource(); + if (!originalSource) { + return new RawSource("throw new Error('No source available');"); + } + + const source = new ReplaceSource(originalSource); + /** @type {InitFragment[]} */ + const initFragments = []; + + this.sourceModule(module, initFragments, source, generateContext); + + return InitFragment.addToSource(source, initFragments, generateContext); + } + + /** + * @param {Module} module the module to generate + * @param {InitFragment[]} initFragments mutable list of init fragments + * @param {ReplaceSource} source the current replace source which can be modified + * @param {GenerateContext} generateContext the generateContext + * @returns {void} + */ + sourceModule(module, initFragments, source, generateContext) { + for (const dependency of module.dependencies) { + this.sourceDependency( + module, + dependency, + initFragments, + source, + generateContext + ); + } + + if (module.presentationalDependencies !== undefined) { + for (const dependency of module.presentationalDependencies) { + this.sourceDependency( + module, + dependency, + initFragments, + source, + generateContext + ); + } + } + + for (const childBlock of module.blocks) { + this.sourceBlock( + module, + childBlock, + initFragments, + source, + generateContext + ); + } + } + + /** + * @param {Module} module the module to generate + * @param {DependenciesBlock} block the dependencies block which will be processed + * @param {InitFragment[]} initFragments mutable list of init fragments + * @param {ReplaceSource} source the current replace source which can be modified + * @param {GenerateContext} generateContext the generateContext + * @returns {void} + */ + sourceBlock(module, block, initFragments, source, generateContext) { + for (const dependency of block.dependencies) { + this.sourceDependency( + module, + dependency, + initFragments, + source, + generateContext + ); + } + + for (const childBlock of block.blocks) { + this.sourceBlock( + module, + childBlock, + initFragments, + source, + generateContext + ); + } + } + + /** + * @param {Module} module the current module + * @param {Dependency} dependency the dependency to generate + * @param {InitFragment[]} initFragments mutable list of init fragments + * @param {ReplaceSource} source the current replace source which can be modified + * @param {GenerateContext} generateContext the render context + * @returns {void} + */ + sourceDependency(module, dependency, initFragments, source, generateContext) { + const constructor = + /** @type {new (...args: EXPECTED_ANY[]) => Dependency} */ + (dependency.constructor); + const template = generateContext.dependencyTemplates.get(constructor); + if (!template) { + throw new Error( + `No template for dependency: ${dependency.constructor.name}` + ); + } + + /** @type {InitFragment[] | undefined} */ + let chunkInitFragments; + + /** @type {DependencyTemplateContext} */ + const templateContext = { + runtimeTemplate: generateContext.runtimeTemplate, + dependencyTemplates: generateContext.dependencyTemplates, + moduleGraph: generateContext.moduleGraph, + chunkGraph: generateContext.chunkGraph, + module, + runtime: generateContext.runtime, + runtimeRequirements: generateContext.runtimeRequirements, + concatenationScope: generateContext.concatenationScope, + codeGenerationResults: + /** @type {NonNullable} */ + (generateContext.codeGenerationResults), + initFragments, + get chunkInitFragments() { + if (!chunkInitFragments) { + const data = + /** @type {NonNullable} */ + (generateContext.getData)(); + chunkInitFragments = data.get("chunkInitFragments"); + if (!chunkInitFragments) { + chunkInitFragments = []; + data.set("chunkInitFragments", chunkInitFragments); + } + } + + return chunkInitFragments; + } + }; + + template.apply(dependency, source, templateContext); + + // TODO remove in webpack 6 + if ("getInitFragments" in template) { + const fragments = deprecatedGetInitFragments( + template, + dependency, + templateContext + ); + + if (fragments) { + for (const fragment of fragments) { + initFragments.push(fragment); + } + } + } + } +} + +module.exports = JavascriptGenerator; diff --git a/webpack-lib/lib/javascript/JavascriptModulesPlugin.js b/webpack-lib/lib/javascript/JavascriptModulesPlugin.js new file mode 100644 index 00000000000..6b4046c4e5b --- /dev/null +++ b/webpack-lib/lib/javascript/JavascriptModulesPlugin.js @@ -0,0 +1,1651 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const eslintScope = require("eslint-scope"); +const { SyncWaterfallHook, SyncHook, SyncBailHook } = require("tapable"); +const vm = require("vm"); +const { + ConcatSource, + OriginalSource, + PrefixSource, + RawSource, + CachedSource, + ReplaceSource +} = require("webpack-sources"); +const Compilation = require("../Compilation"); +const { tryRunOrWebpackError } = require("../HookWebpackError"); +const HotUpdateChunk = require("../HotUpdateChunk"); +const InitFragment = require("../InitFragment"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_DYNAMIC, + JAVASCRIPT_MODULE_TYPE_ESM, + WEBPACK_MODULE_TYPE_RUNTIME +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const { last, someInIterable } = require("../util/IterableHelpers"); +const StringXor = require("../util/StringXor"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const { + getPathInAst, + getAllReferences, + RESERVED_NAMES, + findNewName, + addScopeSymbols, + getUsedNamesInScopeInfo +} = require("../util/concatenate"); +const createHash = require("../util/createHash"); +const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); +const { intersectRuntime } = require("../util/runtime"); +const JavascriptGenerator = require("./JavascriptGenerator"); +const JavascriptParser = require("./JavascriptParser"); + +/** @typedef {import("eslint-scope").Reference} Reference */ +/** @typedef {import("eslint-scope").Scope} Scope */ +/** @typedef {import("eslint-scope").Variable} Variable */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compilation").ModuleObject} ModuleObject */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Entrypoint")} Entrypoint */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../util/createHash").Algorithm} Algorithm */ + +/** + * @param {Chunk} chunk a chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {boolean} true, when a JS file is needed for this chunk + */ +const chunkHasJs = (chunk, chunkGraph) => { + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) return true; + + return Boolean( + chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript") + ); +}; + +/** + * @param {Chunk} chunk a chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {boolean} true, when a JS file is needed for this chunk + */ +const chunkHasRuntimeOrJs = (chunk, chunkGraph) => { + if ( + chunkGraph.getChunkModulesIterableBySourceType( + chunk, + WEBPACK_MODULE_TYPE_RUNTIME + ) + ) + return true; + + return Boolean( + chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript") + ); +}; + +/** + * @param {Module} module a module + * @param {string} code the code + * @returns {string} generated code for the stack + */ +const printGeneratedCodeForStack = (module, code) => { + const lines = code.split("\n"); + const n = `${lines.length}`.length; + return `\n\nGenerated code for ${module.identifier()}\n${lines + .map( + /** + * @param {string} line the line + * @param {number} i the index + * @param {string[]} lines the lines + * @returns {string} the line with line number + */ + (line, i, lines) => { + const iStr = `${i + 1}`; + return `${" ".repeat(n - iStr.length)}${iStr} | ${line}`; + } + ) + .join("\n")}`; +}; + +/** + * @typedef {object} RenderContext + * @property {Chunk} chunk the chunk + * @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 {CodeGenerationResults} codeGenerationResults results of code generation + * @property {boolean | undefined} strictMode rendering in strict context + */ + +/** + * @typedef {object} MainRenderContext + * @property {Chunk} chunk the chunk + * @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 {CodeGenerationResults} codeGenerationResults results of code generation + * @property {string} hash hash to be used for render call + * @property {boolean | undefined} strictMode rendering in strict context + */ + +/** + * @typedef {object} ChunkRenderContext + * @property {Chunk} chunk the chunk + * @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 {CodeGenerationResults} codeGenerationResults results of code generation + * @property {InitFragment[]} chunkInitFragments init fragments for the chunk + * @property {boolean | undefined} strictMode rendering in strict context + */ + +/** + * @typedef {object} RenderBootstrapContext + * @property {Chunk} chunk the chunk + * @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 + * @property {string} hash hash to be used for render call + */ + +/** @typedef {RenderContext & { inlined: boolean }} StartupRenderContext */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModuleContent + * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModuleContainer + * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage + * @property {SyncWaterfallHook<[Source, RenderContext]>} renderChunk + * @property {SyncWaterfallHook<[Source, RenderContext]>} renderMain + * @property {SyncWaterfallHook<[Source, RenderContext]>} renderContent + * @property {SyncWaterfallHook<[Source, RenderContext]>} render + * @property {SyncWaterfallHook<[Source, Module, StartupRenderContext]>} renderStartup + * @property {SyncWaterfallHook<[string, RenderBootstrapContext]>} renderRequire + * @property {SyncBailHook<[Module, RenderBootstrapContext], string | void>} inlineInRuntimeBailout + * @property {SyncBailHook<[Module, RenderContext], string | void>} embedInRuntimeBailout + * @property {SyncBailHook<[RenderContext], string | void>} strictRuntimeBailout + * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash + * @property {SyncBailHook<[Chunk, RenderContext], boolean | void>} useSourceMap + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +const PLUGIN_NAME = "JavascriptModulesPlugin"; + +/** @typedef {{ header: string[], beforeStartup: string[], startup: string[], afterStartup: string[], allowInlineStartup: boolean }} Bootstrap */ + +class JavascriptModulesPlugin { + /** + * @param {Compilation} compilation the compilation + * @returns {CompilationHooks} 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 = { + renderModuleContent: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]), + renderModuleContainer: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]), + renderModulePackage: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]), + render: new SyncWaterfallHook(["source", "renderContext"]), + renderContent: new SyncWaterfallHook(["source", "renderContext"]), + renderStartup: new SyncWaterfallHook([ + "source", + "module", + "startupRenderContext" + ]), + renderChunk: new SyncWaterfallHook(["source", "renderContext"]), + renderMain: new SyncWaterfallHook(["source", "renderContext"]), + renderRequire: new SyncWaterfallHook(["code", "renderContext"]), + inlineInRuntimeBailout: new SyncBailHook(["module", "renderContext"]), + embedInRuntimeBailout: new SyncBailHook(["module", "renderContext"]), + strictRuntimeBailout: new SyncBailHook(["renderContext"]), + chunkHash: new SyncHook(["chunk", "hash", "context"]), + useSourceMap: new SyncBailHook(["chunk", "renderContext"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + constructor(options = {}) { + this.options = options; + /** @type {WeakMap} */ + this._moduleFactoryCache = new WeakMap(); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); + normalModuleFactory.hooks.createParser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, options => new JavascriptParser("auto")); + normalModuleFactory.hooks.createParser + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, options => new JavascriptParser("script")); + normalModuleFactory.hooks.createParser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, options => new JavascriptParser("module")); + normalModuleFactory.hooks.createGenerator + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, () => new JavascriptGenerator()); + normalModuleFactory.hooks.createGenerator + .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) + .tap(PLUGIN_NAME, () => new JavascriptGenerator()); + normalModuleFactory.hooks.createGenerator + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, () => new JavascriptGenerator()); + compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { + const { + hash, + chunk, + chunkGraph, + moduleGraph, + runtimeTemplate, + dependencyTemplates, + outputOptions, + codeGenerationResults + } = options; + + const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null; + const filenameTemplate = + JavascriptModulesPlugin.getChunkFilenameTemplate( + chunk, + outputOptions + ); + + let render; + + if (hotUpdateChunk) { + render = () => + this.renderChunk( + { + chunk, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + codeGenerationResults, + strictMode: runtimeTemplate.isModule() + }, + hooks + ); + } else if (chunk.hasRuntime()) { + if (!chunkHasRuntimeOrJs(chunk, chunkGraph)) { + return result; + } + + render = () => + this.renderMain( + { + hash, + chunk, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + codeGenerationResults, + strictMode: runtimeTemplate.isModule() + }, + hooks, + compilation + ); + } else { + if (!chunkHasJs(chunk, chunkGraph)) { + return result; + } + + render = () => + this.renderChunk( + { + chunk, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + codeGenerationResults, + strictMode: runtimeTemplate.isModule() + }, + hooks + ); + } + + result.push({ + render, + filenameTemplate, + pathOptions: { + hash, + runtime: chunk.runtime, + chunk, + contentHashType: "javascript" + }, + info: { + javascriptModule: compilation.runtimeTemplate.isModule() + }, + identifier: hotUpdateChunk + ? `hotupdatechunk${chunk.id}` + : `chunk${chunk.id}`, + hash: chunk.contentHash.javascript + }); + + return result; + }); + compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => { + hooks.chunkHash.call(chunk, hash, context); + if (chunk.hasRuntime()) { + this.updateHashWithBootstrap( + hash, + { + hash: "0000", + chunk, + codeGenerationResults: context.codeGenerationResults, + chunkGraph: context.chunkGraph, + moduleGraph: context.moduleGraph, + runtimeTemplate: context.runtimeTemplate + }, + hooks + ); + } + }); + compilation.hooks.contentHash.tap(PLUGIN_NAME, chunk => { + const { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate, + outputOptions: { + hashSalt, + hashDigest, + hashDigestLength, + hashFunction + } + } = compilation; + const hash = createHash(/** @type {Algorithm} */ (hashFunction)); + if (hashSalt) hash.update(hashSalt); + if (chunk.hasRuntime()) { + this.updateHashWithBootstrap( + hash, + { + hash: "0000", + chunk, + codeGenerationResults, + chunkGraph: compilation.chunkGraph, + moduleGraph: compilation.moduleGraph, + runtimeTemplate: compilation.runtimeTemplate + }, + hooks + ); + } else { + hash.update(`${chunk.id} `); + hash.update(chunk.ids ? chunk.ids.join(",") : ""); + } + hooks.chunkHash.call(chunk, hash, { + chunkGraph, + codeGenerationResults, + moduleGraph, + runtimeTemplate + }); + const modules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + "javascript" + ); + if (modules) { + const xor = new StringXor(); + for (const m of modules) { + xor.add(chunkGraph.getModuleHash(m, chunk.runtime)); + } + xor.updateHash(hash); + } + const runtimeModules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + WEBPACK_MODULE_TYPE_RUNTIME + ); + if (runtimeModules) { + const xor = new StringXor(); + for (const m of runtimeModules) { + xor.add(chunkGraph.getModuleHash(m, chunk.runtime)); + } + xor.updateHash(hash); + } + const digest = /** @type {string} */ (hash.digest(hashDigest)); + chunk.contentHash.javascript = nonNumericOnlyHash( + digest, + /** @type {number} */ + (hashDigestLength) + ); + }); + compilation.hooks.additionalTreeRuntimeRequirements.tap( + PLUGIN_NAME, + (chunk, set, { chunkGraph }) => { + if ( + !set.has(RuntimeGlobals.startupNoDefault) && + chunkGraph.hasChunkEntryDependentChunks(chunk) + ) { + set.add(RuntimeGlobals.onChunksLoaded); + set.add(RuntimeGlobals.exports); + set.add(RuntimeGlobals.require); + } + } + ); + compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => { + const source = options.codeGenerationResult.sources.get("javascript"); + if (source === undefined) return; + const { module } = options; + const code = source.source(); + + const fn = vm.runInThisContext( + `(function(${module.moduleArgument}, ${module.exportsArgument}, ${RuntimeGlobals.require}) {\n${code}\n/**/})`, + { + filename: module.identifier(), + lineOffset: -1 + } + ); + + const moduleObject = + /** @type {ModuleObject} */ + (options.moduleObject); + + try { + fn.call( + moduleObject.exports, + moduleObject, + moduleObject.exports, + context.__webpack_require__ + ); + } catch (err) { + /** @type {Error} */ + (err).stack += printGeneratedCodeForStack( + options.module, + /** @type {string} */ (code) + ); + throw err; + } + }); + compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => { + const source = options.codeGenerationResult.sources.get("runtime"); + if (source === undefined) return; + let code = source.source(); + if (typeof code !== "string") code = code.toString(); + + const fn = vm.runInThisContext( + `(function(${RuntimeGlobals.require}) {\n${code}\n/**/})`, + { + filename: options.module.identifier(), + lineOffset: -1 + } + ); + try { + // eslint-disable-next-line no-useless-call + fn.call(null, context.__webpack_require__); + } catch (err) { + /** @type {Error} */ + (err).stack += printGeneratedCodeForStack(options.module, code); + throw err; + } + }); + } + ); + } + + /** + * @param {Chunk} chunk chunk + * @param {OutputOptions} outputOptions output options + * @returns {TemplatePath} used filename template + */ + static getChunkFilenameTemplate(chunk, outputOptions) { + if (chunk.filenameTemplate) { + return chunk.filenameTemplate; + } else if (chunk instanceof HotUpdateChunk) { + return /** @type {TemplatePath} */ (outputOptions.hotUpdateChunkFilename); + } else if (chunk.canBeInitial()) { + return /** @type {TemplatePath} */ (outputOptions.filename); + } + return /** @type {TemplatePath} */ (outputOptions.chunkFilename); + } + + /** + * @param {Module} module the rendered module + * @param {ChunkRenderContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @param {boolean} factory true: renders as factory method, false: pure module content + * @returns {Source | null} the newly generated source from rendering + */ + renderModule(module, renderContext, hooks, factory) { + const { + chunk, + chunkGraph, + runtimeTemplate, + codeGenerationResults, + strictMode + } = renderContext; + try { + const codeGenResult = codeGenerationResults.get(module, chunk.runtime); + const moduleSource = codeGenResult.sources.get("javascript"); + if (!moduleSource) return null; + if (codeGenResult.data !== undefined) { + const chunkInitFragments = codeGenResult.data.get("chunkInitFragments"); + if (chunkInitFragments) { + for (const i of chunkInitFragments) + renderContext.chunkInitFragments.push(i); + } + } + const moduleSourcePostContent = tryRunOrWebpackError( + () => + hooks.renderModuleContent.call(moduleSource, module, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderModuleContent" + ); + let moduleSourcePostContainer; + if (factory) { + const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements( + module, + chunk.runtime + ); + const needModule = runtimeRequirements.has(RuntimeGlobals.module); + const needExports = runtimeRequirements.has(RuntimeGlobals.exports); + const needRequire = + runtimeRequirements.has(RuntimeGlobals.require) || + runtimeRequirements.has(RuntimeGlobals.requireScope); + const needThisAsExports = runtimeRequirements.has( + RuntimeGlobals.thisAsExports + ); + const needStrict = + /** @type {BuildInfo} */ + (module.buildInfo).strict && !strictMode; + const cacheEntry = this._moduleFactoryCache.get( + moduleSourcePostContent + ); + let source; + if ( + cacheEntry && + cacheEntry.needModule === needModule && + cacheEntry.needExports === needExports && + cacheEntry.needRequire === needRequire && + cacheEntry.needThisAsExports === needThisAsExports && + cacheEntry.needStrict === needStrict + ) { + source = cacheEntry.source; + } else { + const factorySource = new ConcatSource(); + const args = []; + if (needExports || needRequire || needModule) + args.push( + needModule + ? module.moduleArgument + : `__unused_webpack_${module.moduleArgument}` + ); + if (needExports || needRequire) + args.push( + needExports + ? module.exportsArgument + : `__unused_webpack_${module.exportsArgument}` + ); + if (needRequire) args.push(RuntimeGlobals.require); + if (!needThisAsExports && runtimeTemplate.supportsArrowFunction()) { + factorySource.add(`/***/ ((${args.join(", ")}) => {\n\n`); + } else { + factorySource.add(`/***/ (function(${args.join(", ")}) {\n\n`); + } + if (needStrict) { + factorySource.add('"use strict";\n'); + } + factorySource.add(moduleSourcePostContent); + factorySource.add("\n\n/***/ })"); + source = new CachedSource(factorySource); + this._moduleFactoryCache.set(moduleSourcePostContent, { + source, + needModule, + needExports, + needRequire, + needThisAsExports, + needStrict + }); + } + moduleSourcePostContainer = tryRunOrWebpackError( + () => hooks.renderModuleContainer.call(source, module, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderModuleContainer" + ); + } else { + moduleSourcePostContainer = moduleSourcePostContent; + } + return tryRunOrWebpackError( + () => + hooks.renderModulePackage.call( + moduleSourcePostContainer, + module, + renderContext + ), + "JavascriptModulesPlugin.getCompilationHooks().renderModulePackage" + ); + } catch (err) { + /** @type {WebpackError} */ + (err).module = module; + throw err; + } + } + + /** + * @param {RenderContext} renderContext the render context + * @param {CompilationHooks} hooks hooks + * @returns {Source} the rendered source + */ + renderChunk(renderContext, hooks) { + const { chunk, chunkGraph } = renderContext; + const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "javascript", + compareModulesByIdentifier + ); + const allModules = modules ? Array.from(modules) : []; + let strictHeader; + let allStrict = renderContext.strictMode; + if ( + !allStrict && + allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict) + ) { + const strictBailout = hooks.strictRuntimeBailout.call(renderContext); + strictHeader = strictBailout + ? `// runtime can't be in strict mode because ${strictBailout}.\n` + : '"use strict";\n'; + if (!strictBailout) allStrict = true; + } + /** @type {ChunkRenderContext} */ + const chunkRenderContext = { + ...renderContext, + chunkInitFragments: [], + strictMode: allStrict + }; + const moduleSources = + Template.renderChunkModules(chunkRenderContext, allModules, module => + this.renderModule(module, chunkRenderContext, hooks, true) + ) || new RawSource("{}"); + let source = tryRunOrWebpackError( + () => hooks.renderChunk.call(moduleSources, chunkRenderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderChunk" + ); + source = tryRunOrWebpackError( + () => hooks.renderContent.call(source, chunkRenderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderContent" + ); + if (!source) { + throw new Error( + "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something" + ); + } + source = InitFragment.addToSource( + source, + chunkRenderContext.chunkInitFragments, + chunkRenderContext + ); + source = tryRunOrWebpackError( + () => hooks.render.call(source, chunkRenderContext), + "JavascriptModulesPlugin.getCompilationHooks().render" + ); + if (!source) { + throw new Error( + "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something" + ); + } + chunk.rendered = true; + return strictHeader + ? new ConcatSource(strictHeader, source, ";") + : renderContext.runtimeTemplate.isModule() + ? source + : new ConcatSource(source, ";"); + } + + /** + * @param {MainRenderContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @param {Compilation} compilation the compilation + * @returns {Source} the newly generated source from rendering + */ + renderMain(renderContext, hooks, compilation) { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); + const iife = runtimeTemplate.isIIFE(); + + const bootstrap = this.renderBootstrap(renderContext, hooks); + const useSourceMap = hooks.useSourceMap.call(chunk, renderContext); + + const allModules = Array.from( + chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "javascript", + compareModulesByIdentifier + ) || [] + ); + + const hasEntryModules = chunkGraph.getNumberOfEntryModules(chunk) > 0; + /** @type {Set | undefined} */ + let inlinedModules; + if (bootstrap.allowInlineStartup && hasEntryModules) { + inlinedModules = new Set(chunkGraph.getChunkEntryModulesIterable(chunk)); + } + + const source = new ConcatSource(); + let prefix; + if (iife) { + if (runtimeTemplate.supportsArrowFunction()) { + source.add("/******/ (() => { // webpackBootstrap\n"); + } else { + source.add("/******/ (function() { // webpackBootstrap\n"); + } + prefix = "/******/ \t"; + } else { + prefix = "/******/ "; + } + let allStrict = renderContext.strictMode; + if ( + !allStrict && + allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict) + ) { + const strictBailout = hooks.strictRuntimeBailout.call(renderContext); + if (strictBailout) { + source.add( + `${ + prefix + }// runtime can't be in strict mode because ${strictBailout}.\n` + ); + } else { + allStrict = true; + source.add(`${prefix}"use strict";\n`); + } + } + + /** @type {ChunkRenderContext} */ + const chunkRenderContext = { + ...renderContext, + chunkInitFragments: [], + strictMode: allStrict + }; + + const chunkModules = Template.renderChunkModules( + chunkRenderContext, + inlinedModules + ? allModules.filter( + m => !(/** @type {Set} */ (inlinedModules).has(m)) + ) + : allModules, + module => this.renderModule(module, chunkRenderContext, hooks, true), + prefix + ); + if ( + chunkModules || + runtimeRequirements.has(RuntimeGlobals.moduleFactories) || + runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly) || + runtimeRequirements.has(RuntimeGlobals.require) + ) { + source.add(`${prefix}var __webpack_modules__ = (`); + source.add(chunkModules || "{}"); + source.add(");\n"); + source.add( + "/************************************************************************/\n" + ); + } + + if (bootstrap.header.length > 0) { + const header = `${Template.asString(bootstrap.header)}\n`; + source.add( + new PrefixSource( + prefix, + useSourceMap + ? new OriginalSource(header, "webpack/bootstrap") + : new RawSource(header) + ) + ); + source.add( + "/************************************************************************/\n" + ); + } + + const runtimeModules = + renderContext.chunkGraph.getChunkRuntimeModulesInOrder(chunk); + + if (runtimeModules.length > 0) { + source.add( + new PrefixSource( + prefix, + Template.renderRuntimeModules(runtimeModules, chunkRenderContext) + ) + ); + source.add( + "/************************************************************************/\n" + ); + // runtimeRuntimeModules calls codeGeneration + for (const module of runtimeModules) { + compilation.codeGeneratedModules.add(module); + } + } + if (inlinedModules) { + if (bootstrap.beforeStartup.length > 0) { + const beforeStartup = `${Template.asString(bootstrap.beforeStartup)}\n`; + source.add( + new PrefixSource( + prefix, + useSourceMap + ? new OriginalSource(beforeStartup, "webpack/before-startup") + : new RawSource(beforeStartup) + ) + ); + } + const lastInlinedModule = /** @type {Module} */ (last(inlinedModules)); + const startupSource = new ConcatSource(); + + if (runtimeRequirements.has(RuntimeGlobals.exports)) { + startupSource.add(`var ${RuntimeGlobals.exports} = {};\n`); + } + + const avoidEntryIife = compilation.options.optimization.avoidEntryIife; + /** @type {Map | false} */ + let renamedInlinedModule = false; + if (avoidEntryIife) { + renamedInlinedModule = this.getRenamedInlineModule( + allModules, + renderContext, + inlinedModules, + chunkRenderContext, + hooks, + allStrict, + Boolean(chunkModules) + ); + } + + for (const m of inlinedModules) { + const renderedModule = renamedInlinedModule + ? renamedInlinedModule.get(m) + : this.renderModule(m, chunkRenderContext, hooks, false); + + if (renderedModule) { + const innerStrict = + !allStrict && /** @type {BuildInfo} */ (m.buildInfo).strict; + const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements( + m, + chunk.runtime + ); + const exports = runtimeRequirements.has(RuntimeGlobals.exports); + const webpackExports = + exports && m.exportsArgument === RuntimeGlobals.exports; + const iife = innerStrict + ? "it needs to be in strict mode." + : inlinedModules.size > 1 + ? // TODO check globals and top-level declarations of other entries and chunk modules + // to make a better decision + "it needs to be isolated against other entry modules." + : chunkModules && !renamedInlinedModule + ? "it needs to be isolated against other modules in the chunk." + : exports && !webpackExports + ? `it uses a non-standard name for the exports (${m.exportsArgument}).` + : hooks.embedInRuntimeBailout.call(m, renderContext); + let footer; + if (iife !== undefined) { + startupSource.add( + `// This entry needs to be wrapped in an IIFE because ${iife}\n` + ); + const arrow = runtimeTemplate.supportsArrowFunction(); + if (arrow) { + startupSource.add("(() => {\n"); + footer = "\n})();\n\n"; + } else { + startupSource.add("!function() {\n"); + footer = "\n}();\n"; + } + if (innerStrict) startupSource.add('"use strict";\n'); + } else { + footer = "\n"; + } + if (exports) { + if (m !== lastInlinedModule) + startupSource.add(`var ${m.exportsArgument} = {};\n`); + else if (m.exportsArgument !== RuntimeGlobals.exports) + startupSource.add( + `var ${m.exportsArgument} = ${RuntimeGlobals.exports};\n` + ); + } + startupSource.add(renderedModule); + startupSource.add(footer); + } + } + if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) { + startupSource.add( + `${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});\n` + ); + } + source.add( + hooks.renderStartup.call(startupSource, lastInlinedModule, { + ...renderContext, + inlined: true + }) + ); + if (bootstrap.afterStartup.length > 0) { + const afterStartup = `${Template.asString(bootstrap.afterStartup)}\n`; + source.add( + new PrefixSource( + prefix, + useSourceMap + ? new OriginalSource(afterStartup, "webpack/after-startup") + : new RawSource(afterStartup) + ) + ); + } + } else { + const lastEntryModule = + /** @type {Module} */ + (last(chunkGraph.getChunkEntryModulesIterable(chunk))); + /** @type {function(string[], string): Source} */ + const toSource = useSourceMap + ? (content, name) => + new OriginalSource(Template.asString(content), name) + : content => new RawSource(Template.asString(content)); + source.add( + new PrefixSource( + prefix, + new ConcatSource( + toSource(bootstrap.beforeStartup, "webpack/before-startup"), + "\n", + hooks.renderStartup.call( + toSource(bootstrap.startup.concat(""), "webpack/startup"), + lastEntryModule, + { + ...renderContext, + inlined: false + } + ), + toSource(bootstrap.afterStartup, "webpack/after-startup"), + "\n" + ) + ) + ); + } + if ( + hasEntryModules && + runtimeRequirements.has(RuntimeGlobals.returnExportsFromRuntime) + ) { + source.add(`${prefix}return ${RuntimeGlobals.exports};\n`); + } + if (iife) { + source.add("/******/ })()\n"); + } + + /** @type {Source} */ + let finalSource = tryRunOrWebpackError( + () => hooks.renderMain.call(source, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderMain" + ); + if (!finalSource) { + throw new Error( + "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderMain plugins should return something" + ); + } + finalSource = tryRunOrWebpackError( + () => hooks.renderContent.call(finalSource, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderContent" + ); + if (!finalSource) { + throw new Error( + "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something" + ); + } + + finalSource = InitFragment.addToSource( + finalSource, + chunkRenderContext.chunkInitFragments, + chunkRenderContext + ); + finalSource = tryRunOrWebpackError( + () => hooks.render.call(finalSource, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().render" + ); + if (!finalSource) { + throw new Error( + "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something" + ); + } + chunk.rendered = true; + return iife ? new ConcatSource(finalSource, ";") : finalSource; + } + + /** + * @param {Hash} hash the hash to be updated + * @param {RenderBootstrapContext} renderContext options object + * @param {CompilationHooks} hooks hooks + */ + updateHashWithBootstrap(hash, renderContext, hooks) { + const bootstrap = this.renderBootstrap(renderContext, hooks); + for (const _k of Object.keys(bootstrap)) { + const key = /** @type {keyof Bootstrap} */ (_k); + hash.update(key); + if (Array.isArray(bootstrap[key])) { + for (const line of bootstrap[key]) { + hash.update(line); + } + } else { + hash.update(JSON.stringify(bootstrap[key])); + } + } + } + + /** + * @param {RenderBootstrapContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @returns {Bootstrap} the generated source of the bootstrap code + */ + renderBootstrap(renderContext, hooks) { + const { + chunkGraph, + codeGenerationResults, + moduleGraph, + chunk, + runtimeTemplate + } = renderContext; + + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); + + const requireFunction = runtimeRequirements.has(RuntimeGlobals.require); + const moduleCache = runtimeRequirements.has(RuntimeGlobals.moduleCache); + const moduleFactories = runtimeRequirements.has( + RuntimeGlobals.moduleFactories + ); + const moduleUsed = runtimeRequirements.has(RuntimeGlobals.module); + const requireScopeUsed = runtimeRequirements.has( + RuntimeGlobals.requireScope + ); + const interceptModuleExecution = runtimeRequirements.has( + RuntimeGlobals.interceptModuleExecution + ); + + const useRequire = + requireFunction || interceptModuleExecution || moduleUsed; + + /** + * @type {{startup: string[], beforeStartup: string[], header: string[], afterStartup: string[], allowInlineStartup: boolean}} + */ + const result = { + header: [], + beforeStartup: [], + startup: [], + afterStartup: [], + allowInlineStartup: true + }; + + const { header: buf, startup, beforeStartup, afterStartup } = result; + + if (result.allowInlineStartup && moduleFactories) { + startup.push( + "// module factories are used so entry inlining is disabled" + ); + result.allowInlineStartup = false; + } + if (result.allowInlineStartup && moduleCache) { + startup.push("// module cache are used so entry inlining is disabled"); + result.allowInlineStartup = false; + } + if (result.allowInlineStartup && interceptModuleExecution) { + startup.push( + "// module execution is intercepted so entry inlining is disabled" + ); + result.allowInlineStartup = false; + } + + if (useRequire || moduleCache) { + buf.push("// The module cache"); + buf.push("var __webpack_module_cache__ = {};"); + buf.push(""); + } + + if (useRequire) { + buf.push("// The require function"); + buf.push(`function ${RuntimeGlobals.require}(moduleId) {`); + buf.push(Template.indent(this.renderRequire(renderContext, hooks))); + buf.push("}"); + buf.push(""); + } else if (runtimeRequirements.has(RuntimeGlobals.requireScope)) { + buf.push("// The require scope"); + buf.push(`var ${RuntimeGlobals.require} = {};`); + buf.push(""); + } + + if ( + moduleFactories || + runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly) + ) { + buf.push("// expose the modules object (__webpack_modules__)"); + buf.push(`${RuntimeGlobals.moduleFactories} = __webpack_modules__;`); + buf.push(""); + } + + if (moduleCache) { + buf.push("// expose the module cache"); + buf.push(`${RuntimeGlobals.moduleCache} = __webpack_module_cache__;`); + buf.push(""); + } + + if (interceptModuleExecution) { + buf.push("// expose the module execution interceptor"); + buf.push(`${RuntimeGlobals.interceptModuleExecution} = [];`); + buf.push(""); + } + + if (!runtimeRequirements.has(RuntimeGlobals.startupNoDefault)) { + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { + /** @type {string[]} */ + const buf2 = []; + const runtimeRequirements = + chunkGraph.getTreeRuntimeRequirements(chunk); + buf2.push("// Load entry module and return exports"); + let i = chunkGraph.getNumberOfEntryModules(chunk); + for (const [ + entryModule, + entrypoint + ] of chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)) { + if (!chunkGraph.getModuleSourceTypes(entryModule).has("javascript")) { + i--; + continue; + } + const chunks = + /** @type {Entrypoint} */ + (entrypoint).chunks.filter(c => c !== chunk); + if (result.allowInlineStartup && chunks.length > 0) { + buf2.push( + "// This entry module depends on other loaded chunks and execution need to be delayed" + ); + result.allowInlineStartup = false; + } + if ( + result.allowInlineStartup && + someInIterable( + moduleGraph.getIncomingConnectionsByOriginModule(entryModule), + ([originModule, connections]) => + originModule && + connections.some(c => c.isTargetActive(chunk.runtime)) && + someInIterable( + chunkGraph.getModuleRuntimes(originModule), + runtime => + intersectRuntime(runtime, chunk.runtime) !== undefined + ) + ) + ) { + buf2.push( + "// This entry module is referenced by other modules so it can't be inlined" + ); + result.allowInlineStartup = false; + } + + let data; + if (codeGenerationResults.has(entryModule, chunk.runtime)) { + const result = codeGenerationResults.get( + entryModule, + chunk.runtime + ); + data = result.data; + } + if ( + result.allowInlineStartup && + (!data || !data.get("topLevelDeclarations")) && + (!entryModule.buildInfo || + !entryModule.buildInfo.topLevelDeclarations) + ) { + buf2.push( + "// This entry module doesn't tell about it's top-level declarations so it can't be inlined" + ); + result.allowInlineStartup = false; + } + if (result.allowInlineStartup) { + const bailout = hooks.inlineInRuntimeBailout.call( + entryModule, + renderContext + ); + if (bailout !== undefined) { + buf2.push( + `// This entry module can't be inlined because ${bailout}` + ); + result.allowInlineStartup = false; + } + } + i--; + const moduleId = chunkGraph.getModuleId(entryModule); + const entryRuntimeRequirements = + chunkGraph.getModuleRuntimeRequirements(entryModule, chunk.runtime); + let moduleIdExpr = JSON.stringify(moduleId); + if (runtimeRequirements.has(RuntimeGlobals.entryModuleId)) { + moduleIdExpr = `${RuntimeGlobals.entryModuleId} = ${moduleIdExpr}`; + } + if ( + result.allowInlineStartup && + entryRuntimeRequirements.has(RuntimeGlobals.module) + ) { + result.allowInlineStartup = false; + buf2.push( + "// This entry module used 'module' so it can't be inlined" + ); + } + if (chunks.length > 0) { + buf2.push( + `${i === 0 ? `var ${RuntimeGlobals.exports} = ` : ""}${ + RuntimeGlobals.onChunksLoaded + }(undefined, ${JSON.stringify( + chunks.map(c => c.id) + )}, ${runtimeTemplate.returningFunction( + `${RuntimeGlobals.require}(${moduleIdExpr})` + )})` + ); + } else if (useRequire) { + buf2.push( + `${i === 0 ? `var ${RuntimeGlobals.exports} = ` : ""}${ + RuntimeGlobals.require + }(${moduleIdExpr});` + ); + } else { + if (i === 0) buf2.push(`var ${RuntimeGlobals.exports} = {};`); + if (requireScopeUsed) { + buf2.push( + `__webpack_modules__[${moduleIdExpr}](0, ${ + i === 0 ? RuntimeGlobals.exports : "{}" + }, ${RuntimeGlobals.require});` + ); + } else if (entryRuntimeRequirements.has(RuntimeGlobals.exports)) { + buf2.push( + `__webpack_modules__[${moduleIdExpr}](0, ${ + i === 0 ? RuntimeGlobals.exports : "{}" + });` + ); + } else { + buf2.push(`__webpack_modules__[${moduleIdExpr}]();`); + } + } + } + if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) { + buf2.push( + `${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});` + ); + } + if ( + runtimeRequirements.has(RuntimeGlobals.startup) || + (runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) && + runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)) + ) { + result.allowInlineStartup = false; + buf.push("// the startup function"); + buf.push( + `${RuntimeGlobals.startup} = ${runtimeTemplate.basicFunction("", [ + ...buf2, + `return ${RuntimeGlobals.exports};` + ])};` + ); + buf.push(""); + startup.push("// run startup"); + startup.push( + `var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();` + ); + } else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore)) { + buf.push("// the startup function"); + buf.push( + `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` + ); + beforeStartup.push("// run runtime startup"); + beforeStartup.push(`${RuntimeGlobals.startup}();`); + startup.push("// startup"); + startup.push(Template.asString(buf2)); + } else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)) { + buf.push("// the startup function"); + buf.push( + `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` + ); + startup.push("// startup"); + startup.push(Template.asString(buf2)); + afterStartup.push("// run runtime startup"); + afterStartup.push(`${RuntimeGlobals.startup}();`); + } else { + startup.push("// startup"); + startup.push(Template.asString(buf2)); + } + } else if ( + runtimeRequirements.has(RuntimeGlobals.startup) || + runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) || + runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter) + ) { + buf.push( + "// the startup function", + "// It's empty as no entry modules are in this chunk", + `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`, + "" + ); + } + } else if ( + runtimeRequirements.has(RuntimeGlobals.startup) || + runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) || + runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter) + ) { + result.allowInlineStartup = false; + buf.push( + "// the startup function", + "// It's empty as some runtime module handles the default behavior", + `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` + ); + startup.push("// run startup"); + startup.push( + `var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();` + ); + } + return result; + } + + /** + * @param {RenderBootstrapContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @returns {string} the generated source of the require function + */ + renderRequire(renderContext, hooks) { + const { + chunk, + chunkGraph, + runtimeTemplate: { outputOptions } + } = renderContext; + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); + const moduleExecution = runtimeRequirements.has( + RuntimeGlobals.interceptModuleExecution + ) + ? Template.asString([ + `var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: ${RuntimeGlobals.require} };`, + `${RuntimeGlobals.interceptModuleExecution}.forEach(function(handler) { handler(execOptions); });`, + "module = execOptions.module;", + "execOptions.factory.call(module.exports, module, module.exports, execOptions.require);" + ]) + : runtimeRequirements.has(RuntimeGlobals.thisAsExports) + ? Template.asString([ + `__webpack_modules__[moduleId].call(module.exports, module, module.exports, ${RuntimeGlobals.require});` + ]) + : Template.asString([ + `__webpack_modules__[moduleId](module, module.exports, ${RuntimeGlobals.require});` + ]); + const needModuleId = runtimeRequirements.has(RuntimeGlobals.moduleId); + const needModuleLoaded = runtimeRequirements.has( + RuntimeGlobals.moduleLoaded + ); + const content = Template.asString([ + "// Check if module is in cache", + "var cachedModule = __webpack_module_cache__[moduleId];", + "if (cachedModule !== undefined) {", + outputOptions.strictModuleErrorHandling + ? Template.indent([ + "if (cachedModule.error !== undefined) throw cachedModule.error;", + "return cachedModule.exports;" + ]) + : Template.indent("return cachedModule.exports;"), + "}", + "// Create a new module (and put it into the cache)", + "var module = __webpack_module_cache__[moduleId] = {", + Template.indent([ + needModuleId ? "id: moduleId," : "// no module.id needed", + needModuleLoaded ? "loaded: false," : "// no module.loaded needed", + "exports: {}" + ]), + "};", + "", + outputOptions.strictModuleExceptionHandling + ? Template.asString([ + "// Execute the module function", + "var threw = true;", + "try {", + Template.indent([moduleExecution, "threw = false;"]), + "} finally {", + Template.indent([ + "if(threw) delete __webpack_module_cache__[moduleId];" + ]), + "}" + ]) + : outputOptions.strictModuleErrorHandling + ? Template.asString([ + "// Execute the module function", + "try {", + Template.indent(moduleExecution), + "} catch(e) {", + Template.indent(["module.error = e;", "throw e;"]), + "}" + ]) + : Template.asString([ + "// Execute the module function", + moduleExecution + ]), + needModuleLoaded + ? Template.asString([ + "", + "// Flag the module as loaded", + `${RuntimeGlobals.moduleLoaded} = true;`, + "" + ]) + : "", + "// Return the exports of the module", + "return module.exports;" + ]); + return tryRunOrWebpackError( + () => hooks.renderRequire.call(content, renderContext), + "JavascriptModulesPlugin.getCompilationHooks().renderRequire" + ); + } + + /** + * @param {Module[]} allModules allModules + * @param {MainRenderContext} renderContext renderContext + * @param {Set} inlinedModules inlinedModules + * @param {ChunkRenderContext} chunkRenderContext chunkRenderContext + * @param {CompilationHooks} hooks hooks + * @param {boolean | undefined} allStrict allStrict + * @param {boolean} hasChunkModules hasChunkModules + * @returns {Map | false} renamed inlined modules + */ + getRenamedInlineModule( + allModules, + renderContext, + inlinedModules, + chunkRenderContext, + hooks, + allStrict, + hasChunkModules + ) { + const innerStrict = + !allStrict && + allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict); + const isMultipleEntries = inlinedModules.size > 1; + const singleEntryWithModules = inlinedModules.size === 1 && hasChunkModules; + + // TODO: + // This step is before the IIFE reason calculation. Ideally, it should only be executed when this function can optimize the + // IIFE reason. Otherwise, it should directly return false. There are four reasons now, we have skipped two already, the left + // one is 'it uses a non-standard name for the exports'. + if (isMultipleEntries || innerStrict || !singleEntryWithModules) { + return false; + } + + /** @type {Map} */ + const renamedInlinedModules = new Map(); + const { runtimeTemplate } = renderContext; + + /** @typedef {{ source: Source, module: Module, ast: any, variables: Set, through: Set, usedInNonInlined: Set, moduleScope: Scope }} Info */ + /** @type {Map} */ + const inlinedModulesToInfo = new Map(); + /** @type {Set} */ + const nonInlinedModuleThroughIdentifiers = new Set(); + /** @type {Map} */ + + for (const m of allModules) { + const isInlinedModule = inlinedModules && inlinedModules.has(m); + const moduleSource = this.renderModule( + m, + chunkRenderContext, + hooks, + !isInlinedModule + ); + + if (!moduleSource) continue; + const code = /** @type {string} */ (moduleSource.source()); + const ast = JavascriptParser._parse(code, { + sourceType: "auto" + }); + + const scopeManager = eslintScope.analyze(ast, { + ecmaVersion: 6, + sourceType: "module", + optimistic: true, + ignoreEval: true + }); + + const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast)); + if (inlinedModules && inlinedModules.has(m)) { + const moduleScope = globalScope.childScopes[0]; + inlinedModulesToInfo.set(m, { + source: moduleSource, + ast, + module: m, + variables: new Set(moduleScope.variables), + through: new Set(moduleScope.through), + usedInNonInlined: new Set(), + moduleScope + }); + } else { + for (const ref of globalScope.through) { + nonInlinedModuleThroughIdentifiers.add(ref.identifier.name); + } + } + } + + for (const [, { variables, usedInNonInlined }] of inlinedModulesToInfo) { + for (const variable of variables) { + if ( + nonInlinedModuleThroughIdentifiers.has(variable.name) || + RESERVED_NAMES.has(variable.name) + ) { + usedInNonInlined.add(variable); + } + } + } + + for (const [m, moduleInfo] of inlinedModulesToInfo) { + const { ast, source: _source, usedInNonInlined } = moduleInfo; + const source = new ReplaceSource(_source); + if (usedInNonInlined.size === 0) { + renamedInlinedModules.set(m, source); + continue; + } + + const info = /** @type {Info} */ (inlinedModulesToInfo.get(m)); + const allUsedNames = new Set( + Array.from(info.through, v => v.identifier.name) + ); + + for (const variable of usedInNonInlined) { + allUsedNames.add(variable.name); + } + + for (const variable of info.variables) { + allUsedNames.add(variable.name); + const references = getAllReferences(variable); + const allIdentifiers = new Set( + references.map(r => r.identifier).concat(variable.identifiers) + ); + + const usedNamesInScopeInfo = new Map(); + const ignoredScopes = new Set(); + + const name = variable.name; + const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( + usedNamesInScopeInfo, + info.module.identifier(), + name + ); + + if (allUsedNames.has(name) || usedNames.has(name)) { + const references = getAllReferences(variable); + for (const ref of references) { + addScopeSymbols( + ref.from, + usedNames, + alreadyCheckedScopes, + ignoredScopes + ); + } + + const newName = findNewName( + variable.name, + allUsedNames, + usedNames, + m.readableIdentifier(runtimeTemplate.requestShortener) + ); + allUsedNames.add(newName); + for (const identifier of allIdentifiers) { + const r = /** @type {Range} */ (identifier.range); + const path = getPathInAst(ast, identifier); + if (path && path.length > 1) { + const maybeProperty = + path[1].type === "AssignmentPattern" && path[1].left === path[0] + ? path[2] + : path[1]; + if ( + maybeProperty.type === "Property" && + maybeProperty.shorthand + ) { + source.insert(r[1], `: ${newName}`); + continue; + } + } + source.replace(r[0], r[1] - 1, newName); + } + } else { + allUsedNames.add(name); + } + } + + renamedInlinedModules.set(m, source); + } + + return renamedInlinedModules; + } +} + +module.exports = JavascriptModulesPlugin; +module.exports.chunkHasJs = chunkHasJs; diff --git a/webpack-lib/lib/javascript/JavascriptParser.js b/webpack-lib/lib/javascript/JavascriptParser.js new file mode 100644 index 00000000000..084292385ae --- /dev/null +++ b/webpack-lib/lib/javascript/JavascriptParser.js @@ -0,0 +1,5007 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { Parser: AcornParser, tokTypes } = require("acorn"); +const { SyncBailHook, HookMap } = require("tapable"); +const vm = require("vm"); +const Parser = require("../Parser"); +const StackedMap = require("../util/StackedMap"); +const binarySearchBounds = require("../util/binarySearchBounds"); +const { + webpackCommentRegExp, + createMagicCommentContext +} = require("../util/magicComment"); +const memoize = require("../util/memoize"); +const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); + +/** @typedef {import("acorn").Options} AcornOptions */ +/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ +/** @typedef {import("estree").BinaryExpression} BinaryExpression */ +/** @typedef {import("estree").BlockStatement} BlockStatement */ +/** @typedef {import("estree").SequenceExpression} SequenceExpression */ +/** @typedef {import("estree").CallExpression} CallExpression */ +/** @typedef {import("estree").BaseCallExpression} BaseCallExpression */ +/** @typedef {import("estree").StaticBlock} StaticBlock */ +/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */ +/** @typedef {import("estree").ForStatement} ForStatement */ +/** @typedef {import("estree").SwitchStatement} SwitchStatement */ +/** @typedef {import("estree").ClassExpression} ClassExpression */ +/** @typedef {import("estree").Comment} Comment */ +/** @typedef {import("estree").ConditionalExpression} ConditionalExpression */ +/** @typedef {import("estree").Declaration} Declaration */ +/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */ +/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").Identifier} Identifier */ +/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */ +/** @typedef {import("estree").IfStatement} IfStatement */ +/** @typedef {import("estree").LabeledStatement} LabeledStatement */ +/** @typedef {import("estree").Literal} Literal */ +/** @typedef {import("estree").LogicalExpression} LogicalExpression */ +/** @typedef {import("estree").ChainExpression} ChainExpression */ +/** @typedef {import("estree").MemberExpression} MemberExpression */ +/** @typedef {import("estree").YieldExpression} YieldExpression */ +/** @typedef {import("estree").MetaProperty} MetaProperty */ +/** @typedef {import("estree").Property} Property */ +/** @typedef {import("estree").AssignmentPattern} AssignmentPattern */ +/** @typedef {import("estree").ChainElement} ChainElement */ +/** @typedef {import("estree").Pattern} Pattern */ +/** @typedef {import("estree").UpdateExpression} UpdateExpression */ +/** @typedef {import("estree").ObjectExpression} ObjectExpression */ +/** @typedef {import("estree").UnaryExpression} UnaryExpression */ +/** @typedef {import("estree").ArrayExpression} ArrayExpression */ +/** @typedef {import("estree").ArrayPattern} ArrayPattern */ +/** @typedef {import("estree").AwaitExpression} AwaitExpression */ +/** @typedef {import("estree").ThisExpression} ThisExpression */ +/** @typedef {import("estree").RestElement} RestElement */ +/** @typedef {import("estree").ObjectPattern} ObjectPattern */ +/** @typedef {import("estree").SwitchCase} SwitchCase */ +/** @typedef {import("estree").CatchClause} CatchClause */ +/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */ +/** @typedef {import("estree").ForInStatement} ForInStatement */ +/** @typedef {import("estree").ForOfStatement} ForOfStatement */ +/** @typedef {import("estree").ReturnStatement} ReturnStatement */ +/** @typedef {import("estree").WithStatement} WithStatement */ +/** @typedef {import("estree").ThrowStatement} ThrowStatement */ +/** @typedef {import("estree").MethodDefinition} MethodDefinition */ +/** @typedef {import("estree").NewExpression} NewExpression */ +/** @typedef {import("estree").SpreadElement} SpreadElement */ +/** @typedef {import("estree").FunctionExpression} FunctionExpression */ +/** @typedef {import("estree").WhileStatement} WhileStatement */ +/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ +/** @typedef {import("estree").ExpressionStatement} ExpressionStatement */ +/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */ +/** @typedef {import("estree").DoWhileStatement} DoWhileStatement */ +/** @typedef {import("estree").TryStatement} TryStatement */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("estree").Program} Program */ +/** @typedef {import("estree").Directive} Directive */ +/** @typedef {import("estree").Statement} Statement */ +/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */ +/** @typedef {import("estree").Super} Super */ +/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression */ +/** @typedef {import("estree").TemplateLiteral} TemplateLiteral */ +/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */ +/** + * @template T + * @typedef {import("tapable").AsArray} AsArray + */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ +/** @typedef {{declaredScope: ScopeInfo, freeName: string | true | undefined, tagInfo: TagInfo | undefined}} VariableInfoInterface */ +/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[] }} GetInfoResult */ +/** @typedef {Statement | ModuleDeclaration | Expression} StatementPathItem */ +/** @typedef {function(string): void} OnIdentString */ +/** @typedef {function(string, Identifier): void} OnIdent */ +/** @typedef {StatementPathItem[]} StatementPath */ + +// TODO remove cast when @types/estree has been updated to import assertions +/** @typedef {import("estree").BaseNode & { type: "ImportAttribute", key: Identifier | Literal, value: Literal }} ImportAttribute */ +/** @typedef {import("estree").ImportDeclaration & { attributes?: Array }} ImportDeclaration */ +/** @typedef {import("estree").ExportNamedDeclaration & { attributes?: Array }} ExportNamedDeclaration */ +/** @typedef {import("estree").ExportAllDeclaration & { attributes?: Array }} ExportAllDeclaration */ +/** @typedef {import("estree").ImportExpression & { options?: Expression | null }} ImportExpression */ +/** @typedef {ImportDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration | ExportAllDeclaration} ModuleDeclaration */ + +/** @type {string[]} */ +const EMPTY_ARRAY = []; +const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01; +const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10; +const ALLOWED_MEMBER_TYPES_ALL = 0b11; + +const LEGACY_ASSERT_ATTRIBUTES = Symbol("assert"); + +/** + * @param {any} Parser parser + * @returns {typeof AcornParser} extender acorn parser + */ +const importAssertions = Parser => + /** @type {typeof AcornParser} */ ( + /** @type {unknown} */ ( + class extends Parser { + parseWithClause() { + const nodes = []; + + const isAssertLegacy = this.value === "assert"; + + if (isAssertLegacy) { + if (!this.eat(tokTypes.name)) { + return nodes; + } + } else if (!this.eat(tokTypes._with)) { + return nodes; + } + + this.expect(tokTypes.braceL); + + const attributeKeys = {}; + let first = true; + + while (!this.eat(tokTypes.braceR)) { + if (!first) { + this.expect(tokTypes.comma); + if (this.afterTrailingComma(tokTypes.braceR)) { + break; + } + } else { + first = false; + } + + const attr = this.parseImportAttribute(); + const keyName = + attr.key.type === "Identifier" ? attr.key.name : attr.key.value; + + if (Object.prototype.hasOwnProperty.call(attributeKeys, keyName)) { + this.raiseRecoverable( + attr.key.start, + `Duplicate attribute key '${keyName}'` + ); + } + + attributeKeys[keyName] = true; + nodes.push(attr); + } + + if (isAssertLegacy) { + nodes[LEGACY_ASSERT_ATTRIBUTES] = true; + } + + return nodes; + } + } + ) + ); + +// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API +const parser = AcornParser.extend(importAssertions); + +/** @typedef {Record & { _isLegacyAssert?: boolean }} ImportAttributes */ + +/** + * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions + * @returns {ImportAttributes | undefined} import attributes + */ +const getImportAttributes = node => { + if (node.type === "ImportExpression") { + if ( + node.options && + node.options.type === "ObjectExpression" && + node.options.properties[0] && + node.options.properties[0].type === "Property" && + node.options.properties[0].key.type === "Identifier" && + (node.options.properties[0].key.name === "with" || + node.options.properties[0].key.name === "assert") && + node.options.properties[0].value.type === "ObjectExpression" && + node.options.properties[0].value.properties.length > 0 + ) { + const properties = + /** @type {Property[]} */ + (node.options.properties[0].value.properties); + const result = /** @type {ImportAttributes} */ ({}); + for (const property of properties) { + const key = + /** @type {string} */ + ( + property.key.type === "Identifier" + ? property.key.name + : /** @type {Literal} */ (property.key).value + ); + result[key] = + /** @type {string} */ + (/** @type {Literal} */ (property.value).value); + } + const key = + node.options.properties[0].key.type === "Identifier" + ? node.options.properties[0].key.name + : /** @type {Literal} */ (node.options.properties[0].key).value; + + if (key === "assert") { + result._isLegacyAssert = true; + } + + return result; + } + + return; + } + + if (node.attributes === undefined || node.attributes.length === 0) { + return; + } + + const result = /** @type {ImportAttributes} */ ({}); + + for (const attribute of node.attributes) { + const key = + /** @type {string} */ + ( + attribute.key.type === "Identifier" + ? attribute.key.name + : attribute.key.value + ); + + result[key] = /** @type {string} */ (attribute.value.value); + } + + if (node.attributes[LEGACY_ASSERT_ATTRIBUTES]) { + result._isLegacyAssert = true; + } + + return result; +}; + +class VariableInfo { + /** + * @param {ScopeInfo} declaredScope scope in which the variable is declared + * @param {string | true | undefined} freeName which free name the variable aliases, or true when none + * @param {TagInfo | undefined} tagInfo info about tags + */ + constructor(declaredScope, freeName, tagInfo) { + this.declaredScope = declaredScope; + this.freeName = freeName; + this.tagInfo = tagInfo; + } +} + +/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */ +/** @typedef {Literal | string | null | undefined} ImportSource */ +/** @typedef {Omit & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */ + +/** + * @typedef {object} TagInfo + * @property {any} tag + * @property {any} data + * @property {TagInfo | undefined} next + */ + +/** + * @typedef {object} ScopeInfo + * @property {StackedMap} definitions + * @property {boolean | "arrow"} topLevelScope + * @property {boolean | string} inShorthand + * @property {boolean} inTaggedTemplateTag + * @property {boolean} inTry + * @property {boolean} isStrict + * @property {boolean} isAsmJs + */ + +/** @typedef {[number, number]} Range */ + +/** + * @typedef {object} DestructuringAssignmentProperty + * @property {string} id + * @property {Range | undefined=} range + * @property {boolean | string} shorthand + */ + +/** + * Helper function for joining two ranges into a single range. This is useful + * when working with AST nodes, as it allows you to combine the ranges of child nodes + * to create the range of the _parent node_. + * @param {[number, number]} startRange start range to join + * @param {[number, number]} endRange end range to join + * @returns {[number, number]} joined range + * @example + * ```js + * const startRange = [0, 5]; + * const endRange = [10, 15]; + * const joinedRange = joinRanges(startRange, endRange); + * console.log(joinedRange); // [0, 15] + * ``` + */ +const joinRanges = (startRange, endRange) => { + if (!endRange) return startRange; + if (!startRange) return endRange; + return [startRange[0], endRange[1]]; +}; + +/** + * Helper function used to generate a string representation of a + * [member expression](https://github.com/estree/estree/blob/master/es5.md#memberexpression). + * @param {string} object object to name + * @param {string[]} membersReversed reversed list of members + * @returns {string} member expression as a string + * @example + * ```js + * const membersReversed = ["property1", "property2", "property3"]; // Members parsed from the AST + * const name = objectAndMembersToName("myObject", membersReversed); + * + * console.log(name); // "myObject.property1.property2.property3" + * ``` + */ +const objectAndMembersToName = (object, membersReversed) => { + let name = object; + for (let i = membersReversed.length - 1; i >= 0; i--) { + name = `${name}.${membersReversed[i]}`; + } + return name; +}; + +/** + * Grabs the name of a given expression and returns it as a string or undefined. Has particular + * handling for [Identifiers](https://github.com/estree/estree/blob/master/es5.md#identifier), + * [ThisExpressions](https://github.com/estree/estree/blob/master/es5.md#identifier), and + * [MetaProperties](https://github.com/estree/estree/blob/master/es2015.md#metaproperty) which is + * specifically for handling the `new.target` meta property. + * @param {Expression | SpreadElement | Super} expression expression + * @returns {string | "this" | undefined} name or variable info + */ +const getRootName = expression => { + switch (expression.type) { + case "Identifier": + return expression.name; + case "ThisExpression": + return "this"; + case "MetaProperty": + return `${expression.meta.name}.${expression.property.name}`; + default: + return undefined; + } +}; + +/** @type {AcornOptions} */ +const defaultParserOptions = { + ranges: true, + locations: true, + ecmaVersion: "latest", + sourceType: "module", + // https://github.com/tc39/proposal-hashbang + allowHashBang: true, + onComment: undefined +}; + +const EMPTY_COMMENT_OPTIONS = { + options: null, + errors: null +}; + +class JavascriptParser extends Parser { + /** + * @param {"module" | "script" | "auto"} sourceType default source type + */ + constructor(sourceType = "auto") { + super(); + this.hooks = Object.freeze({ + /** @type {HookMap>} */ + evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {HookMap>} */ + evaluate: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {HookMap>} */ + evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {HookMap>} */ + evaluateDefinedIdentifier: new HookMap( + () => new SyncBailHook(["expression"]) + ), + /** @type {HookMap>} */ + evaluateNewExpression: new HookMap( + () => new SyncBailHook(["expression"]) + ), + /** @type {HookMap>} */ + evaluateCallExpression: new HookMap( + () => new SyncBailHook(["expression"]) + ), + /** @type {HookMap>} */ + evaluateCallExpressionMember: new HookMap( + () => new SyncBailHook(["expression", "param"]) + ), + /** @type {HookMap>} */ + isPure: new HookMap( + () => new SyncBailHook(["expression", "commentsStartPosition"]) + ), + /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ + preStatement: new SyncBailHook(["statement"]), + + /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ + blockPreStatement: new SyncBailHook(["declaration"]), + /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ + statement: new SyncBailHook(["statement"]), + /** @type {SyncBailHook<[IfStatement], boolean | void>} */ + statementIf: new SyncBailHook(["statement"]), + /** @type {SyncBailHook<[Expression, ClassExpression | ClassDeclaration], boolean | void>} */ + classExtendsExpression: new SyncBailHook([ + "expression", + "classDefinition" + ]), + /** @type {SyncBailHook<[MethodDefinition | PropertyDefinition | StaticBlock, ClassExpression | ClassDeclaration], boolean | void>} */ + classBodyElement: new SyncBailHook(["element", "classDefinition"]), + /** @type {SyncBailHook<[Expression, MethodDefinition | PropertyDefinition, ClassExpression | ClassDeclaration], boolean | void>} */ + classBodyValue: new SyncBailHook([ + "expression", + "element", + "classDefinition" + ]), + /** @type {HookMap>} */ + label: new HookMap(() => new SyncBailHook(["statement"])), + /** @type {SyncBailHook<[ImportDeclaration, ImportSource], boolean | void>} */ + import: new SyncBailHook(["statement", "source"]), + /** @type {SyncBailHook<[ImportDeclaration, ImportSource, string | null, string], boolean | void>} */ + importSpecifier: new SyncBailHook([ + "statement", + "source", + "exportName", + "identifierName" + ]), + /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration], boolean | void>} */ + export: new SyncBailHook(["statement"]), + /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource], boolean | void>} */ + exportImport: new SyncBailHook(["statement", "source"]), + /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, Declaration], boolean | void>} */ + exportDeclaration: new SyncBailHook(["statement", "declaration"]), + /** @type {SyncBailHook<[ExportDefaultDeclaration, FunctionDeclaration | ClassDeclaration], boolean | void>} */ + exportExpression: new SyncBailHook(["statement", "declaration"]), + /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, string, string, number | undefined], boolean | void>} */ + exportSpecifier: new SyncBailHook([ + "statement", + "identifierName", + "exportName", + "index" + ]), + /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource, string | null, string | null, number | undefined], boolean | void>} */ + exportImportSpecifier: new SyncBailHook([ + "statement", + "source", + "identifierName", + "exportName", + "index" + ]), + /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */ + preDeclarator: new SyncBailHook(["declarator", "statement"]), + /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */ + declarator: new SyncBailHook(["declarator", "statement"]), + /** @type {HookMap>} */ + varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])), + /** @type {HookMap>} */ + varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])), + /** @type {HookMap>} */ + varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])), + /** @type {HookMap>} */ + varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])), + /** @type {HookMap>} */ + pattern: new HookMap(() => new SyncBailHook(["pattern"])), + /** @type {HookMap>} */ + canRename: new HookMap(() => new SyncBailHook(["initExpression"])), + /** @type {HookMap>} */ + rename: new HookMap(() => new SyncBailHook(["initExpression"])), + /** @type {HookMap>} */ + assign: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {HookMap>} */ + assignMemberChain: new HookMap( + () => new SyncBailHook(["expression", "members"]) + ), + /** @type {HookMap>} */ + typeof: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {SyncBailHook<[ImportExpression], boolean | void>} */ + importCall: new SyncBailHook(["expression"]), + /** @type {SyncBailHook<[Expression | ForOfStatement], boolean | void>} */ + topLevelAwait: new SyncBailHook(["expression"]), + /** @type {HookMap>} */ + call: new HookMap(() => new SyncBailHook(["expression"])), + /** Something like "a.b()" */ + /** @type {HookMap>} */ + callMemberChain: new HookMap( + () => + new SyncBailHook([ + "expression", + "members", + "membersOptionals", + "memberRanges" + ]) + ), + /** Something like "a.b().c.d" */ + /** @type {HookMap>} */ + memberChainOfCallMemberChain: new HookMap( + () => + new SyncBailHook([ + "expression", + "calleeMembers", + "callExpression", + "members", + "memberRanges" + ]) + ), + /** Something like "a.b().c.d()"" */ + /** @type {HookMap>} */ + callMemberChainOfCallMemberChain: new HookMap( + () => + new SyncBailHook([ + "expression", + "calleeMembers", + "innerCallExpression", + "members", + "memberRanges" + ]) + ), + /** @type {SyncBailHook<[ChainExpression], boolean | void>} */ + optionalChaining: new SyncBailHook(["optionalChaining"]), + /** @type {HookMap>} */ + new: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {SyncBailHook<[BinaryExpression], boolean | void>} */ + binaryExpression: new SyncBailHook(["binaryExpression"]), + /** @type {HookMap>} */ + expression: new HookMap(() => new SyncBailHook(["expression"])), + /** @type {HookMap>} */ + expressionMemberChain: new HookMap( + () => + new SyncBailHook([ + "expression", + "members", + "membersOptionals", + "memberRanges" + ]) + ), + /** @type {HookMap>} */ + unhandledExpressionMemberChain: new HookMap( + () => new SyncBailHook(["expression", "members"]) + ), + /** @type {SyncBailHook<[ConditionalExpression], boolean | void>} */ + expressionConditionalOperator: new SyncBailHook(["expression"]), + /** @type {SyncBailHook<[LogicalExpression], boolean | void>} */ + expressionLogicalOperator: new SyncBailHook(["expression"]), + /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ + program: new SyncBailHook(["ast", "comments"]), + /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ + finish: new SyncBailHook(["ast", "comments"]) + }); + this.sourceType = sourceType; + /** @type {ScopeInfo} */ + this.scope = undefined; + /** @type {ParserState} */ + this.state = undefined; + /** @type {Comment[] | undefined} */ + this.comments = undefined; + /** @type {Set | undefined} */ + this.semicolons = undefined; + /** @type {StatementPath | undefined} */ + this.statementPath = undefined; + /** @type {Statement | ModuleDeclaration | Expression | undefined} */ + this.prevStatement = undefined; + /** @type {WeakMap> | undefined} */ + this.destructuringAssignmentProperties = undefined; + this.currentTagData = undefined; + this.magicCommentContext = createMagicCommentContext(); + this._initializeEvaluating(); + } + + _initializeEvaluating() { + this.hooks.evaluate.for("Literal").tap("JavascriptParser", _expr => { + const expr = /** @type {Literal} */ (_expr); + + switch (typeof expr.value) { + case "number": + return new BasicEvaluatedExpression() + .setNumber(expr.value) + .setRange(/** @type {Range} */ (expr.range)); + case "bigint": + return new BasicEvaluatedExpression() + .setBigInt(expr.value) + .setRange(/** @type {Range} */ (expr.range)); + case "string": + return new BasicEvaluatedExpression() + .setString(expr.value) + .setRange(/** @type {Range} */ (expr.range)); + case "boolean": + return new BasicEvaluatedExpression() + .setBoolean(expr.value) + .setRange(/** @type {Range} */ (expr.range)); + } + if (expr.value === null) { + return new BasicEvaluatedExpression() + .setNull() + .setRange(/** @type {Range} */ (expr.range)); + } + if (expr.value instanceof RegExp) { + return new BasicEvaluatedExpression() + .setRegExp(expr.value) + .setRange(/** @type {Range} */ (expr.range)); + } + }); + this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => { + const expr = /** @type {NewExpression} */ (_expr); + const callee = expr.callee; + if (callee.type !== "Identifier") return; + if (callee.name !== "RegExp") { + return this.callHooksForName( + this.hooks.evaluateNewExpression, + callee.name, + expr + ); + } else if ( + expr.arguments.length > 2 || + this.getVariableInfo("RegExp") !== "RegExp" + ) + return; + + let regExp; + const arg1 = expr.arguments[0]; + + if (arg1) { + if (arg1.type === "SpreadElement") return; + + const evaluatedRegExp = this.evaluateExpression(arg1); + + if (!evaluatedRegExp) return; + + regExp = evaluatedRegExp.asString(); + + if (!regExp) return; + } else { + return ( + new BasicEvaluatedExpression() + // eslint-disable-next-line prefer-regex-literals + .setRegExp(new RegExp("")) + .setRange(/** @type {Range} */ (expr.range)) + ); + } + + let flags; + const arg2 = expr.arguments[1]; + + if (arg2) { + if (arg2.type === "SpreadElement") return; + + const evaluatedFlags = this.evaluateExpression(arg2); + + if (!evaluatedFlags) return; + + if (!evaluatedFlags.isUndefined()) { + flags = evaluatedFlags.asString(); + + if ( + flags === undefined || + !BasicEvaluatedExpression.isValidRegExpFlags(flags) + ) + return; + } + } + + return new BasicEvaluatedExpression() + .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp)) + .setRange(/** @type {Range} */ (expr.range)); + }); + this.hooks.evaluate + .for("LogicalExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {LogicalExpression} */ (_expr); + + const left = this.evaluateExpression(expr.left); + let returnRight = false; + /** @type {boolean | undefined} */ + let allowedRight; + if (expr.operator === "&&") { + const leftAsBool = left.asBool(); + if (leftAsBool === false) + return left.setRange(/** @type {Range} */ (expr.range)); + returnRight = leftAsBool === true; + allowedRight = false; + } else if (expr.operator === "||") { + const leftAsBool = left.asBool(); + if (leftAsBool === true) + return left.setRange(/** @type {Range} */ (expr.range)); + returnRight = leftAsBool === false; + allowedRight = true; + } else if (expr.operator === "??") { + const leftAsNullish = left.asNullish(); + if (leftAsNullish === false) + return left.setRange(/** @type {Range} */ (expr.range)); + if (leftAsNullish !== true) return; + returnRight = true; + } else return; + const right = this.evaluateExpression(expr.right); + if (returnRight) { + if (left.couldHaveSideEffects()) right.setSideEffects(); + return right.setRange(/** @type {Range} */ (expr.range)); + } + + const asBool = right.asBool(); + + if (allowedRight === true && asBool === true) { + return new BasicEvaluatedExpression() + .setRange(/** @type {Range} */ (expr.range)) + .setTruthy(); + } else if (allowedRight === false && asBool === false) { + return new BasicEvaluatedExpression() + .setRange(/** @type {Range} */ (expr.range)) + .setFalsy(); + } + }); + + /** + * In simple logical cases, we can use valueAsExpression to assist us in evaluating the expression on + * either side of a [BinaryExpression](https://github.com/estree/estree/blob/master/es5.md#binaryexpression). + * This supports scenarios in webpack like conditionally `import()`'ing modules based on some simple evaluation: + * + * ```js + * if (1 === 3) { + * import("./moduleA"); // webpack will auto evaluate this and not import the modules + * } + * ``` + * + * Additional scenarios include evaluation of strings inside of dynamic import statements: + * + * ```js + * const foo = "foo"; + * const bar = "bar"; + * + * import("./" + foo + bar); // webpack will auto evaluate this into import("./foobar") + * ``` + * @param {boolean | number | bigint | string} value the value to convert to an expression + * @param {BinaryExpression | UnaryExpression} expr the expression being evaluated + * @param {boolean} sideEffects whether the expression has side effects + * @returns {BasicEvaluatedExpression | undefined} the evaluated expression + * @example + * + * ```js + * const binaryExpr = new BinaryExpression("+", + * { type: "Literal", value: 2 }, + * { type: "Literal", value: 3 } + * ); + * + * const leftValue = 2; + * const rightValue = 3; + * + * const leftExpr = valueAsExpression(leftValue, binaryExpr.left, false); + * const rightExpr = valueAsExpression(rightValue, binaryExpr.right, false); + * const result = new BasicEvaluatedExpression() + * .setNumber(leftExpr.number + rightExpr.number) + * .setRange(binaryExpr.range); + * + * console.log(result.number); // Output: 5 + * ``` + */ + const valueAsExpression = (value, expr, sideEffects) => { + switch (typeof value) { + case "boolean": + return new BasicEvaluatedExpression() + .setBoolean(value) + .setSideEffects(sideEffects) + .setRange(/** @type {Range} */ (expr.range)); + case "number": + return new BasicEvaluatedExpression() + .setNumber(value) + .setSideEffects(sideEffects) + .setRange(/** @type {Range} */ (expr.range)); + case "bigint": + return new BasicEvaluatedExpression() + .setBigInt(value) + .setSideEffects(sideEffects) + .setRange(/** @type {Range} */ (expr.range)); + case "string": + return new BasicEvaluatedExpression() + .setString(value) + .setSideEffects(sideEffects) + .setRange(/** @type {Range} */ (expr.range)); + } + }; + + this.hooks.evaluate + .for("BinaryExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {BinaryExpression} */ (_expr); + + /** + * Evaluates a binary expression if and only if it is a const operation (e.g. 1 + 2, "a" + "b", etc.). + * @template T + * @param {(leftOperand: T, rightOperand: T) => boolean | number | bigint | string} operandHandler the handler for the operation (e.g. (a, b) => a + b) + * @returns {BasicEvaluatedExpression | undefined} the evaluated expression + */ + const handleConstOperation = operandHandler => { + const left = this.evaluateExpression(expr.left); + if (!left.isCompileTimeValue()) return; + + const right = this.evaluateExpression(expr.right); + if (!right.isCompileTimeValue()) return; + + const result = operandHandler( + left.asCompileTimeValue(), + right.asCompileTimeValue() + ); + return valueAsExpression( + result, + expr, + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + }; + + /** + * Helper function to determine if two booleans are always different. This is used in `handleStrictEqualityComparison` + * to determine if an expressions boolean or nullish conversion is equal or not. + * @param {boolean} a first boolean to compare + * @param {boolean} b second boolean to compare + * @returns {boolean} true if the two booleans are always different, false otherwise + */ + const isAlwaysDifferent = (a, b) => + (a === true && b === false) || (a === false && b === true); + + /** + * @param {BasicEvaluatedExpression} left left + * @param {BasicEvaluatedExpression} right right + * @param {BasicEvaluatedExpression} res res + * @param {boolean} eql true for "===" and false for "!==" + * @returns {BasicEvaluatedExpression | undefined} result + */ + const handleTemplateStringCompare = (left, right, res, eql) => { + /** + * @param {BasicEvaluatedExpression[]} parts parts + * @returns {string} value + */ + const getPrefix = parts => { + let value = ""; + for (const p of parts) { + const v = p.asString(); + if (v !== undefined) value += v; + else break; + } + return value; + }; + /** + * @param {BasicEvaluatedExpression[]} parts parts + * @returns {string} value + */ + const getSuffix = parts => { + let value = ""; + for (let i = parts.length - 1; i >= 0; i--) { + const v = parts[i].asString(); + if (v !== undefined) value = v + value; + else break; + } + return value; + }; + const leftPrefix = getPrefix( + /** @type {BasicEvaluatedExpression[]} */ (left.parts) + ); + const rightPrefix = getPrefix( + /** @type {BasicEvaluatedExpression[]} */ (right.parts) + ); + const leftSuffix = getSuffix( + /** @type {BasicEvaluatedExpression[]} */ (left.parts) + ); + const rightSuffix = getSuffix( + /** @type {BasicEvaluatedExpression[]} */ (right.parts) + ); + const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length); + const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length); + const prefixMismatch = + lenPrefix > 0 && + leftPrefix.slice(0, lenPrefix) !== rightPrefix.slice(0, lenPrefix); + const suffixMismatch = + lenSuffix > 0 && + leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix); + if (prefixMismatch || suffixMismatch) { + return res + .setBoolean(!eql) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + }; + + /** + * Helper function to handle BinaryExpressions using strict equality comparisons (e.g. "===" and "!=="). + * @param {boolean} eql true for "===" and false for "!==" + * @returns {BasicEvaluatedExpression | undefined} the evaluated expression + */ + const handleStrictEqualityComparison = eql => { + const left = this.evaluateExpression(expr.left); + const right = this.evaluateExpression(expr.right); + const res = new BasicEvaluatedExpression(); + res.setRange(/** @type {Range} */ (expr.range)); + + const leftConst = left.isCompileTimeValue(); + const rightConst = right.isCompileTimeValue(); + + if (leftConst && rightConst) { + return res + .setBoolean( + eql === + (left.asCompileTimeValue() === right.asCompileTimeValue()) + ) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + + if (left.isArray() && right.isArray()) { + return res + .setBoolean(!eql) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + if (left.isTemplateString() && right.isTemplateString()) { + return handleTemplateStringCompare(left, right, res, eql); + } + + const leftPrimitive = left.isPrimitiveType(); + const rightPrimitive = right.isPrimitiveType(); + + if ( + // Primitive !== Object or + // compile-time object types are never equal to something at runtime + (leftPrimitive === false && + (leftConst || rightPrimitive === true)) || + (rightPrimitive === false && + (rightConst || leftPrimitive === true)) || + // Different nullish or boolish status also means not equal + isAlwaysDifferent( + /** @type {boolean} */ (left.asBool()), + /** @type {boolean} */ (right.asBool()) + ) || + isAlwaysDifferent( + /** @type {boolean} */ (left.asNullish()), + /** @type {boolean} */ (right.asNullish()) + ) + ) { + return res + .setBoolean(!eql) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + }; + + /** + * Helper function to handle BinaryExpressions using abstract equality comparisons (e.g. "==" and "!="). + * @param {boolean} eql true for "==" and false for "!=" + * @returns {BasicEvaluatedExpression | undefined} the evaluated expression + */ + const handleAbstractEqualityComparison = eql => { + const left = this.evaluateExpression(expr.left); + const right = this.evaluateExpression(expr.right); + const res = new BasicEvaluatedExpression(); + res.setRange(/** @type {Range} */ (expr.range)); + + const leftConst = left.isCompileTimeValue(); + const rightConst = right.isCompileTimeValue(); + + if (leftConst && rightConst) { + return res + .setBoolean( + eql === + // eslint-disable-next-line eqeqeq + (left.asCompileTimeValue() == right.asCompileTimeValue()) + ) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + + if (left.isArray() && right.isArray()) { + return res + .setBoolean(!eql) + .setSideEffects( + left.couldHaveSideEffects() || right.couldHaveSideEffects() + ); + } + if (left.isTemplateString() && right.isTemplateString()) { + return handleTemplateStringCompare(left, right, res, eql); + } + }; + + if (expr.operator === "+") { + const left = this.evaluateExpression(expr.left); + const right = this.evaluateExpression(expr.right); + const res = new BasicEvaluatedExpression(); + if (left.isString()) { + if (right.isString()) { + res.setString( + /** @type {string} */ (left.string) + + /** @type {string} */ (right.string) + ); + } else if (right.isNumber()) { + res.setString(/** @type {string} */ (left.string) + right.number); + } else if ( + right.isWrapped() && + right.prefix && + right.prefix.isString() + ) { + // "left" + ("prefix" + inner + "postfix") + // => ("leftPrefix" + inner + "postfix") + res.setWrapped( + new BasicEvaluatedExpression() + .setString( + /** @type {string} */ (left.string) + + /** @type {string} */ (right.prefix.string) + ) + .setRange( + joinRanges( + /** @type {Range} */ (left.range), + /** @type {Range} */ (right.prefix.range) + ) + ), + right.postfix, + right.wrappedInnerExpressions + ); + } else if (right.isWrapped()) { + // "left" + ([null] + inner + "postfix") + // => ("left" + inner + "postfix") + res.setWrapped( + left, + right.postfix, + right.wrappedInnerExpressions + ); + } else { + // "left" + expr + // => ("left" + expr + "") + res.setWrapped(left, null, [right]); + } + } else if (left.isNumber()) { + if (right.isString()) { + res.setString(left.number + /** @type {string} */ (right.string)); + } else if (right.isNumber()) { + res.setNumber( + /** @type {number} */ (left.number) + + /** @type {number} */ (right.number) + ); + } else { + return; + } + } else if (left.isBigInt()) { + if (right.isBigInt()) { + res.setBigInt( + /** @type {bigint} */ (left.bigint) + + /** @type {bigint} */ (right.bigint) + ); + } + } else if (left.isWrapped()) { + if (left.postfix && left.postfix.isString() && right.isString()) { + // ("prefix" + inner + "postfix") + "right" + // => ("prefix" + inner + "postfixRight") + res.setWrapped( + left.prefix, + new BasicEvaluatedExpression() + .setString( + /** @type {string} */ (left.postfix.string) + + /** @type {string} */ (right.string) + ) + .setRange( + joinRanges( + /** @type {Range} */ (left.postfix.range), + /** @type {Range} */ (right.range) + ) + ), + left.wrappedInnerExpressions + ); + } else if ( + left.postfix && + left.postfix.isString() && + right.isNumber() + ) { + // ("prefix" + inner + "postfix") + 123 + // => ("prefix" + inner + "postfix123") + res.setWrapped( + left.prefix, + new BasicEvaluatedExpression() + .setString( + /** @type {string} */ (left.postfix.string) + + /** @type {number} */ (right.number) + ) + .setRange( + joinRanges( + /** @type {Range} */ (left.postfix.range), + /** @type {Range} */ (right.range) + ) + ), + left.wrappedInnerExpressions + ); + } else if (right.isString()) { + // ("prefix" + inner + [null]) + "right" + // => ("prefix" + inner + "right") + res.setWrapped(left.prefix, right, left.wrappedInnerExpressions); + } else if (right.isNumber()) { + // ("prefix" + inner + [null]) + 123 + // => ("prefix" + inner + "123") + res.setWrapped( + left.prefix, + new BasicEvaluatedExpression() + .setString(String(right.number)) + .setRange(/** @type {Range} */ (right.range)), + left.wrappedInnerExpressions + ); + } else if (right.isWrapped()) { + // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2") + // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2") + res.setWrapped( + left.prefix, + right.postfix, + left.wrappedInnerExpressions && + right.wrappedInnerExpressions && + left.wrappedInnerExpressions + .concat(left.postfix ? [left.postfix] : []) + .concat(right.prefix ? [right.prefix] : []) + .concat(right.wrappedInnerExpressions) + ); + } else { + // ("prefix" + inner + postfix) + expr + // => ("prefix" + inner + postfix + expr + [null]) + res.setWrapped( + left.prefix, + null, + left.wrappedInnerExpressions && + left.wrappedInnerExpressions.concat( + left.postfix ? [left.postfix, right] : [right] + ) + ); + } + } else if (right.isString()) { + // left + "right" + // => ([null] + left + "right") + res.setWrapped(null, right, [left]); + } else if (right.isWrapped()) { + // left + (prefix + inner + "postfix") + // => ([null] + left + prefix + inner + "postfix") + res.setWrapped( + null, + right.postfix, + right.wrappedInnerExpressions && + (right.prefix ? [left, right.prefix] : [left]).concat( + right.wrappedInnerExpressions + ) + ); + } else { + return; + } + if (left.couldHaveSideEffects() || right.couldHaveSideEffects()) + res.setSideEffects(); + res.setRange(/** @type {Range} */ (expr.range)); + return res; + } else if (expr.operator === "-") { + return handleConstOperation((l, r) => l - r); + } else if (expr.operator === "*") { + return handleConstOperation((l, r) => l * r); + } else if (expr.operator === "/") { + return handleConstOperation((l, r) => l / r); + } else if (expr.operator === "**") { + return handleConstOperation((l, r) => l ** r); + } else if (expr.operator === "===") { + return handleStrictEqualityComparison(true); + } else if (expr.operator === "==") { + return handleAbstractEqualityComparison(true); + } else if (expr.operator === "!==") { + return handleStrictEqualityComparison(false); + } else if (expr.operator === "!=") { + return handleAbstractEqualityComparison(false); + } else if (expr.operator === "&") { + return handleConstOperation((l, r) => l & r); + } else if (expr.operator === "|") { + return handleConstOperation((l, r) => l | r); + } else if (expr.operator === "^") { + return handleConstOperation((l, r) => l ^ r); + } else if (expr.operator === ">>>") { + return handleConstOperation((l, r) => l >>> r); + } else if (expr.operator === ">>") { + return handleConstOperation((l, r) => l >> r); + } else if (expr.operator === "<<") { + return handleConstOperation((l, r) => l << r); + } else if (expr.operator === "<") { + return handleConstOperation((l, r) => l < r); + } else if (expr.operator === ">") { + return handleConstOperation((l, r) => l > r); + } else if (expr.operator === "<=") { + return handleConstOperation((l, r) => l <= r); + } else if (expr.operator === ">=") { + return handleConstOperation((l, r) => l >= r); + } + }); + this.hooks.evaluate + .for("UnaryExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {UnaryExpression} */ (_expr); + + /** + * Evaluates a UnaryExpression if and only if it is a basic const operator (e.g. +a, -a, ~a). + * @template T + * @param {(operand: T) => boolean | number | bigint | string} operandHandler handler for the operand + * @returns {BasicEvaluatedExpression | undefined} evaluated expression + */ + const handleConstOperation = operandHandler => { + const argument = this.evaluateExpression(expr.argument); + if (!argument.isCompileTimeValue()) return; + const result = operandHandler(argument.asCompileTimeValue()); + return valueAsExpression( + result, + expr, + argument.couldHaveSideEffects() + ); + }; + + if (expr.operator === "typeof") { + switch (expr.argument.type) { + case "Identifier": { + const res = this.callHooksForName( + this.hooks.evaluateTypeof, + expr.argument.name, + expr + ); + if (res !== undefined) return res; + break; + } + case "MetaProperty": { + const res = this.callHooksForName( + this.hooks.evaluateTypeof, + /** @type {string} */ (getRootName(expr.argument)), + expr + ); + if (res !== undefined) return res; + break; + } + case "MemberExpression": { + const res = this.callHooksForExpression( + this.hooks.evaluateTypeof, + expr.argument, + expr + ); + if (res !== undefined) return res; + break; + } + case "ChainExpression": { + const res = this.callHooksForExpression( + this.hooks.evaluateTypeof, + expr.argument.expression, + expr + ); + if (res !== undefined) return res; + break; + } + case "FunctionExpression": { + return new BasicEvaluatedExpression() + .setString("function") + .setRange(/** @type {Range} */ (expr.range)); + } + } + const arg = this.evaluateExpression(expr.argument); + if (arg.isUnknown()) return; + if (arg.isString()) { + return new BasicEvaluatedExpression() + .setString("string") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isWrapped()) { + return new BasicEvaluatedExpression() + .setString("string") + .setSideEffects() + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isUndefined()) { + return new BasicEvaluatedExpression() + .setString("undefined") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isNumber()) { + return new BasicEvaluatedExpression() + .setString("number") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isBigInt()) { + return new BasicEvaluatedExpression() + .setString("bigint") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isBoolean()) { + return new BasicEvaluatedExpression() + .setString("boolean") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isConstArray() || arg.isRegExp() || arg.isNull()) { + return new BasicEvaluatedExpression() + .setString("object") + .setRange(/** @type {Range} */ (expr.range)); + } + if (arg.isArray()) { + return new BasicEvaluatedExpression() + .setString("object") + .setSideEffects(arg.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + } + } else if (expr.operator === "!") { + const argument = this.evaluateExpression(expr.argument); + const bool = argument.asBool(); + if (typeof bool !== "boolean") return; + return new BasicEvaluatedExpression() + .setBoolean(!bool) + .setSideEffects(argument.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + } else if (expr.operator === "~") { + return handleConstOperation(v => ~v); + } else if (expr.operator === "+") { + // eslint-disable-next-line no-implicit-coercion + return handleConstOperation(v => +v); + } else if (expr.operator === "-") { + return handleConstOperation(v => -v); + } + }); + this.hooks.evaluateTypeof + .for("undefined") + .tap("JavascriptParser", expr => + new BasicEvaluatedExpression() + .setString("undefined") + .setRange(/** @type {Range} */ (expr.range)) + ); + this.hooks.evaluate.for("Identifier").tap("JavascriptParser", expr => { + if (/** @type {Identifier} */ (expr).name === "undefined") { + return new BasicEvaluatedExpression() + .setUndefined() + .setRange(/** @type {Range} */ (expr.range)); + } + }); + /** + * @param {"Identifier" | "ThisExpression" | "MemberExpression"} exprType expression type name + * @param {function(Expression | SpreadElement): GetInfoResult | undefined} getInfo get info + * @returns {void} + */ + const tapEvaluateWithVariableInfo = (exprType, getInfo) => { + /** @type {Expression | undefined} */ + let cachedExpression; + /** @type {GetInfoResult | undefined} */ + let cachedInfo; + this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => { + const expression = + /** @type {Identifier | ThisExpression | MemberExpression} */ (expr); + + const info = getInfo(expression); + if (info !== undefined) { + return this.callHooksForInfoWithFallback( + this.hooks.evaluateIdentifier, + info.name, + name => { + cachedExpression = expression; + cachedInfo = info; + }, + name => { + const hook = this.hooks.evaluateDefinedIdentifier.get(name); + if (hook !== undefined) { + return hook.call(expression); + } + }, + expression + ); + } + }); + this.hooks.evaluate + .for(exprType) + .tap({ name: "JavascriptParser", stage: 100 }, expr => { + const expression = + /** @type {Identifier | ThisExpression | MemberExpression} */ + (expr); + const info = + cachedExpression === expression ? cachedInfo : getInfo(expression); + if (info !== undefined) { + return new BasicEvaluatedExpression() + .setIdentifier( + info.name, + info.rootInfo, + info.getMembers, + info.getMembersOptionals, + info.getMemberRanges + ) + .setRange(/** @type {Range} */ (expression.range)); + } + }); + this.hooks.finish.tap("JavascriptParser", () => { + // Cleanup for GC + cachedExpression = cachedInfo = undefined; + }); + }; + tapEvaluateWithVariableInfo("Identifier", expr => { + const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name); + if ( + typeof info === "string" || + (info instanceof VariableInfo && typeof info.freeName === "string") + ) { + return { + name: info, + rootInfo: info, + getMembers: () => [], + getMembersOptionals: () => [], + getMemberRanges: () => [] + }; + } + }); + tapEvaluateWithVariableInfo("ThisExpression", expr => { + const info = this.getVariableInfo("this"); + if ( + typeof info === "string" || + (info instanceof VariableInfo && typeof info.freeName === "string") + ) { + return { + name: info, + rootInfo: info, + getMembers: () => [], + getMembersOptionals: () => [], + getMemberRanges: () => [] + }; + } + }); + this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => { + const metaProperty = /** @type {MetaProperty} */ (expr); + + return this.callHooksForName( + this.hooks.evaluateIdentifier, + /** @type {string} */ (getRootName(metaProperty)), + metaProperty + ); + }); + tapEvaluateWithVariableInfo("MemberExpression", expr => + this.getMemberExpressionInfo( + /** @type {MemberExpression} */ (expr), + ALLOWED_MEMBER_TYPES_EXPRESSION + ) + ); + + this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => { + const expr = /** @type {CallExpression} */ (_expr); + if ( + expr.callee.type === "MemberExpression" && + expr.callee.property.type === + (expr.callee.computed ? "Literal" : "Identifier") + ) { + // type Super also possible here + const param = this.evaluateExpression( + /** @type {Expression} */ (expr.callee.object) + ); + const property = + expr.callee.property.type === "Literal" + ? `${expr.callee.property.value}` + : expr.callee.property.name; + const hook = this.hooks.evaluateCallExpressionMember.get(property); + if (hook !== undefined) { + return hook.call(expr, param); + } + } else if (expr.callee.type === "Identifier") { + return this.callHooksForName( + this.hooks.evaluateCallExpression, + expr.callee.name, + expr + ); + } + }); + this.hooks.evaluateCallExpressionMember + .for("indexOf") + .tap("JavascriptParser", (expr, param) => { + if (!param.isString()) return; + if (expr.arguments.length === 0) return; + const [arg1, arg2] = expr.arguments; + if (arg1.type === "SpreadElement") return; + const arg1Eval = this.evaluateExpression(arg1); + if (!arg1Eval.isString()) return; + const arg1Value = /** @type {string} */ (arg1Eval.string); + + let result; + if (arg2) { + if (arg2.type === "SpreadElement") return; + const arg2Eval = this.evaluateExpression(arg2); + if (!arg2Eval.isNumber()) return; + result = /** @type {string} */ (param.string).indexOf( + arg1Value, + arg2Eval.number + ); + } else { + result = /** @type {string} */ (param.string).indexOf(arg1Value); + } + return new BasicEvaluatedExpression() + .setNumber(result) + .setSideEffects(param.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + }); + this.hooks.evaluateCallExpressionMember + .for("replace") + .tap("JavascriptParser", (expr, param) => { + if (!param.isString()) return; + if (expr.arguments.length !== 2) return; + if (expr.arguments[0].type === "SpreadElement") return; + if (expr.arguments[1].type === "SpreadElement") return; + const arg1 = this.evaluateExpression(expr.arguments[0]); + const arg2 = this.evaluateExpression(expr.arguments[1]); + if (!arg1.isString() && !arg1.isRegExp()) return; + const arg1Value = /** @type {string | RegExp} */ ( + arg1.regExp || arg1.string + ); + if (!arg2.isString()) return; + const arg2Value = /** @type {string} */ (arg2.string); + return new BasicEvaluatedExpression() + .setString( + /** @type {string} */ (param.string).replace(arg1Value, arg2Value) + ) + .setSideEffects(param.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + }); + for (const fn of ["substr", "substring", "slice"]) { + this.hooks.evaluateCallExpressionMember + .for(fn) + .tap("JavascriptParser", (expr, param) => { + if (!param.isString()) return; + let arg1; + let result; + const str = /** @type {string} */ (param.string); + switch (expr.arguments.length) { + case 1: + if (expr.arguments[0].type === "SpreadElement") return; + arg1 = this.evaluateExpression(expr.arguments[0]); + if (!arg1.isNumber()) return; + result = str[ + /** @type {"substr" | "substring" | "slice"} */ (fn) + ](/** @type {number} */ (arg1.number)); + break; + case 2: { + if (expr.arguments[0].type === "SpreadElement") return; + if (expr.arguments[1].type === "SpreadElement") return; + arg1 = this.evaluateExpression(expr.arguments[0]); + const arg2 = this.evaluateExpression(expr.arguments[1]); + if (!arg1.isNumber()) return; + if (!arg2.isNumber()) return; + result = str[ + /** @type {"substr" | "substring" | "slice"} */ (fn) + ]( + /** @type {number} */ (arg1.number), + /** @type {number} */ (arg2.number) + ); + break; + } + default: + return; + } + return new BasicEvaluatedExpression() + .setString(result) + .setSideEffects(param.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + }); + } + + /** + * @param {"cooked" | "raw"} kind kind of values to get + * @param {TemplateLiteral} templateLiteralExpr TemplateLiteral expr + * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template + */ + const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => { + /** @type {BasicEvaluatedExpression[]} */ + const quasis = []; + /** @type {BasicEvaluatedExpression[]} */ + const parts = []; + + for (let i = 0; i < templateLiteralExpr.quasis.length; i++) { + const quasiExpr = templateLiteralExpr.quasis[i]; + const quasi = quasiExpr.value[kind]; + + if (i > 0) { + const prevExpr = parts[parts.length - 1]; + const expr = this.evaluateExpression( + templateLiteralExpr.expressions[i - 1] + ); + const exprAsString = expr.asString(); + if ( + typeof exprAsString === "string" && + !expr.couldHaveSideEffects() + ) { + // We can merge quasi + expr + quasi when expr + // is a const string + + prevExpr.setString(prevExpr.string + exprAsString + quasi); + prevExpr.setRange([ + /** @type {Range} */ (prevExpr.range)[0], + /** @type {Range} */ (quasiExpr.range)[1] + ]); + // We unset the expression as it doesn't match to a single expression + prevExpr.setExpression(undefined); + continue; + } + parts.push(expr); + } + + const part = new BasicEvaluatedExpression() + .setString(/** @type {string} */ (quasi)) + .setRange(/** @type {Range} */ (quasiExpr.range)) + .setExpression(quasiExpr); + quasis.push(part); + parts.push(part); + } + return { + quasis, + parts + }; + }; + + this.hooks.evaluate + .for("TemplateLiteral") + .tap("JavascriptParser", _node => { + const node = /** @type {TemplateLiteral} */ (_node); + + const { quasis, parts } = getSimplifiedTemplateResult("cooked", node); + if (parts.length === 1) { + return parts[0].setRange(/** @type {Range} */ (node.range)); + } + return new BasicEvaluatedExpression() + .setTemplateString(quasis, parts, "cooked") + .setRange(/** @type {Range} */ (node.range)); + }); + this.hooks.evaluate + .for("TaggedTemplateExpression") + .tap("JavascriptParser", _node => { + const node = /** @type {TaggedTemplateExpression} */ (_node); + const tag = this.evaluateExpression(node.tag); + + if (tag.isIdentifier() && tag.identifier === "String.raw") { + const { quasis, parts } = getSimplifiedTemplateResult( + "raw", + node.quasi + ); + return new BasicEvaluatedExpression() + .setTemplateString(quasis, parts, "raw") + .setRange(/** @type {Range} */ (node.range)); + } + }); + + this.hooks.evaluateCallExpressionMember + .for("concat") + .tap("JavascriptParser", (expr, param) => { + if (!param.isString() && !param.isWrapped()) return; + let stringSuffix = null; + let hasUnknownParams = false; + const innerExpressions = []; + for (let i = expr.arguments.length - 1; i >= 0; i--) { + const arg = expr.arguments[i]; + if (arg.type === "SpreadElement") return; + const argExpr = this.evaluateExpression(arg); + if ( + hasUnknownParams || + (!argExpr.isString() && !argExpr.isNumber()) + ) { + hasUnknownParams = true; + innerExpressions.push(argExpr); + continue; + } + + /** @type {string} */ + const value = argExpr.isString() + ? /** @type {string} */ (argExpr.string) + : String(/** @type {number} */ (argExpr.number)); + + /** @type {string} */ + const newString = value + (stringSuffix ? stringSuffix.string : ""); + const newRange = /** @type {Range} */ ([ + /** @type {Range} */ (argExpr.range)[0], + /** @type {Range} */ ((stringSuffix || argExpr).range)[1] + ]); + stringSuffix = new BasicEvaluatedExpression() + .setString(newString) + .setSideEffects( + (stringSuffix && stringSuffix.couldHaveSideEffects()) || + argExpr.couldHaveSideEffects() + ) + .setRange(newRange); + } + + if (hasUnknownParams) { + const prefix = param.isString() ? param : param.prefix; + const inner = + param.isWrapped() && param.wrappedInnerExpressions + ? param.wrappedInnerExpressions.concat(innerExpressions.reverse()) + : innerExpressions.reverse(); + return new BasicEvaluatedExpression() + .setWrapped(prefix, stringSuffix, inner) + .setRange(/** @type {Range} */ (expr.range)); + } else if (param.isWrapped()) { + const postfix = stringSuffix || param.postfix; + const inner = param.wrappedInnerExpressions + ? param.wrappedInnerExpressions.concat(innerExpressions.reverse()) + : innerExpressions.reverse(); + return new BasicEvaluatedExpression() + .setWrapped(param.prefix, postfix, inner) + .setRange(/** @type {Range} */ (expr.range)); + } + const newString = + /** @type {string} */ (param.string) + + (stringSuffix ? stringSuffix.string : ""); + return new BasicEvaluatedExpression() + .setString(newString) + .setSideEffects( + (stringSuffix && stringSuffix.couldHaveSideEffects()) || + param.couldHaveSideEffects() + ) + .setRange(/** @type {Range} */ (expr.range)); + }); + this.hooks.evaluateCallExpressionMember + .for("split") + .tap("JavascriptParser", (expr, param) => { + if (!param.isString()) return; + if (expr.arguments.length !== 1) return; + if (expr.arguments[0].type === "SpreadElement") return; + let result; + const arg = this.evaluateExpression(expr.arguments[0]); + if (arg.isString()) { + result = + /** @type {string} */ + (param.string).split(/** @type {string} */ (arg.string)); + } else if (arg.isRegExp()) { + result = /** @type {string} */ (param.string).split( + /** @type {RegExp} */ (arg.regExp) + ); + } else { + return; + } + return new BasicEvaluatedExpression() + .setArray(result) + .setSideEffects(param.couldHaveSideEffects()) + .setRange(/** @type {Range} */ (expr.range)); + }); + this.hooks.evaluate + .for("ConditionalExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {ConditionalExpression} */ (_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); + res = new BasicEvaluatedExpression(); + if (consequent.isConditional()) { + res.setOptions( + /** @type {BasicEvaluatedExpression[]} */ (consequent.options) + ); + } else { + res.setOptions([consequent]); + } + if (alternate.isConditional()) { + res.addOptions( + /** @type {BasicEvaluatedExpression[]} */ (alternate.options) + ); + } else { + res.addOptions([alternate]); + } + } else { + res = this.evaluateExpression( + conditionValue ? expr.consequent : expr.alternate + ); + if (condition.couldHaveSideEffects()) res.setSideEffects(); + } + res.setRange(/** @type {Range} */ (expr.range)); + return res; + }); + this.hooks.evaluate + .for("ArrayExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {ArrayExpression} */ (_expr); + + const items = expr.elements.map( + element => + element !== null && + element.type !== "SpreadElement" && + this.evaluateExpression(element) + ); + if (!items.every(Boolean)) return; + return new BasicEvaluatedExpression() + .setItems(/** @type {BasicEvaluatedExpression[]} */ (items)) + .setRange(/** @type {Range} */ (expr.range)); + }); + this.hooks.evaluate + .for("ChainExpression") + .tap("JavascriptParser", _expr => { + const expr = /** @type {ChainExpression} */ (_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 > 0) { + const expression = + /** @type {Expression} */ + (optionalExpressionsStack.pop()); + const evaluated = this.evaluateExpression(expression); + + if (evaluated.asNullish()) { + return evaluated.setRange(/** @type {Range} */ (_expr.range)); + } + } + return this.evaluateExpression(expr.expression); + }); + } + + /** + * @param {Expression} node node + * @returns {Set | undefined} destructured identifiers + */ + destructuringAssignmentPropertiesFor(node) { + if (!this.destructuringAssignmentProperties) return; + return this.destructuringAssignmentProperties.get(node); + } + + /** + * @param {Expression | SpreadElement} expr expression + * @returns {string | VariableInfoInterface | undefined} identifier + */ + getRenameIdentifier(expr) { + const result = this.evaluateExpression(expr); + if (result.isIdentifier()) { + return result.identifier; + } + } + + /** + * @param {ClassExpression | ClassDeclaration} classy a class node + * @returns {void} + */ + walkClass(classy) { + if ( + classy.superClass && + !this.hooks.classExtendsExpression.call(classy.superClass, classy) + ) { + this.walkExpression(classy.superClass); + } + if (classy.body && classy.body.type === "ClassBody") { + const scopeParams = []; + // Add class name in scope for recursive calls + if (classy.id) { + scopeParams.push(classy.id); + } + this.inClassScope(true, scopeParams, () => { + for (const classElement of /** @type {TODO} */ (classy.body.body)) { + if (!this.hooks.classBodyElement.call(classElement, classy)) { + if (classElement.computed && classElement.key) { + this.walkExpression(classElement.key); + } + if (classElement.value) { + if ( + !this.hooks.classBodyValue.call( + classElement.value, + classElement, + classy + ) + ) { + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = false; + this.walkExpression(classElement.value); + this.scope.topLevelScope = wasTopLevel; + } + } else if (classElement.type === "StaticBlock") { + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = false; + this.walkBlockStatement(classElement); + this.scope.topLevelScope = wasTopLevel; + } + } + } + }); + } + } + + /** + * Pre walking iterates the scope for variable declarations + * @param {(Statement | ModuleDeclaration)[]} statements statements + */ + preWalkStatements(statements) { + for (let index = 0, len = statements.length; index < len; index++) { + const statement = statements[index]; + this.preWalkStatement(statement); + } + } + + /** + * Block pre walking iterates the scope for block variable declarations + * @param {(Statement | ModuleDeclaration)[]} statements statements + */ + blockPreWalkStatements(statements) { + for (let index = 0, len = statements.length; index < len; index++) { + const statement = statements[index]; + this.blockPreWalkStatement(statement); + } + } + + /** + * Walking iterates the statements and expressions and processes them + * @param {(Statement | ModuleDeclaration)[]} statements statements + */ + walkStatements(statements) { + for (let index = 0, len = statements.length; index < len; index++) { + const statement = statements[index]; + this.walkStatement(statement); + } + } + + /** + * Walking iterates the statements and expressions and processes them + * @param {Statement | ModuleDeclaration} statement statement + */ + preWalkStatement(statement) { + /** @type {StatementPath} */ + (this.statementPath).push(statement); + if (this.hooks.preStatement.call(statement)) { + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + return; + } + switch (statement.type) { + case "BlockStatement": + this.preWalkBlockStatement(statement); + break; + case "DoWhileStatement": + this.preWalkDoWhileStatement(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 "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; + } + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + } + + /** + * @param {Statement | ModuleDeclaration} statement statement + */ + blockPreWalkStatement(statement) { + /** @type {StatementPath} */ + (this.statementPath).push(statement); + if (this.hooks.blockPreStatement.call(statement)) { + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + return; + } + switch (statement.type) { + case "ImportDeclaration": + this.blockPreWalkImportDeclaration(statement); + break; + case "ExportAllDeclaration": + this.blockPreWalkExportAllDeclaration(statement); + break; + case "ExportDefaultDeclaration": + this.blockPreWalkExportDefaultDeclaration(statement); + break; + case "ExportNamedDeclaration": + this.blockPreWalkExportNamedDeclaration(statement); + break; + case "VariableDeclaration": + this.blockPreWalkVariableDeclaration(statement); + break; + case "ClassDeclaration": + this.blockPreWalkClassDeclaration(statement); + break; + case "ExpressionStatement": + this.blockPreWalkExpressionStatement(statement); + } + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + } + + /** + * @param {Statement | ModuleDeclaration} statement statement + */ + walkStatement(statement) { + /** @type {StatementPath} */ + (this.statementPath).push(statement); + if (this.hooks.statement.call(statement) !== undefined) { + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + 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; + } + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + } + + /** + * Walks a statements that is nested within a parent statement + * and can potentially be a non-block statement. + * This enforces the nested statement to never be in ASI position. + * @param {Statement} statement the nested statement + */ + walkNestedStatement(statement) { + this.prevStatement = undefined; + this.walkStatement(statement); + } + + // Real Statements + /** + * @param {BlockStatement} statement block statement + */ + preWalkBlockStatement(statement) { + this.preWalkStatements(statement.body); + } + + /** + * @param {BlockStatement} statement block statement + */ + walkBlockStatement(statement) { + this.inBlockScope(() => { + const body = statement.body; + const prev = this.prevStatement; + this.blockPreWalkStatements(body); + this.prevStatement = prev; + this.walkStatements(body); + }); + } + + /** + * @param {ExpressionStatement} statement expression statement + */ + walkExpressionStatement(statement) { + this.walkExpression(statement.expression); + } + + /** + * @param {IfStatement} statement if statement + */ + preWalkIfStatement(statement) { + this.preWalkStatement(statement.consequent); + if (statement.alternate) { + this.preWalkStatement(statement.alternate); + } + } + + /** + * @param {IfStatement} statement if statement + */ + walkIfStatement(statement) { + const result = this.hooks.statementIf.call(statement); + if (result === undefined) { + this.walkExpression(statement.test); + this.walkNestedStatement(statement.consequent); + if (statement.alternate) { + this.walkNestedStatement(statement.alternate); + } + } else if (result) { + this.walkNestedStatement(statement.consequent); + } else if (statement.alternate) { + this.walkNestedStatement(statement.alternate); + } + } + + /** + * @param {LabeledStatement} statement with statement + */ + preWalkLabeledStatement(statement) { + this.preWalkStatement(statement.body); + } + + /** + * @param {LabeledStatement} statement with statement + */ + walkLabeledStatement(statement) { + const hook = this.hooks.label.get(statement.label.name); + if (hook !== undefined) { + const result = hook.call(statement); + if (result === true) return; + } + this.walkNestedStatement(statement.body); + } + + /** + * @param {WithStatement} statement with statement + */ + preWalkWithStatement(statement) { + this.preWalkStatement(statement.body); + } + + /** + * @param {WithStatement} statement with statement + */ + walkWithStatement(statement) { + this.walkExpression(statement.object); + this.walkNestedStatement(statement.body); + } + + /** + * @param {SwitchStatement} statement switch statement + */ + preWalkSwitchStatement(statement) { + this.preWalkSwitchCases(statement.cases); + } + + /** + * @param {SwitchStatement} statement switch statement + */ + walkSwitchStatement(statement) { + this.walkExpression(statement.discriminant); + this.walkSwitchCases(statement.cases); + } + + /** + * @param {ReturnStatement | ThrowStatement} statement return or throw statement + */ + walkTerminatingStatement(statement) { + if (statement.argument) this.walkExpression(statement.argument); + } + + /** + * @param {ReturnStatement} statement return statement + */ + walkReturnStatement(statement) { + this.walkTerminatingStatement(statement); + } + + /** + * @param {ThrowStatement} statement return statement + */ + walkThrowStatement(statement) { + this.walkTerminatingStatement(statement); + } + + /** + * @param {TryStatement} statement try statement + */ + preWalkTryStatement(statement) { + this.preWalkStatement(statement.block); + if (statement.handler) this.preWalkCatchClause(statement.handler); + if (statement.finalizer) this.preWalkStatement(statement.finalizer); + } + + /** + * @param {TryStatement} statement try statement + */ + 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); + } + + /** + * @param {WhileStatement} statement while statement + */ + preWalkWhileStatement(statement) { + this.preWalkStatement(statement.body); + } + + /** + * @param {WhileStatement} statement while statement + */ + walkWhileStatement(statement) { + this.walkExpression(statement.test); + this.walkNestedStatement(statement.body); + } + + /** + * @param {DoWhileStatement} statement do while statement + */ + preWalkDoWhileStatement(statement) { + this.preWalkStatement(statement.body); + } + + /** + * @param {DoWhileStatement} statement do while statement + */ + walkDoWhileStatement(statement) { + this.walkNestedStatement(statement.body); + this.walkExpression(statement.test); + } + + /** + * @param {ForStatement} statement for statement + */ + preWalkForStatement(statement) { + if (statement.init && statement.init.type === "VariableDeclaration") { + this.preWalkStatement(statement.init); + } + this.preWalkStatement(statement.body); + } + + /** + * @param {ForStatement} statement for statement + */ + walkForStatement(statement) { + this.inBlockScope(() => { + if (statement.init) { + if (statement.init.type === "VariableDeclaration") { + this.blockPreWalkVariableDeclaration(statement.init); + this.prevStatement = undefined; + this.walkStatement(statement.init); + } else { + this.walkExpression(statement.init); + } + } + if (statement.test) { + this.walkExpression(statement.test); + } + if (statement.update) { + this.walkExpression(statement.update); + } + const body = statement.body; + if (body.type === "BlockStatement") { + // no need to add additional scope + const prev = this.prevStatement; + this.blockPreWalkStatements(body.body); + this.prevStatement = prev; + this.walkStatements(body.body); + } else { + this.walkNestedStatement(body); + } + }); + } + + /** + * @param {ForInStatement} statement for statement + */ + preWalkForInStatement(statement) { + if (statement.left.type === "VariableDeclaration") { + this.preWalkVariableDeclaration(statement.left); + } + this.preWalkStatement(statement.body); + } + + /** + * @param {ForInStatement} statement for statement + */ + walkForInStatement(statement) { + this.inBlockScope(() => { + if (statement.left.type === "VariableDeclaration") { + this.blockPreWalkVariableDeclaration(statement.left); + this.walkVariableDeclaration(statement.left); + } else { + this.walkPattern(statement.left); + } + this.walkExpression(statement.right); + const body = statement.body; + if (body.type === "BlockStatement") { + // no need to add additional scope + const prev = this.prevStatement; + this.blockPreWalkStatements(body.body); + this.prevStatement = prev; + this.walkStatements(body.body); + } else { + this.walkNestedStatement(body); + } + }); + } + + /** + * @param {ForOfStatement} statement statement + */ + preWalkForOfStatement(statement) { + if (statement.await && this.scope.topLevelScope === true) { + this.hooks.topLevelAwait.call(statement); + } + if (statement.left.type === "VariableDeclaration") { + this.preWalkVariableDeclaration(statement.left); + } + this.preWalkStatement(statement.body); + } + + /** + * @param {ForOfStatement} statement for statement + */ + walkForOfStatement(statement) { + this.inBlockScope(() => { + if (statement.left.type === "VariableDeclaration") { + this.blockPreWalkVariableDeclaration(statement.left); + this.walkVariableDeclaration(statement.left); + } else { + this.walkPattern(statement.left); + } + this.walkExpression(statement.right); + const body = statement.body; + if (body.type === "BlockStatement") { + // no need to add additional scope + const prev = this.prevStatement; + this.blockPreWalkStatements(body.body); + this.prevStatement = prev; + this.walkStatements(body.body); + } else { + this.walkNestedStatement(body); + } + }); + } + + /** + * @param {FunctionDeclaration} statement function declaration + */ + preWalkFunctionDeclaration(statement) { + if (statement.id) { + this.defineVariable(statement.id.name); + } + } + + /** + * @param {FunctionDeclaration} statement function declaration + */ + walkFunctionDeclaration(statement) { + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = false; + this.inFunctionScope(true, statement.params, () => { + for (const param of statement.params) { + this.walkPattern(param); + } + if (statement.body.type === "BlockStatement") { + this.detectMode(statement.body.body); + const prev = this.prevStatement; + this.preWalkStatement(statement.body); + this.prevStatement = prev; + this.walkStatement(statement.body); + } else { + this.walkExpression(statement.body); + } + }); + this.scope.topLevelScope = wasTopLevel; + } + + /** + * @param {ExpressionStatement} statement expression statement + */ + blockPreWalkExpressionStatement(statement) { + const expression = statement.expression; + switch (expression.type) { + case "AssignmentExpression": + this.preWalkAssignmentExpression(expression); + } + } + + /** + * @param {AssignmentExpression} expression assignment expression + */ + preWalkAssignmentExpression(expression) { + if ( + expression.left.type !== "ObjectPattern" || + !this.destructuringAssignmentProperties + ) + return; + const keys = this._preWalkObjectPattern(expression.left); + if (!keys) return; + + // check multiple assignments + if (this.destructuringAssignmentProperties.has(expression)) { + const set = + /** @type {Set} */ + (this.destructuringAssignmentProperties.get(expression)); + this.destructuringAssignmentProperties.delete(expression); + for (const id of set) keys.add(id); + } + + this.destructuringAssignmentProperties.set( + expression.right.type === "AwaitExpression" + ? expression.right.argument + : expression.right, + keys + ); + + if (expression.right.type === "AssignmentExpression") { + this.preWalkAssignmentExpression(expression.right); + } + } + + /** + * @param {ImportDeclaration} statement statement + */ + blockPreWalkImportDeclaration(statement) { + const source = /** @type {ImportSource} */ (statement.source.value); + this.hooks.import.call(statement, source); + for (const specifier of statement.specifiers) { + const name = specifier.local.name; + switch (specifier.type) { + case "ImportDefaultSpecifier": + if ( + !this.hooks.importSpecifier.call(statement, source, "default", name) + ) { + this.defineVariable(name); + } + break; + case "ImportSpecifier": + if ( + !this.hooks.importSpecifier.call( + statement, + source, + /** @type {Identifier} */ + (specifier.imported).name || + /** @type {string} */ + ( + /** @type {Literal} */ + (specifier.imported).value + ), + name + ) + ) { + this.defineVariable(name); + } + break; + case "ImportNamespaceSpecifier": + if (!this.hooks.importSpecifier.call(statement, source, null, name)) { + this.defineVariable(name); + } + break; + default: + this.defineVariable(name); + } + } + } + + /** + * @param {Declaration} declaration declaration + * @param {OnIdent} onIdent on ident callback + */ + enterDeclaration(declaration, onIdent) { + switch (declaration.type) { + case "VariableDeclaration": + for (const declarator of declaration.declarations) { + switch (declarator.type) { + case "VariableDeclarator": { + this.enterPattern(declarator.id, onIdent); + break; + } + } + } + break; + case "FunctionDeclaration": + this.enterPattern(declaration.id, onIdent); + break; + case "ClassDeclaration": + this.enterPattern(declaration.id, onIdent); + break; + } + } + + /** + * @param {ExportNamedDeclaration} statement statement + */ + blockPreWalkExportNamedDeclaration(statement) { + let source; + if (statement.source) { + source = /** @type {ImportSource} */ (statement.source.value); + this.hooks.exportImport.call(statement, source); + } else { + this.hooks.export.call(statement); + } + if ( + statement.declaration && + !this.hooks.exportDeclaration.call(statement, statement.declaration) + ) { + const prev = this.prevStatement; + this.preWalkStatement(statement.declaration); + this.prevStatement = prev; + this.blockPreWalkStatement(statement.declaration); + let index = 0; + this.enterDeclaration(statement.declaration, def => { + 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 localName = + /** @type {Identifier} */ (specifier.local).name || + /** @type {string} */ ( + /** @type {Literal} */ (specifier.local).value + ); + const name = + /** @type {Identifier} */ + (specifier.exported).name || + /** @type {string} */ + (/** @type {Literal} */ (specifier.exported).value); + if (source) { + this.hooks.exportImportSpecifier.call( + statement, + source, + localName, + name, + specifierIndex + ); + } else { + this.hooks.exportSpecifier.call( + statement, + localName, + name, + specifierIndex + ); + } + break; + } + } + } + } + } + + /** + * @param {ExportNamedDeclaration} statement the statement + */ + walkExportNamedDeclaration(statement) { + if (statement.declaration) { + this.walkStatement(statement.declaration); + } + } + + /** + * @param {TODO} statement statement + */ + blockPreWalkExportDefaultDeclaration(statement) { + const prev = this.prevStatement; + this.preWalkStatement(statement.declaration); + this.prevStatement = prev; + this.blockPreWalkStatement(statement.declaration); + if ( + /** @type {FunctionDeclaration | ClassDeclaration} */ ( + statement.declaration + ).id && + statement.declaration.type !== "FunctionExpression" && + statement.declaration.type !== "ClassExpression" + ) { + const declaration = + /** @type {FunctionDeclaration | ClassDeclaration} */ + (statement.declaration); + this.hooks.exportSpecifier.call( + statement, + declaration.id.name, + "default", + undefined + ); + } + } + + /** + * @param {ExportDefaultDeclaration} statement statement + */ + walkExportDefaultDeclaration(statement) { + this.hooks.export.call(statement); + if ( + /** @type {FunctionDeclaration | ClassDeclaration} */ ( + statement.declaration + ).id && + statement.declaration.type !== "FunctionExpression" && + statement.declaration.type !== "ClassExpression" + ) { + const declaration = + /** @type {FunctionDeclaration | ClassDeclaration} */ + (statement.declaration); + if (!this.hooks.exportDeclaration.call(statement, declaration)) { + this.walkStatement(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" || + statement.declaration.type === "ClassDeclaration" + ) { + this.walkStatement( + /** @type {FunctionDeclaration | ClassDeclaration} */ + (statement.declaration) + ); + } else { + this.walkExpression(statement.declaration); + } + + if ( + !this.hooks.exportExpression.call( + statement, + /** @type {TODO} */ (statement).declaration + ) + ) { + this.hooks.exportSpecifier.call( + statement, + /** @type {TODO} */ (statement.declaration), + "default", + undefined + ); + } + } + } + + /** + * @param {ExportAllDeclaration} statement statement + */ + blockPreWalkExportAllDeclaration(statement) { + const source = /** @type {ImportSource} */ (statement.source.value); + const name = statement.exported + ? /** @type {Identifier} */ + (statement.exported).name || + /** @type {string} */ + (/** @type {Literal} */ (statement.exported).value) + : null; + this.hooks.exportImport.call(statement, source); + this.hooks.exportImportSpecifier.call(statement, source, null, name, 0); + } + + /** + * @param {VariableDeclaration} statement variable declaration + */ + preWalkVariableDeclaration(statement) { + if (statement.kind !== "var") return; + this._preWalkVariableDeclaration(statement, this.hooks.varDeclarationVar); + } + + /** + * @param {VariableDeclaration} statement variable declaration + */ + blockPreWalkVariableDeclaration(statement) { + if (statement.kind === "var") return; + const hookMap = + statement.kind === "const" + ? this.hooks.varDeclarationConst + : this.hooks.varDeclarationLet; + this._preWalkVariableDeclaration(statement, hookMap); + } + + /** + * @param {VariableDeclaration} statement variable declaration + * @param {TODO} hookMap map of hooks + */ + _preWalkVariableDeclaration(statement, hookMap) { + for (const declarator of statement.declarations) { + switch (declarator.type) { + case "VariableDeclarator": { + this.preWalkVariableDeclarator(declarator); + if (!this.hooks.preDeclarator.call(declarator, statement)) { + 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.defineVariable(name); + } + } + }); + } + break; + } + } + } + } + + /** + * @param {ObjectPattern} objectPattern object pattern + * @returns {Set | undefined} set of names or undefined if not all keys are identifiers + */ + _preWalkObjectPattern(objectPattern) { + /** @type {Set} */ + const props = new Set(); + const properties = objectPattern.properties; + for (let i = 0; i < properties.length; i++) { + const property = properties[i]; + if (property.type !== "Property") return; + if (property.shorthand && property.value.type === "Identifier") { + this.scope.inShorthand = property.value.name; + } + const key = property.key; + if (key.type === "Identifier") { + props.add({ + id: key.name, + range: key.range, + shorthand: this.scope.inShorthand + }); + } else { + const id = this.evaluateExpression(key); + const str = id.asString(); + if (str) { + props.add({ + id: str, + range: key.range, + shorthand: this.scope.inShorthand + }); + } else { + // could not evaluate key + return; + } + } + this.scope.inShorthand = false; + } + + return props; + } + + /** + * @param {VariableDeclarator} declarator variable declarator + */ + preWalkVariableDeclarator(declarator) { + if ( + !declarator.init || + declarator.id.type !== "ObjectPattern" || + !this.destructuringAssignmentProperties + ) + return; + const keys = this._preWalkObjectPattern(declarator.id); + + if (!keys) return; + this.destructuringAssignmentProperties.set( + declarator.init.type === "AwaitExpression" + ? declarator.init.argument + : declarator.init, + keys + ); + + if (declarator.init.type === "AssignmentExpression") { + this.preWalkAssignmentExpression(declarator.init); + } + } + + /** + * @param {VariableDeclaration} statement variable declaration + */ + 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(/** @type {Expression} */ (declarator.init)) + ) { + // renaming with "var a = b;" + const hook = this.hooks.rename.get(renameIdentifier); + if ( + hook === undefined || + !hook.call(/** @type {Expression} */ (declarator.init)) + ) { + this.setVariable(declarator.id.name, renameIdentifier); + } + break; + } + } + if (!this.hooks.declarator.call(declarator, statement)) { + this.walkPattern(declarator.id); + if (declarator.init) this.walkExpression(declarator.init); + } + break; + } + } + } + } + + /** + * @param {ClassDeclaration} statement class declaration + */ + blockPreWalkClassDeclaration(statement) { + if (statement.id) { + this.defineVariable(statement.id.name); + } + } + + /** + * @param {ClassDeclaration} statement class declaration + */ + walkClassDeclaration(statement) { + this.walkClass(statement); + } + + /** + * @param {SwitchCase[]} switchCases switch statement + */ + preWalkSwitchCases(switchCases) { + for (let index = 0, len = switchCases.length; index < len; index++) { + const switchCase = switchCases[index]; + this.preWalkStatements(switchCase.consequent); + } + } + + /** + * @param {SwitchCase[]} switchCases switch statement + */ + walkSwitchCases(switchCases) { + this.inBlockScope(() => { + const len = switchCases.length; + + // we need to pre walk all statements first since we can have invalid code + // import A from "module"; + // switch(1) { + // case 1: + // console.log(A); // should fail at runtime + // case 2: + // const A = 1; + // } + for (let index = 0; index < len; index++) { + const switchCase = switchCases[index]; + + if (switchCase.consequent.length > 0) { + const prev = this.prevStatement; + this.blockPreWalkStatements(switchCase.consequent); + this.prevStatement = prev; + } + } + + for (let index = 0; index < len; index++) { + const switchCase = switchCases[index]; + + if (switchCase.test) { + this.walkExpression(switchCase.test); + } + if (switchCase.consequent.length > 0) { + this.walkStatements(switchCase.consequent); + } + } + }); + } + + /** + * @param {CatchClause} catchClause catch clause + */ + preWalkCatchClause(catchClause) { + this.preWalkStatement(catchClause.body); + } + + /** + * @param {CatchClause} catchClause catch clause + */ + walkCatchClause(catchClause) { + this.inBlockScope(() => { + // Error binding is optional in catch clause since ECMAScript 2019 + if (catchClause.param !== null) { + this.enterPattern(catchClause.param, ident => { + this.defineVariable(ident); + }); + this.walkPattern(catchClause.param); + } + const prev = this.prevStatement; + this.blockPreWalkStatement(catchClause.body); + this.prevStatement = prev; + this.walkStatement(catchClause.body); + }); + } + + /** + * @param {Pattern} pattern pattern + */ + 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; + } + } + + /** + * @param {AssignmentPattern} pattern assignment pattern + */ + walkAssignmentPattern(pattern) { + this.walkExpression(pattern.right); + this.walkPattern(pattern.left); + } + + /** + * @param {ObjectPattern} pattern pattern + */ + walkObjectPattern(pattern) { + for (let i = 0, len = pattern.properties.length; i < len; i++) { + const prop = pattern.properties[i]; + if (prop) { + if (prop.type === "RestElement") { + continue; + } + if (prop.computed) this.walkExpression(prop.key); + if (prop.value) this.walkPattern(prop.value); + } + } + } + + /** + * @param {ArrayPattern} pattern array pattern + */ + walkArrayPattern(pattern) { + for (let i = 0, len = pattern.elements.length; i < len; i++) { + const element = pattern.elements[i]; + if (element) this.walkPattern(element); + } + } + + /** + * @param {RestElement} pattern rest element + */ + walkRestElement(pattern) { + this.walkPattern(pattern.argument); + } + + /** + * @param {(Expression | SpreadElement | null)[]} expressions expressions + */ + walkExpressions(expressions) { + for (const expression of expressions) { + if (expression) { + this.walkExpression(expression); + } + } + } + + /** + * @param {TODO} expression 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 "ChainExpression": + this.walkChainExpression(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 "ImportExpression": + this.walkImportExpression(expression); + break; + case "LogicalExpression": + this.walkLogicalExpression(expression); + break; + case "MetaProperty": + this.walkMetaProperty(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; + } + } + + /** + * @param {AwaitExpression} expression await expression + */ + walkAwaitExpression(expression) { + if (this.scope.topLevelScope === true) + this.hooks.topLevelAwait.call(expression); + this.walkExpression(expression.argument); + } + + /** + * @param {ArrayExpression} expression array expression + */ + walkArrayExpression(expression) { + if (expression.elements) { + this.walkExpressions(expression.elements); + } + } + + /** + * @param {SpreadElement} expression spread element + */ + walkSpreadElement(expression) { + if (expression.argument) { + this.walkExpression(expression.argument); + } + } + + /** + * @param {ObjectExpression} expression object expression + */ + walkObjectExpression(expression) { + for ( + let propIndex = 0, len = expression.properties.length; + propIndex < len; + propIndex++ + ) { + const prop = expression.properties[propIndex]; + this.walkProperty(prop); + } + } + + /** + * @param {Property | SpreadElement} prop property or spread element + */ + walkProperty(prop) { + if (prop.type === "SpreadElement") { + this.walkExpression(prop.argument); + return; + } + if (prop.computed) { + this.walkExpression(prop.key); + } + if (prop.shorthand && prop.value && prop.value.type === "Identifier") { + this.scope.inShorthand = prop.value.name; + this.walkIdentifier(prop.value); + this.scope.inShorthand = false; + } else { + this.walkExpression(prop.value); + } + } + + /** + * @param {FunctionExpression} expression arrow function expression + */ + walkFunctionExpression(expression) { + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = false; + const scopeParams = [...expression.params]; + + // Add function name in scope for recursive calls + if (expression.id) { + scopeParams.push(expression.id); + } + + this.inFunctionScope(true, scopeParams, () => { + for (const param of expression.params) { + this.walkPattern(param); + } + if (expression.body.type === "BlockStatement") { + this.detectMode(expression.body.body); + const prev = this.prevStatement; + this.preWalkStatement(expression.body); + this.prevStatement = prev; + this.walkStatement(expression.body); + } else { + this.walkExpression(expression.body); + } + }); + this.scope.topLevelScope = wasTopLevel; + } + + /** + * @param {ArrowFunctionExpression} expression arrow function expression + */ + walkArrowFunctionExpression(expression) { + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = wasTopLevel ? "arrow" : false; + this.inFunctionScope(false, expression.params, () => { + for (const param of expression.params) { + this.walkPattern(param); + } + if (expression.body.type === "BlockStatement") { + this.detectMode(expression.body.body); + const prev = this.prevStatement; + this.preWalkStatement(expression.body); + this.prevStatement = prev; + this.walkStatement(expression.body); + } else { + this.walkExpression(expression.body); + } + }); + this.scope.topLevelScope = wasTopLevel; + } + + /** + * @param {SequenceExpression} expression the sequence + */ + walkSequenceExpression(expression) { + if (!expression.expressions) return; + // We treat sequence expressions like statements when they are one statement level + // This has some benefits for optimizations that only work on statement level + const currentStatement = + /** @type {StatementPath} */ + (this.statementPath)[ + /** @type {StatementPath} */ + (this.statementPath).length - 1 + ]; + if ( + currentStatement === expression || + (currentStatement.type === "ExpressionStatement" && + currentStatement.expression === expression) + ) { + const old = + /** @type {StatementPathItem} */ + (/** @type {StatementPath} */ (this.statementPath).pop()); + const prev = this.prevStatement; + for (const expr of expression.expressions) { + /** @type {StatementPath} */ + (this.statementPath).push(expr); + this.walkExpression(expr); + this.prevStatement = + /** @type {StatementPath} */ + (this.statementPath).pop(); + } + this.prevStatement = prev; + /** @type {StatementPath} */ + (this.statementPath).push(old); + } else { + this.walkExpressions(expression.expressions); + } + } + + /** + * @param {UpdateExpression} expression the update expression + */ + walkUpdateExpression(expression) { + this.walkExpression(expression.argument); + } + + /** + * @param {UnaryExpression} expression the unary expression + */ + walkUnaryExpression(expression) { + if (expression.operator === "typeof") { + const result = this.callHooksForExpression( + this.hooks.typeof, + expression.argument, + expression + ); + if (result === true) return; + if (expression.argument.type === "ChainExpression") { + const result = this.callHooksForExpression( + this.hooks.typeof, + expression.argument.expression, + expression + ); + if (result === true) return; + } + } + this.walkExpression(expression.argument); + } + + /** + * @param {LogicalExpression | BinaryExpression} expression the expression + */ + walkLeftRightExpression(expression) { + this.walkExpression(expression.left); + this.walkExpression(expression.right); + } + + /** + * @param {BinaryExpression} expression the binary expression + */ + walkBinaryExpression(expression) { + if (this.hooks.binaryExpression.call(expression) === undefined) { + this.walkLeftRightExpression(expression); + } + } + + /** + * @param {LogicalExpression} expression the logical expression + */ + walkLogicalExpression(expression) { + const result = this.hooks.expressionLogicalOperator.call(expression); + if (result === undefined) { + this.walkLeftRightExpression(expression); + } else if (result) { + this.walkExpression(expression.right); + } + } + + /** + * @param {AssignmentExpression} expression assignment expression + */ + walkAssignmentExpression(expression) { + if (expression.left.type === "Identifier") { + const renameIdentifier = this.getRenameIdentifier(expression.right); + if ( + renameIdentifier && + this.callHooksForInfo( + this.hooks.canRename, + renameIdentifier, + expression.right + ) + ) { + // renaming "a = b;" + if ( + !this.callHooksForInfo( + this.hooks.rename, + renameIdentifier, + expression.right + ) + ) { + this.setVariable( + expression.left.name, + typeof renameIdentifier === "string" + ? this.getVariableInfo(renameIdentifier) + : renameIdentifier + ); + } + return; + } + this.walkExpression(expression.right); + this.enterPattern(expression.left, (name, decl) => { + if (!this.callHooksForName(this.hooks.assign, name, expression)) { + this.walkExpression(expression.left); + } + }); + return; + } + if (expression.left.type.endsWith("Pattern")) { + this.walkExpression(expression.right); + this.enterPattern(expression.left, (name, decl) => { + if (!this.callHooksForName(this.hooks.assign, name, expression)) { + this.defineVariable(name); + } + }); + this.walkPattern(expression.left); + } else if (expression.left.type === "MemberExpression") { + const exprName = this.getMemberExpressionInfo( + expression.left, + ALLOWED_MEMBER_TYPES_EXPRESSION + ); + if ( + exprName && + this.callHooksForInfo( + this.hooks.assignMemberChain, + exprName.rootInfo, + expression, + exprName.getMembers() + ) + ) { + return; + } + this.walkExpression(expression.right); + this.walkExpression(expression.left); + } else { + this.walkExpression(expression.right); + this.walkExpression(expression.left); + } + } + + /** + * @param {ConditionalExpression} expression conditional expression + */ + 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); + } + } + + /** + * @param {NewExpression} expression new expression + */ + walkNewExpression(expression) { + const result = this.callHooksForExpression( + this.hooks.new, + expression.callee, + expression + ); + if (result === true) return; + this.walkExpression(expression.callee); + if (expression.arguments) { + this.walkExpressions(expression.arguments); + } + } + + /** + * @param {YieldExpression} expression yield expression + */ + walkYieldExpression(expression) { + if (expression.argument) { + this.walkExpression(expression.argument); + } + } + + /** + * @param {TemplateLiteral} expression template literal + */ + walkTemplateLiteral(expression) { + if (expression.expressions) { + this.walkExpressions(expression.expressions); + } + } + + /** + * @param {TaggedTemplateExpression} expression tagged template expression + */ + walkTaggedTemplateExpression(expression) { + if (expression.tag) { + this.scope.inTaggedTemplateTag = true; + this.walkExpression(expression.tag); + this.scope.inTaggedTemplateTag = false; + } + if (expression.quasi && expression.quasi.expressions) { + this.walkExpressions(expression.quasi.expressions); + } + } + + /** + * @param {ClassExpression} expression the class expression + */ + walkClassExpression(expression) { + this.walkClass(expression); + } + + /** + * @param {ChainExpression} expression expression + */ + walkChainExpression(expression) { + const result = this.hooks.optionalChaining.call(expression); + + if (result === undefined) { + if (expression.expression.type === "CallExpression") { + this.walkCallExpression(expression.expression); + } else { + this.walkMemberExpression(expression.expression); + } + } + } + + /** + * @private + * @param {FunctionExpression | ArrowFunctionExpression} functionExpression function expression + * @param {(Expression | SpreadElement)[]} options options + * @param {Expression | SpreadElement | null} currentThis current this + */ + _walkIIFE(functionExpression, options, currentThis) { + /** + * @param {Expression | SpreadElement} argOrThis arg or this + * @returns {string | VariableInfoInterface | undefined} var info + */ + const getVarInfo = argOrThis => { + const renameIdentifier = this.getRenameIdentifier(argOrThis); + if ( + renameIdentifier && + this.callHooksForInfo( + this.hooks.canRename, + renameIdentifier, + /** @type {Expression} */ + (argOrThis) + ) && + !this.callHooksForInfo( + this.hooks.rename, + renameIdentifier, + /** @type {Expression} */ + (argOrThis) + ) + ) { + return typeof renameIdentifier === "string" + ? /** @type {string} */ (this.getVariableInfo(renameIdentifier)) + : renameIdentifier; + } + this.walkExpression(argOrThis); + }; + const { params, type } = functionExpression; + const arrow = type === "ArrowFunctionExpression"; + const renameThis = currentThis ? getVarInfo(currentThis) : null; + const varInfoForArgs = options.map(getVarInfo); + const wasTopLevel = this.scope.topLevelScope; + this.scope.topLevelScope = wasTopLevel && arrow ? "arrow" : false; + const scopeParams = + /** @type {(Identifier | string)[]} */ + (params.filter((identifier, idx) => !varInfoForArgs[idx])); + + // Add function name in scope for recursive calls + if ( + functionExpression.type === "FunctionExpression" && + functionExpression.id + ) { + scopeParams.push(functionExpression.id.name); + } + + this.inFunctionScope(true, scopeParams, () => { + if (renameThis && !arrow) { + this.setVariable("this", renameThis); + } + for (let i = 0; i < varInfoForArgs.length; i++) { + const varInfo = varInfoForArgs[i]; + if (!varInfo) continue; + if (!params[i] || params[i].type !== "Identifier") continue; + this.setVariable(/** @type {Identifier} */ (params[i]).name, varInfo); + } + if (functionExpression.body.type === "BlockStatement") { + this.detectMode(functionExpression.body.body); + const prev = this.prevStatement; + this.preWalkStatement(functionExpression.body); + this.prevStatement = prev; + this.walkStatement(functionExpression.body); + } else { + this.walkExpression(functionExpression.body); + } + }); + this.scope.topLevelScope = wasTopLevel; + } + + /** + * @param {ImportExpression} expression import expression + */ + walkImportExpression(expression) { + const result = this.hooks.importCall.call(expression); + if (result === true) return; + + this.walkExpression(expression.source); + } + + /** + * @param {CallExpression} expression expression + */ + walkCallExpression(expression) { + /** + * @param {FunctionExpression | ArrowFunctionExpression} fn function + * @returns {boolean} true when simple function + */ + const isSimpleFunction = fn => + fn.params.every(p => p.type === "Identifier"); + if ( + expression.callee.type === "MemberExpression" && + expression.callee.object.type.endsWith("FunctionExpression") && + !expression.callee.computed && + // eslint-disable-next-line no-warning-comments + // @ts-ignore + // TODO check me and handle more cases + (expression.callee.property.name === "call" || + // eslint-disable-next-line no-warning-comments + // @ts-ignore + expression.callee.property.name === "bind") && + expression.arguments.length > 0 && + isSimpleFunction( + /** @type {FunctionExpression | ArrowFunctionExpression} */ + (expression.callee.object) + ) + ) { + // (function(…) { }.call/bind(?, …)) + this._walkIIFE( + /** @type {FunctionExpression | ArrowFunctionExpression} */ + (expression.callee.object), + expression.arguments.slice(1), + expression.arguments[0] + ); + } else if ( + expression.callee.type.endsWith("FunctionExpression") && + isSimpleFunction( + /** @type {FunctionExpression | ArrowFunctionExpression} */ + (expression.callee) + ) + ) { + // (function(…) { }(…)) + this._walkIIFE( + /** @type {FunctionExpression | ArrowFunctionExpression} */ + (expression.callee), + expression.arguments, + null + ); + } else { + if (expression.callee.type === "MemberExpression") { + const exprInfo = this.getMemberExpressionInfo( + expression.callee, + ALLOWED_MEMBER_TYPES_CALL_EXPRESSION + ); + if (exprInfo && exprInfo.type === "call") { + const result = this.callHooksForInfo( + this.hooks.callMemberChainOfCallMemberChain, + exprInfo.rootInfo, + expression, + exprInfo.getCalleeMembers(), + exprInfo.call, + exprInfo.getMembers(), + exprInfo.getMemberRanges() + ); + if (result === true) return; + } + } + const callee = this.evaluateExpression( + /** @type {TODO} */ (expression.callee) + ); + if (callee.isIdentifier()) { + const result1 = this.callHooksForInfo( + this.hooks.callMemberChain, + /** @type {NonNullable} */ + (callee.rootInfo), + expression, + /** @type {NonNullable} */ + (callee.getMembers)(), + callee.getMembersOptionals + ? callee.getMembersOptionals() + : /** @type {NonNullable} */ + (callee.getMembers)().map(() => false), + callee.getMemberRanges ? callee.getMemberRanges() : [] + ); + if (result1 === true) return; + const result2 = this.callHooksForInfo( + this.hooks.call, + /** @type {NonNullable} */ + (callee.identifier), + expression + ); + if (result2 === true) return; + } + + if (expression.callee) { + if (expression.callee.type === "MemberExpression") { + // because of call context we need to walk the call context as expression + this.walkExpression(expression.callee.object); + if (expression.callee.computed === true) + this.walkExpression(expression.callee.property); + } else { + this.walkExpression(expression.callee); + } + } + if (expression.arguments) this.walkExpressions(expression.arguments); + } + } + + /** + * @param {MemberExpression} expression member expression + */ + walkMemberExpression(expression) { + const exprInfo = this.getMemberExpressionInfo( + expression, + ALLOWED_MEMBER_TYPES_ALL + ); + if (exprInfo) { + switch (exprInfo.type) { + case "expression": { + const result1 = this.callHooksForInfo( + this.hooks.expression, + exprInfo.name, + expression + ); + if (result1 === true) return; + const members = exprInfo.getMembers(); + const membersOptionals = exprInfo.getMembersOptionals(); + const memberRanges = exprInfo.getMemberRanges(); + const result2 = this.callHooksForInfo( + this.hooks.expressionMemberChain, + exprInfo.rootInfo, + expression, + members, + membersOptionals, + memberRanges + ); + if (result2 === true) return; + this.walkMemberExpressionWithExpressionName( + expression, + exprInfo.name, + exprInfo.rootInfo, + members.slice(), + () => + this.callHooksForInfo( + this.hooks.unhandledExpressionMemberChain, + exprInfo.rootInfo, + expression, + members + ) + ); + return; + } + case "call": { + const result = this.callHooksForInfo( + this.hooks.memberChainOfCallMemberChain, + exprInfo.rootInfo, + expression, + exprInfo.getCalleeMembers(), + exprInfo.call, + exprInfo.getMembers(), + exprInfo.getMemberRanges() + ); + if (result === true) return; + // Fast skip over the member chain as we already called memberChainOfCallMemberChain + // and call computed property are literals anyway + this.walkExpression(exprInfo.call); + return; + } + } + } + this.walkExpression(expression.object); + if (expression.computed === true) this.walkExpression(expression.property); + } + + /** + * @param {TODO} expression member expression + * @param {string} name name + * @param {string | VariableInfo} rootInfo root info + * @param {string[]} members members + * @param {TODO} onUnhandled on unhandled callback + */ + walkMemberExpressionWithExpressionName( + expression, + name, + rootInfo, + members, + onUnhandled + ) { + if (expression.object.type === "MemberExpression") { + // optimize the case where expression.object is a MemberExpression too. + // we can keep info here when calling walkMemberExpression directly + const property = + expression.property.name || `${expression.property.value}`; + name = name.slice(0, -property.length - 1); + members.pop(); + const result = this.callHooksForInfo( + this.hooks.expression, + name, + expression.object + ); + if (result === true) return; + this.walkMemberExpressionWithExpressionName( + expression.object, + name, + rootInfo, + members, + onUnhandled + ); + } else if (!onUnhandled || !onUnhandled()) { + this.walkExpression(expression.object); + } + if (expression.computed === true) this.walkExpression(expression.property); + } + + /** + * @param {ThisExpression} expression this expression + */ + walkThisExpression(expression) { + this.callHooksForName(this.hooks.expression, "this", expression); + } + + /** + * @param {Identifier} expression identifier + */ + walkIdentifier(expression) { + this.callHooksForName(this.hooks.expression, expression.name, expression); + } + + /** + * @param {MetaProperty} metaProperty meta property + */ + walkMetaProperty(metaProperty) { + this.hooks.expression.for(getRootName(metaProperty)).call(metaProperty); + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks the should be called + * @param {Expression | Super} expr expression + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForExpression(hookMap, expr, ...args) { + return this.callHooksForExpressionWithFallback( + hookMap, + expr, + undefined, + undefined, + ...args + ); + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks the should be called + * @param {Expression | Super} expr expression info + * @param {(function(string, string | ScopeInfo | VariableInfo, function(): string[]): any) | undefined} fallback callback when variable in not handled by hooks + * @param {(function(string): any) | undefined} defined callback when variable is defined + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForExpressionWithFallback( + hookMap, + expr, + fallback, + defined, + ...args + ) { + const exprName = this.getMemberExpressionInfo( + expr, + ALLOWED_MEMBER_TYPES_EXPRESSION + ); + if (exprName !== undefined) { + const members = exprName.getMembers(); + return this.callHooksForInfoWithFallback( + hookMap, + members.length === 0 ? exprName.rootInfo : exprName.name, + fallback && + (name => fallback(name, exprName.rootInfo, exprName.getMembers)), + defined && (() => defined(exprName.name)), + ...args + ); + } + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks the should be called + * @param {string} name key in map + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForName(hookMap, name, ...args) { + return this.callHooksForNameWithFallback( + hookMap, + name, + undefined, + undefined, + ...args + ); + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks that should be called + * @param {ExportedVariableInfo} info variable info + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForInfo(hookMap, info, ...args) { + return this.callHooksForInfoWithFallback( + hookMap, + info, + undefined, + undefined, + ...args + ); + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks the should be called + * @param {ExportedVariableInfo} info variable info + * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks + * @param {(function(string=): any) | undefined} defined callback when variable is defined + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForInfoWithFallback(hookMap, info, fallback, defined, ...args) { + let name; + if (typeof info === "string") { + name = info; + } else { + if (!(info instanceof VariableInfo)) { + if (defined !== undefined) { + return defined(); + } + return; + } + let tagInfo = info.tagInfo; + while (tagInfo !== undefined) { + const hook = hookMap.get(tagInfo.tag); + if (hook !== undefined) { + this.currentTagData = tagInfo.data; + const result = hook.call(...args); + this.currentTagData = undefined; + if (result !== undefined) return result; + } + tagInfo = tagInfo.next; + } + if (info.freeName === true) { + if (defined !== undefined) { + return defined(); + } + return; + } + name = info.freeName; + } + const hook = hookMap.get(name); + if (hook !== undefined) { + const result = hook.call(...args); + if (result !== undefined) return result; + } + if (fallback !== undefined) { + return fallback(/** @type {string} */ (name)); + } + } + + /** + * @template T + * @template R + * @param {HookMap>} hookMap hooks the should be called + * @param {string} name key in map + * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks + * @param {(function(): any) | undefined} defined callback when variable is defined + * @param {AsArray} args args for the hook + * @returns {R | undefined} result of hook + */ + callHooksForNameWithFallback(hookMap, name, fallback, defined, ...args) { + return this.callHooksForInfoWithFallback( + hookMap, + this.getVariableInfo(name), + fallback, + defined, + ...args + ); + } + + /** + * @deprecated + * @param {any} params scope params + * @param {function(): void} fn inner function + * @returns {void} + */ + inScope(params, fn) { + const oldScope = this.scope; + this.scope = { + topLevelScope: oldScope.topLevelScope, + inTry: false, + inShorthand: false, + inTaggedTemplateTag: false, + isStrict: oldScope.isStrict, + isAsmJs: oldScope.isAsmJs, + definitions: oldScope.definitions.createChild() + }; + + this.undefineVariable("this"); + + this.enterPatterns(params, ident => { + this.defineVariable(ident); + }); + + fn(); + + this.scope = oldScope; + } + + /** + * @param {boolean} hasThis true, when this is defined + * @param {Identifier[]} params scope params + * @param {function(): void} fn inner function + * @returns {void} + */ + inClassScope(hasThis, params, fn) { + const oldScope = this.scope; + this.scope = { + topLevelScope: oldScope.topLevelScope, + inTry: false, + inShorthand: false, + inTaggedTemplateTag: false, + isStrict: oldScope.isStrict, + isAsmJs: oldScope.isAsmJs, + definitions: oldScope.definitions.createChild() + }; + + if (hasThis) { + this.undefineVariable("this"); + } + + this.enterPatterns(params, ident => { + this.defineVariable(ident); + }); + + fn(); + + this.scope = oldScope; + } + + /** + * @param {boolean} hasThis true, when this is defined + * @param {(Pattern | string)[]} params scope params + * @param {function(): void} fn inner function + * @returns {void} + */ + inFunctionScope(hasThis, params, fn) { + const oldScope = this.scope; + this.scope = { + topLevelScope: oldScope.topLevelScope, + inTry: false, + inShorthand: false, + inTaggedTemplateTag: false, + isStrict: oldScope.isStrict, + isAsmJs: oldScope.isAsmJs, + definitions: oldScope.definitions.createChild() + }; + + if (hasThis) { + this.undefineVariable("this"); + } + + this.enterPatterns(params, ident => { + this.defineVariable(ident); + }); + + fn(); + + this.scope = oldScope; + } + + /** + * @param {function(): void} fn inner function + * @returns {void} + */ + inBlockScope(fn) { + const oldScope = this.scope; + this.scope = { + topLevelScope: oldScope.topLevelScope, + inTry: oldScope.inTry, + inShorthand: false, + inTaggedTemplateTag: false, + isStrict: oldScope.isStrict, + isAsmJs: oldScope.isAsmJs, + definitions: oldScope.definitions.createChild() + }; + + fn(); + + this.scope = oldScope; + } + + /** + * @param {Array} statements statements + */ + detectMode(statements) { + const isLiteral = + statements.length >= 1 && + statements[0].type === "ExpressionStatement" && + statements[0].expression.type === "Literal"; + if ( + isLiteral && + /** @type {Literal} */ + (/** @type {ExpressionStatement} */ (statements[0]).expression).value === + "use strict" + ) { + this.scope.isStrict = true; + } + if ( + isLiteral && + /** @type {Literal} */ + (/** @type {ExpressionStatement} */ (statements[0]).expression).value === + "use asm" + ) { + this.scope.isAsmJs = true; + } + } + + /** + * @param {(string | Pattern | Property)[]} patterns patterns + * @param {OnIdentString} onIdent on ident callback + */ + enterPatterns(patterns, onIdent) { + for (const pattern of patterns) { + if (typeof pattern !== "string") { + this.enterPattern(pattern, onIdent); + } else if (pattern) { + onIdent(pattern); + } + } + } + + /** + * @param {Pattern | Property} pattern pattern + * @param {OnIdent} onIdent on ident callback + */ + 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; + case "Property": + if (pattern.shorthand && pattern.value.type === "Identifier") { + this.scope.inShorthand = pattern.value.name; + this.enterIdentifier(pattern.value, onIdent); + this.scope.inShorthand = false; + } else { + this.enterPattern(/** @type {Pattern} */ (pattern.value), onIdent); + } + break; + } + } + + /** + * @param {Identifier} pattern identifier pattern + * @param {OnIdent} onIdent callback + */ + enterIdentifier(pattern, onIdent) { + if (!this.callHooksForName(this.hooks.pattern, pattern.name, pattern)) { + onIdent(pattern.name, pattern); + } + } + + /** + * @param {ObjectPattern} pattern object pattern + * @param {OnIdent} onIdent callback + */ + enterObjectPattern(pattern, onIdent) { + for ( + let propIndex = 0, len = pattern.properties.length; + propIndex < len; + propIndex++ + ) { + const prop = pattern.properties[propIndex]; + this.enterPattern(prop, onIdent); + } + } + + /** + * @param {ArrayPattern} pattern object pattern + * @param {OnIdent} onIdent callback + */ + enterArrayPattern(pattern, onIdent) { + for ( + let elementIndex = 0, len = pattern.elements.length; + elementIndex < len; + elementIndex++ + ) { + const element = pattern.elements[elementIndex]; + + if (element) { + this.enterPattern(element, onIdent); + } + } + } + + /** + * @param {RestElement} pattern object pattern + * @param {OnIdent} onIdent callback + */ + enterRestElement(pattern, onIdent) { + this.enterPattern(pattern.argument, onIdent); + } + + /** + * @param {AssignmentPattern} pattern object pattern + * @param {OnIdent} onIdent callback + */ + enterAssignmentPattern(pattern, onIdent) { + this.enterPattern(pattern.left, onIdent); + } + + /** + * @param {Expression | SpreadElement | PrivateIdentifier} expression expression node + * @returns {BasicEvaluatedExpression} evaluation result + */ + evaluateExpression(expression) { + try { + const hook = this.hooks.evaluate.get(expression.type); + if (hook !== undefined) { + const result = hook.call(expression); + if (result !== undefined && result !== null) { + result.setExpression(expression); + return result; + } + } + } catch (err) { + console.warn(err); + // ignore error + } + return new BasicEvaluatedExpression() + .setRange(/** @type {Range} */ (expression.range)) + .setExpression(expression); + } + + /** + * @param {Expression} expression expression + * @returns {string} parsed string + */ + parseString(expression) { + switch (expression.type) { + case "BinaryExpression": + if (expression.operator === "+") { + return ( + this.parseString(/** @type {Expression} */ (expression.left)) + + this.parseString(expression.right) + ); + } + break; + case "Literal": + return String(expression.value); + } + throw new Error( + `${expression.type} is not supported as parameter for require` + ); + } + + /** + * @param {Expression} expression expression + * @returns {{ range?: Range, value: string, code: boolean, conditional: TODO }} result + */ + parseCalculatedString(expression) { + switch (expression.type) { + case "BinaryExpression": + if (expression.operator === "+") { + const left = this.parseCalculatedString( + /** @type {Expression} */ + (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: [ + /** @type {Range} */ + (left.range)[0], + right.range + ? right.range[1] + : /** @type {Range} */ (left.range)[1] + ], + value: left.value + right.value, + code: true, + conditional: false + }; + } + return { + range: [ + /** @type {Range} */ + (left.range)[0], + /** @type {Range} */ + (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: String(expression.value), + code: false, + conditional: false + }; + } + return { + range: undefined, + value: "", + code: true, + conditional: false + }; + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + let ast; + /** @type {import("acorn").Comment[]} */ + let comments; + const semicolons = new Set(); + if (source === null) { + throw new Error("source must not be null"); + } + if (Buffer.isBuffer(source)) { + source = source.toString("utf-8"); + } + if (typeof source === "object") { + ast = /** @type {Program} */ (source); + comments = source.comments; + } else { + comments = []; + ast = JavascriptParser._parse(source, { + sourceType: this.sourceType, + onComment: comments, + onInsertedSemicolon: pos => semicolons.add(pos) + }); + } + + const oldScope = this.scope; + const oldState = this.state; + const oldComments = this.comments; + const oldSemicolons = this.semicolons; + const oldStatementPath = this.statementPath; + const oldPrevStatement = this.prevStatement; + this.scope = { + topLevelScope: true, + inTry: false, + inShorthand: false, + inTaggedTemplateTag: false, + isStrict: false, + isAsmJs: false, + definitions: new StackedMap() + }; + /** @type {ParserState} */ + this.state = state; + this.comments = comments; + this.semicolons = semicolons; + this.statementPath = []; + this.prevStatement = undefined; + if (this.hooks.program.call(ast, comments) === undefined) { + this.destructuringAssignmentProperties = new WeakMap(); + this.detectMode(ast.body); + this.preWalkStatements(ast.body); + this.prevStatement = undefined; + this.blockPreWalkStatements(ast.body); + this.prevStatement = undefined; + this.walkStatements(ast.body); + this.destructuringAssignmentProperties = undefined; + } + this.hooks.finish.call(ast, comments); + this.scope = oldScope; + /** @type {ParserState} */ + this.state = oldState; + this.comments = oldComments; + this.semicolons = oldSemicolons; + this.statementPath = oldStatementPath; + this.prevStatement = oldPrevStatement; + return state; + } + + /** + * @param {string} source source code + * @returns {BasicEvaluatedExpression} evaluation result + */ + evaluate(source) { + const ast = JavascriptParser._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); + } + + /** + * @param {Expression | Declaration | PrivateIdentifier | null | undefined} expr an expression + * @param {number} commentsStartPos source position from which annotation comments are checked + * @returns {boolean} true, when the expression is pure + */ + isPure(expr, commentsStartPos) { + if (!expr) return true; + const result = this.hooks.isPure + .for(expr.type) + .call(expr, commentsStartPos); + if (typeof result === "boolean") return result; + switch (expr.type) { + // TODO handle more cases + case "ClassDeclaration": + case "ClassExpression": { + if (expr.body.type !== "ClassBody") return false; + if ( + expr.superClass && + !this.isPure(expr.superClass, /** @type {Range} */ (expr.range)[0]) + ) { + return false; + } + const items = + /** @type {TODO[]} */ + (expr.body.body); + return items.every(item => { + if ( + item.computed && + item.key && + !this.isPure(item.key, item.range[0]) + ) { + return false; + } + + if ( + item.static && + item.value && + !this.isPure( + item.value, + item.key ? item.key.range[1] : item.range[0] + ) + ) { + return false; + } + + if (item.type === "StaticBlock") { + return false; + } + + if ( + expr.superClass && + item.type === "MethodDefinition" && + item.kind === "constructor" + ) { + return false; + } + + return true; + }); + } + + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ThisExpression": + case "Literal": + case "TemplateLiteral": + case "Identifier": + case "PrivateIdentifier": + return true; + + case "VariableDeclaration": + return expr.declarations.every(decl => + this.isPure(decl.init, /** @type {Range} */ (decl.range)[0]) + ); + + case "ConditionalExpression": + return ( + this.isPure(expr.test, commentsStartPos) && + this.isPure( + expr.consequent, + /** @type {Range} */ (expr.test.range)[1] + ) && + this.isPure( + expr.alternate, + /** @type {Range} */ (expr.consequent.range)[1] + ) + ); + + case "LogicalExpression": + return ( + this.isPure(expr.left, commentsStartPos) && + this.isPure(expr.right, /** @type {Range} */ (expr.left.range)[1]) + ); + + case "SequenceExpression": + return expr.expressions.every(expr => { + const pureFlag = this.isPure(expr, commentsStartPos); + commentsStartPos = /** @type {Range} */ (expr.range)[1]; + return pureFlag; + }); + + case "CallExpression": { + const pureFlag = + /** @type {Range} */ (expr.range)[0] - commentsStartPos > 12 && + this.getComments([ + commentsStartPos, + /** @type {Range} */ (expr.range)[0] + ]).some( + comment => + comment.type === "Block" && + /^\s*(#|@)__PURE__\s*$/.test(comment.value) + ); + if (!pureFlag) return false; + commentsStartPos = /** @type {Range} */ (expr.callee.range)[1]; + return expr.arguments.every(arg => { + if (arg.type === "SpreadElement") return false; + const pureFlag = this.isPure(arg, commentsStartPos); + commentsStartPos = /** @type {Range} */ (arg.range)[1]; + return pureFlag; + }); + } + } + const evaluated = this.evaluateExpression(expr); + return !evaluated.couldHaveSideEffects(); + } + + /** + * @param {Range} range range + * @returns {Comment[]} comments in the range + */ + getComments(range) { + const [rangeStart, rangeEnd] = range; + /** + * @param {Comment} comment comment + * @param {number} needle needle + * @returns {number} compared + */ + const compare = (comment, needle) => + /** @type {Range} */ (comment.range)[0] - needle; + const comments = /** @type {Comment[]} */ (this.comments); + let idx = binarySearchBounds.ge(comments, rangeStart, compare); + /** @type {Comment[]} */ + const commentsInRange = []; + while ( + comments[idx] && + /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd + ) { + commentsInRange.push(comments[idx]); + idx++; + } + + return commentsInRange; + } + + /** + * @param {number} pos source code position + * @returns {boolean} true when a semicolon has been inserted before this position, false if not + */ + isAsiPosition(pos) { + const currentStatement = + /** @type {StatementPath} */ + (this.statementPath)[ + /** @type {StatementPath} */ + (this.statementPath).length - 1 + ]; + if (currentStatement === undefined) throw new Error("Not in statement"); + const range = /** @type {Range} */ (currentStatement.range); + + return ( + // Either asking directly for the end position of the current statement + (range[1] === pos && + /** @type {Set} */ (this.semicolons).has(pos)) || + // Or asking for the start position of the current statement, + // here we have to check multiple things + (range[0] === pos && + // is there a previous statement which might be relevant? + this.prevStatement !== undefined && + // is the end position of the previous statement an ASI position? + /** @type {Set} */ (this.semicolons).has( + /** @type {Range} */ (this.prevStatement.range)[1] + )) + ); + } + + /** + * @param {number} pos source code position + * @returns {void} + */ + setAsiPosition(pos) { + /** @type {Set} */ (this.semicolons).add(pos); + } + + /** + * @param {number} pos source code position + * @returns {void} + */ + unsetAsiPosition(pos) { + /** @type {Set} */ (this.semicolons).delete(pos); + } + + /** + * @param {Expression} expr expression + * @returns {boolean} true, when the expression is a statement level expression + */ + isStatementLevelExpression(expr) { + const currentStatement = + /** @type {StatementPath} */ + (this.statementPath)[ + /** @type {StatementPath} */ + (this.statementPath).length - 1 + ]; + return ( + expr === currentStatement || + (currentStatement.type === "ExpressionStatement" && + currentStatement.expression === expr) + ); + } + + /** + * @param {string} name name + * @param {symbol} tag tag info + * @returns {TODO} tag data + */ + getTagData(name, tag) { + const info = this.scope.definitions.get(name); + if (info instanceof VariableInfo) { + let tagInfo = info.tagInfo; + while (tagInfo !== undefined) { + if (tagInfo.tag === tag) return tagInfo.data; + tagInfo = tagInfo.next; + } + } + } + + /** + * @param {string} name name + * @param {symbol} tag tag info + * @param {TODO=} data data + */ + tagVariable(name, tag, data) { + const oldInfo = this.scope.definitions.get(name); + /** @type {VariableInfo} */ + let newInfo; + if (oldInfo === undefined) { + newInfo = new VariableInfo(this.scope, name, { + tag, + data, + next: undefined + }); + } else if (oldInfo instanceof VariableInfo) { + newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, { + tag, + data, + next: oldInfo.tagInfo + }); + } else { + newInfo = new VariableInfo(oldInfo, true, { + tag, + data, + next: undefined + }); + } + this.scope.definitions.set(name, newInfo); + } + + /** + * @param {string} name variable name + */ + defineVariable(name) { + const oldInfo = this.scope.definitions.get(name); + // Don't redefine variable in same scope to keep existing tags + if (oldInfo instanceof VariableInfo && oldInfo.declaredScope === this.scope) + return; + this.scope.definitions.set(name, this.scope); + } + + /** + * @param {string} name variable name + */ + undefineVariable(name) { + this.scope.definitions.delete(name); + } + + /** + * @param {string} name variable name + * @returns {boolean} true, when variable is defined + */ + isVariableDefined(name) { + const info = this.scope.definitions.get(name); + if (info === undefined) return false; + if (info instanceof VariableInfo) { + return info.freeName === true; + } + return true; + } + + /** + * @param {string} name variable name + * @returns {string | ExportedVariableInfo} info for this variable + */ + getVariableInfo(name) { + const value = this.scope.definitions.get(name); + if (value === undefined) { + return name; + } + return value; + } + + /** + * @param {string} name variable name + * @param {string | ExportedVariableInfo} variableInfo new info for this variable + * @returns {void} + */ + setVariable(name, variableInfo) { + if (typeof variableInfo === "string") { + if (variableInfo === name) { + this.scope.definitions.delete(name); + } else { + this.scope.definitions.set( + name, + new VariableInfo(this.scope, variableInfo, undefined) + ); + } + } else { + this.scope.definitions.set(name, variableInfo); + } + } + + /** + * @param {TagInfo} tagInfo tag info + * @returns {VariableInfo} variable info + */ + evaluatedVariable(tagInfo) { + return new VariableInfo(this.scope, undefined, tagInfo); + } + + /** + * @param {Range} range range of the comment + * @returns {{ options: Record | null, errors: (Error & { comment: Comment })[] | null }} result + */ + parseCommentOptions(range) { + const comments = this.getComments(range); + if (comments.length === 0) { + return EMPTY_COMMENT_OPTIONS; + } + /** @type {Record } */ + const options = {}; + /** @type {(Error & { comment: Comment })[]} */ + const errors = []; + for (const comment of comments) { + const { value } = comment; + if (value && webpackCommentRegExp.test(value)) { + // try compile only if webpack options comment is present + try { + for (let [key, val] of Object.entries( + vm.runInContext( + `(function(){return {${value}};})()`, + this.magicCommentContext + ) + )) { + if (typeof val === "object" && val !== null) { + val = + val.constructor.name === "RegExp" + ? new RegExp(val) + : JSON.parse(JSON.stringify(val)); + } + options[key] = val; + } + } catch (err) { + const newErr = new Error(String(/** @type {Error} */ (err).message)); + newErr.stack = String(/** @type {Error} */ (err).stack); + Object.assign(newErr, { comment }); + errors.push(/** @type {(Error & { comment: Comment })} */ (newErr)); + } + } + } + return { options, errors }; + } + + /** + * @param {Expression | Super} expression a member expression + * @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[], memberRanges: Range[] }} member names (reverse order) and remaining object + */ + extractMemberExpressionChain(expression) { + /** @type {Node} */ + let expr = expression; + const members = []; + const membersOptionals = []; + const memberRanges = []; + while (expr.type === "MemberExpression") { + if (expr.computed) { + if (expr.property.type !== "Literal") break; + members.push(`${expr.property.value}`); // the literal + memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the literal + } else { + if (expr.property.type !== "Identifier") break; + members.push(expr.property.name); // the identifier + memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the identifier + } + membersOptionals.push(expr.optional); + expr = expr.object; + } + + return { + members, + membersOptionals, + memberRanges, + object: expr + }; + } + + /** + * @param {string} varName variable name + * @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that + */ + getFreeInfoFromVariable(varName) { + const info = this.getVariableInfo(varName); + let name; + if (info instanceof VariableInfo) { + name = info.freeName; + if (typeof name !== "string") return; + } else if (typeof info !== "string") { + return; + } else { + name = info; + } + return { info, name }; + } + + /** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} CallExpressionInfo */ + /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} ExpressionExpressionInfo */ + + /** + * @param {Expression | Super} expression a member expression + * @param {number} allowedTypes which types should be returned, presented in bit mask + * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info + */ + getMemberExpressionInfo(expression, allowedTypes) { + const { object, members, membersOptionals, memberRanges } = + this.extractMemberExpressionChain(expression); + switch (object.type) { + case "CallExpression": { + if ((allowedTypes & ALLOWED_MEMBER_TYPES_CALL_EXPRESSION) === 0) return; + let callee = object.callee; + let rootMembers = EMPTY_ARRAY; + if (callee.type === "MemberExpression") { + ({ object: callee, members: rootMembers } = + this.extractMemberExpressionChain(callee)); + } + const rootName = getRootName(callee); + if (!rootName) return; + const result = this.getFreeInfoFromVariable(rootName); + if (!result) return; + const { info: rootInfo, name: resolvedRoot } = result; + const calleeName = objectAndMembersToName(resolvedRoot, rootMembers); + return { + type: "call", + call: object, + calleeName, + rootInfo, + getCalleeMembers: memoize(() => rootMembers.reverse()), + name: objectAndMembersToName(`${calleeName}()`, members), + getMembers: memoize(() => members.reverse()), + getMembersOptionals: memoize(() => membersOptionals.reverse()), + getMemberRanges: memoize(() => memberRanges.reverse()) + }; + } + case "Identifier": + case "MetaProperty": + case "ThisExpression": { + if ((allowedTypes & ALLOWED_MEMBER_TYPES_EXPRESSION) === 0) return; + const rootName = getRootName(object); + if (!rootName) return; + + const result = this.getFreeInfoFromVariable(rootName); + if (!result) return; + const { info: rootInfo, name: resolvedRoot } = result; + return { + type: "expression", + name: objectAndMembersToName(resolvedRoot, members), + rootInfo, + getMembers: memoize(() => members.reverse()), + getMembersOptionals: memoize(() => membersOptionals.reverse()), + getMemberRanges: memoize(() => memberRanges.reverse()) + }; + } + } + } + + /** + * @param {MemberExpression} expression an expression + * @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]} | undefined} name info + */ + getNameForExpression(expression) { + return this.getMemberExpressionInfo( + expression, + ALLOWED_MEMBER_TYPES_EXPRESSION + ); + } + + /** + * @param {string} code source code + * @param {ParseOptions} options parsing options + * @returns {Program} parsed ast + */ + static _parse(code, options) { + const type = options ? options.sourceType : "module"; + /** @type {AcornOptions} */ + const parserOptions = { + ...defaultParserOptions, + allowReturnOutsideFunction: type === "script", + ...options, + sourceType: type === "auto" ? "module" : type + }; + + /** @type {import("acorn").Program | undefined} */ + let ast; + let error; + let threw = false; + try { + ast = parser.parse(code, parserOptions); + } catch (err) { + error = err; + threw = true; + } + + if (threw && type === "auto") { + parserOptions.sourceType = "script"; + if (!("allowReturnOutsideFunction" in options)) { + parserOptions.allowReturnOutsideFunction = true; + } + if (Array.isArray(parserOptions.onComment)) { + parserOptions.onComment.length = 0; + } + try { + ast = parser.parse(code, parserOptions); + threw = false; + } catch (_err) { + // we use the error from first parse try + // so nothing to do here + } + } + + if (threw) { + throw error; + } + + return /** @type {Program} */ (ast); + } +} + +module.exports = JavascriptParser; +module.exports.ALLOWED_MEMBER_TYPES_ALL = ALLOWED_MEMBER_TYPES_ALL; +module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION = + ALLOWED_MEMBER_TYPES_EXPRESSION; +module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = + ALLOWED_MEMBER_TYPES_CALL_EXPRESSION; +module.exports.getImportAttributes = getImportAttributes; +module.exports.VariableInfo = VariableInfo; diff --git a/webpack-lib/lib/javascript/JavascriptParserHelpers.js b/webpack-lib/lib/javascript/JavascriptParserHelpers.js new file mode 100644 index 00000000000..7028c4dd158 --- /dev/null +++ b/webpack-lib/lib/javascript/JavascriptParserHelpers.js @@ -0,0 +1,128 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); +const ConstDependency = require("../dependencies/ConstDependency"); +const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); + +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("estree").SourceLocation} SourceLocation */ +/** @typedef {import("./JavascriptParser")} JavascriptParser */ +/** @typedef {import("./JavascriptParser").Range} Range */ + +/** + * @param {JavascriptParser} parser the parser + * @param {string} value the const value + * @param {(string[] | null)=} runtimeRequirements runtime requirements + * @returns {function(Expression): true} plugin function + */ +module.exports.toConstantDependency = (parser, value, runtimeRequirements) => + function constDependency(expr) { + const dep = new ConstDependency( + value, + /** @type {Range} */ (expr.range), + runtimeRequirements + ); + dep.loc = /** @type {SourceLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + return true; + }; + +/** + * @param {string} value the string value + * @returns {function(Expression): BasicEvaluatedExpression} plugin function + */ +module.exports.evaluateToString = value => + function stringExpression(expr) { + return new BasicEvaluatedExpression() + .setString(value) + .setRange(/** @type {Range} */ (expr.range)); + }; + +/** + * @param {number} value the number value + * @returns {function(Expression): BasicEvaluatedExpression} plugin function + */ +module.exports.evaluateToNumber = value => + function stringExpression(expr) { + return new BasicEvaluatedExpression() + .setNumber(value) + .setRange(/** @type {Range} */ (expr.range)); + }; + +/** + * @param {boolean} value the boolean value + * @returns {function(Expression): BasicEvaluatedExpression} plugin function + */ +module.exports.evaluateToBoolean = value => + function booleanExpression(expr) { + return new BasicEvaluatedExpression() + .setBoolean(value) + .setRange(/** @type {Range} */ (expr.range)); + }; + +/** + * @param {string} identifier identifier + * @param {string} rootInfo rootInfo + * @param {function(): string[]} getMembers getMembers + * @param {boolean|null=} truthy is truthy, null if nullish + * @returns {function(Expression): BasicEvaluatedExpression} callback + */ +module.exports.evaluateToIdentifier = ( + identifier, + rootInfo, + getMembers, + truthy +) => + function identifierExpression(expr) { + const evaluatedExpression = new BasicEvaluatedExpression() + .setIdentifier(identifier, rootInfo, getMembers) + .setSideEffects(false) + .setRange(/** @type {Range} */ (expr.range)); + switch (truthy) { + case true: + evaluatedExpression.setTruthy(); + break; + case null: + evaluatedExpression.setNullish(true); + break; + case false: + evaluatedExpression.setFalsy(); + break; + } + + return evaluatedExpression; + }; + +/** + * @param {JavascriptParser} parser the parser + * @param {string} message the message + * @returns {function(Expression): boolean | undefined} callback to handle unsupported expression + */ +module.exports.expressionIsUnsupported = (parser, message) => + function unsupportedExpression(expr) { + const dep = new ConstDependency( + "(void 0)", + /** @type {Range} */ (expr.range), + null + ); + dep.loc = /** @type {SourceLocation} */ (expr.loc); + parser.state.module.addPresentationalDependency(dep); + if (!parser.state.module) return; + parser.state.module.addWarning( + new UnsupportedFeatureWarning( + message, + /** @type {SourceLocation} */ (expr.loc) + ) + ); + return true; + }; + +module.exports.skipTraversal = () => true; + +module.exports.approve = () => true; diff --git a/webpack-lib/lib/javascript/StartupHelpers.js b/webpack-lib/lib/javascript/StartupHelpers.js new file mode 100644 index 00000000000..f6f759d44bc --- /dev/null +++ b/webpack-lib/lib/javascript/StartupHelpers.js @@ -0,0 +1,177 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const { isSubset } = require("../util/SetHelpers"); +const { getAllChunks } = require("./ChunkHelpers"); + +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../Entrypoint")} Entrypoint */ +/** @typedef {import("../ChunkGraph").EntryModuleWithChunkGroup} EntryModuleWithChunkGroup */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {(string|number)[]} EntryItem */ + +const EXPORT_PREFIX = `var ${RuntimeGlobals.exports} = `; + +/** @typedef {Set} Chunks */ +/** @typedef {ModuleId[]} ModuleIds */ + +/** + * @param {ChunkGraph} chunkGraph chunkGraph + * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate + * @param {EntryModuleWithChunkGroup[]} entries entries + * @param {Chunk} chunk chunk + * @param {boolean} passive true: passive startup with on chunks loaded + * @returns {string} runtime code + */ +module.exports.generateEntryStartup = ( + chunkGraph, + runtimeTemplate, + entries, + chunk, + passive +) => { + /** @type {string[]} */ + const runtime = [ + `var __webpack_exec__ = ${runtimeTemplate.returningFunction( + `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`, + "moduleId" + )}` + ]; + + /** + * @param {ModuleId} id id + * @returns {string} fn to execute + */ + const runModule = id => `__webpack_exec__(${JSON.stringify(id)})`; + /** + * @param {Chunks} chunks chunks + * @param {ModuleIds} moduleIds module ids + * @param {boolean=} final true when final, otherwise false + */ + const outputCombination = (chunks, moduleIds, final) => { + if (chunks.size === 0) { + runtime.push( + `${final ? EXPORT_PREFIX : ""}(${moduleIds.map(runModule).join(", ")});` + ); + } else { + const fn = runtimeTemplate.returningFunction( + moduleIds.map(runModule).join(", ") + ); + runtime.push( + `${final && !passive ? EXPORT_PREFIX : ""}${ + passive + ? RuntimeGlobals.onChunksLoaded + : RuntimeGlobals.startupEntrypoint + }(0, ${JSON.stringify(Array.from(chunks, c => c.id))}, ${fn});` + ); + if (final && passive) { + runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`); + } + } + }; + + /** @type {Chunks | undefined} */ + let currentChunks; + /** @type {ModuleIds | undefined} */ + let currentModuleIds; + + for (const [module, entrypoint] of entries) { + const runtimeChunk = + /** @type {Entrypoint} */ + (entrypoint).getRuntimeChunk(); + const moduleId = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + const chunks = getAllChunks( + /** @type {Entrypoint} */ + (entrypoint), + chunk, + runtimeChunk + ); + if ( + currentChunks && + currentChunks.size === chunks.size && + isSubset(currentChunks, chunks) + ) { + /** @type {ModuleIds} */ + (currentModuleIds).push(moduleId); + } else { + if (currentChunks) { + outputCombination( + currentChunks, + /** @type {ModuleIds} */ (currentModuleIds) + ); + } + currentChunks = chunks; + currentModuleIds = [moduleId]; + } + } + + // output current modules with export prefix + if (currentChunks) { + outputCombination( + currentChunks, + /** @type {ModuleIds} */ + (currentModuleIds), + true + ); + } + runtime.push(""); + return Template.asString(runtime); +}; + +/** + * @param {Hash} hash the hash to update + * @param {ChunkGraph} chunkGraph chunkGraph + * @param {EntryModuleWithChunkGroup[]} entries entries + * @param {Chunk} chunk chunk + * @returns {void} + */ +module.exports.updateHashForEntryStartup = ( + hash, + chunkGraph, + entries, + chunk +) => { + for (const [module, entrypoint] of entries) { + const runtimeChunk = + /** @type {Entrypoint} */ + (entrypoint).getRuntimeChunk(); + const moduleId = chunkGraph.getModuleId(module); + hash.update(`${moduleId}`); + for (const c of getAllChunks( + /** @type {Entrypoint} */ (entrypoint), + chunk, + /** @type {Chunk} */ (runtimeChunk) + )) { + hash.update(`${c.id}`); + } + } +}; + +/** + * @param {Chunk} chunk the chunk + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {function(Chunk, ChunkGraph): boolean} filterFn filter function + * @returns {Set} initially fulfilled chunk ids + */ +module.exports.getInitialChunkIds = (chunk, chunkGraph, filterFn) => { + const initialChunkIds = new Set(chunk.ids); + for (const c of chunk.getAllInitialChunks()) { + if (c === chunk || filterFn(c, chunkGraph)) continue; + for (const id of /** @type {ChunkId[]} */ (c.ids)) { + initialChunkIds.add(id); + } + } + return initialChunkIds; +}; diff --git a/webpack-lib/lib/json/JsonData.js b/webpack-lib/lib/json/JsonData.js new file mode 100644 index 00000000000..cb39dfc011b --- /dev/null +++ b/webpack-lib/lib/json/JsonData.js @@ -0,0 +1,74 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { register } = require("../util/serialization"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ + +class JsonData { + /** + * @param {Buffer | RawJsonData} data JSON data + */ + constructor(data) { + /** @type {Buffer | undefined} */ + this._buffer = undefined; + /** @type {RawJsonData | undefined} */ + this._data = undefined; + if (Buffer.isBuffer(data)) { + this._buffer = data; + } else { + this._data = data; + } + } + + /** + * @returns {RawJsonData|undefined} Raw JSON data + */ + get() { + if (this._data === undefined && this._buffer !== undefined) { + this._data = JSON.parse(this._buffer.toString()); + } + return this._data; + } + + /** + * @param {Hash} hash hash to be updated + * @returns {void} the updated hash + */ + updateHash(hash) { + if (this._buffer === undefined && this._data !== undefined) { + this._buffer = Buffer.from(JSON.stringify(this._data)); + } + + if (this._buffer) hash.update(this._buffer); + } +} + +register(JsonData, "webpack/lib/json/JsonData", null, { + /** + * @param {JsonData} obj JSONData object + * @param {ObjectSerializerContext} context context + */ + serialize(obj, { write }) { + if (obj._buffer === undefined && obj._data !== undefined) { + obj._buffer = Buffer.from(JSON.stringify(obj._data)); + } + write(obj._buffer); + }, + /** + * @param {ObjectDeserializerContext} context context + * @returns {JsonData} deserialized JSON data + */ + deserialize({ read }) { + return new JsonData(read()); + } +}); + +module.exports = JsonData; diff --git a/webpack-lib/lib/json/JsonGenerator.js b/webpack-lib/lib/json/JsonGenerator.js new file mode 100644 index 00000000000..b16d406e7cb --- /dev/null +++ b/webpack-lib/lib/json/JsonGenerator.js @@ -0,0 +1,199 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const ConcatenationScope = require("../ConcatenationScope"); +const { UsageState } = require("../ExportsInfo"); +const Generator = require("../Generator"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../ExportsInfo")} ExportsInfo */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./JsonData")} JsonData */ +/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ + +/** + * @param {RawJsonData} data Raw JSON data + * @returns {undefined|string} stringified data + */ +const stringifySafe = data => { + const stringified = JSON.stringify(data); + if (!stringified) { + return; // Invalid JSON + } + + return stringified.replace(/\u2028|\u2029/g, str => + str === "\u2029" ? "\\u2029" : "\\u2028" + ); // invalid in JavaScript but valid JSON +}; + +/** + * @param {RawJsonData} data Raw JSON data (always an object or array) + * @param {ExportsInfo} exportsInfo exports info + * @param {RuntimeSpec} runtime the runtime + * @returns {RawJsonData} reduced data + */ +const createObjectForExportsInfo = (data, exportsInfo, runtime) => { + if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) + return data; + const isArray = Array.isArray(data); + /** @type {RawJsonData} */ + const reducedData = isArray ? [] : {}; + for (const key of Object.keys(data)) { + const exportInfo = exportsInfo.getReadOnlyExportInfo(key); + const used = exportInfo.getUsed(runtime); + if (used === UsageState.Unused) continue; + + /** @type {RawJsonData} */ + const value = + used === UsageState.OnlyPropertiesUsed && exportInfo.exportsInfo + ? createObjectForExportsInfo(data[key], exportInfo.exportsInfo, runtime) + : data[key]; + + const name = /** @type {string} */ (exportInfo.getUsedName(key, runtime)); + /** @type {Record} */ (reducedData)[name] = value; + } + if (isArray) { + const arrayLengthWhenUsed = + exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !== + UsageState.Unused + ? data.length + : undefined; + + let sizeObjectMinusArray = 0; + for (let i = 0; i < reducedData.length; i++) { + if (reducedData[i] === undefined) { + sizeObjectMinusArray -= 2; + } else { + sizeObjectMinusArray += `${i}`.length + 3; + } + } + if (arrayLengthWhenUsed !== undefined) { + sizeObjectMinusArray += + `${arrayLengthWhenUsed}`.length + + 8 - + (arrayLengthWhenUsed - reducedData.length) * 2; + } + if (sizeObjectMinusArray < 0) + return Object.assign( + arrayLengthWhenUsed === undefined + ? {} + : { length: arrayLengthWhenUsed }, + reducedData + ); + /** @type {number} */ + const generatedLength = + arrayLengthWhenUsed !== undefined + ? Math.max(arrayLengthWhenUsed, reducedData.length) + : reducedData.length; + for (let i = 0; i < generatedLength; i++) { + if (reducedData[i] === undefined) { + reducedData[i] = 0; + } + } + } + return reducedData; +}; + +class JsonGenerator extends Generator { + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return JS_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + /** @type {RawJsonData | undefined} */ + const data = + module.buildInfo && + module.buildInfo.jsonData && + module.buildInfo.jsonData.get(); + if (!data) return 0; + return /** @type {string} */ (stringifySafe(data)).length + 10; + } + + /** + * @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; + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @param {GenerateContext} generateContext context for generate + * @returns {Source | null} generated code + */ + generate( + module, + { + moduleGraph, + runtimeTemplate, + runtimeRequirements, + runtime, + concatenationScope + } + ) { + /** @type {RawJsonData | undefined} */ + const data = + module.buildInfo && + module.buildInfo.jsonData && + module.buildInfo.jsonData.get(); + if (data === undefined) { + return new RawSource( + runtimeTemplate.missingModuleStatement({ + request: module.rawRequest + }) + ); + } + const exportsInfo = moduleGraph.getExportsInfo(module); + /** @type {RawJsonData} */ + const finalJson = + typeof data === "object" && + data && + exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused + ? createObjectForExportsInfo(data, exportsInfo, runtime) + : data; + // Use JSON because JSON.parse() is much faster than JavaScript evaluation + const jsonStr = /** @type {string} */ (stringifySafe(finalJson)); + const jsonExpr = + jsonStr.length > 20 && typeof finalJson === "object" + ? `/*#__PURE__*/JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')` + : jsonStr; + /** @type {string} */ + let content; + if (concatenationScope) { + content = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + } = ${jsonExpr};`; + concatenationScope.registerNamespaceExport( + ConcatenationScope.NAMESPACE_OBJECT_EXPORT + ); + } else { + runtimeRequirements.add(RuntimeGlobals.module); + content = `${module.moduleArgument}.exports = ${jsonExpr};`; + } + return new RawSource(content); + } +} + +module.exports = JsonGenerator; diff --git a/webpack-lib/lib/json/JsonModulesPlugin.js b/webpack-lib/lib/json/JsonModulesPlugin.js new file mode 100644 index 00000000000..a33c0e33e7d --- /dev/null +++ b/webpack-lib/lib/json/JsonModulesPlugin.js @@ -0,0 +1,55 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { JSON_MODULE_TYPE } = require("../ModuleTypeConstants"); +const createSchemaValidation = require("../util/create-schema-validation"); +const JsonGenerator = require("./JsonGenerator"); +const JsonParser = require("./JsonParser"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {Record} RawJsonData */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/JsonModulesPluginParser.check.js"), + () => require("../../schemas/plugins/JsonModulesPluginParser.json"), + { + name: "Json Modules Plugin", + baseDataPath: "parser" + } +); + +const PLUGIN_NAME = "JsonModulesPlugin"; + +/** + * The JsonModulesPlugin is the entrypoint plugin for the json modules feature. + * It adds the json module type to the compiler and registers the json parser and generator. + */ +class JsonModulesPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.createParser + .for(JSON_MODULE_TYPE) + .tap(PLUGIN_NAME, parserOptions => { + validate(parserOptions); + return new JsonParser(parserOptions); + }); + normalModuleFactory.hooks.createGenerator + .for(JSON_MODULE_TYPE) + .tap(PLUGIN_NAME, () => new JsonGenerator()); + } + ); + } +} + +module.exports = JsonModulesPlugin; diff --git a/webpack-lib/lib/json/JsonParser.js b/webpack-lib/lib/json/JsonParser.js new file mode 100644 index 00000000000..93a4fb73489 --- /dev/null +++ b/webpack-lib/lib/json/JsonParser.js @@ -0,0 +1,73 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Parser = require("../Parser"); +const JsonExportsDependency = require("../dependencies/JsonExportsDependency"); +const memoize = require("../util/memoize"); +const JsonData = require("./JsonData"); + +/** @typedef {import("../../declarations/plugins/JsonModulesPluginParser").JsonModulesPluginParserOptions} JsonModulesPluginParserOptions */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ +/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ + +const getParseJson = memoize(() => require("json-parse-even-better-errors")); + +class JsonParser extends Parser { + /** + * @param {JsonModulesPluginParserOptions} options parser options + */ + constructor(options) { + super(); + this.options = options || {}; + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (Buffer.isBuffer(source)) { + source = source.toString("utf-8"); + } + + /** @type {NonNullable} */ + const parseFn = + typeof this.options.parse === "function" + ? this.options.parse + : getParseJson(); + /** @type {Buffer | RawJsonData | undefined} */ + let data; + try { + data = + typeof source === "object" + ? source + : parseFn(source[0] === "\uFEFF" ? source.slice(1) : source); + } catch (err) { + throw new Error( + `Cannot parse JSON: ${/** @type {Error} */ (err).message}` + ); + } + const jsonData = new JsonData(/** @type {Buffer | RawJsonData} */ (data)); + const buildInfo = /** @type {BuildInfo} */ (state.module.buildInfo); + buildInfo.jsonData = jsonData; + buildInfo.strict = true; + const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); + buildMeta.exportsType = "default"; + buildMeta.defaultObject = + typeof data === "object" ? "redirect-warn" : false; + state.module.addDependency( + new JsonExportsDependency(jsonData, this.options.exportsDepth) + ); + return state; + } +} + +module.exports = JsonParser; diff --git a/webpack-lib/lib/library/AbstractLibraryPlugin.js b/webpack-lib/lib/library/AbstractLibraryPlugin.js new file mode 100644 index 00000000000..fbd88a4bb82 --- /dev/null +++ b/webpack-lib/lib/library/AbstractLibraryPlugin.js @@ -0,0 +1,301 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @typedef {import("../util/Hash")} Hash */ + +const COMMON_LIBRARY_NAME_MESSAGE = + "Common configuration options that specific library names are 'output.library[.name]', 'entry.xyz.library[.name]', 'ModuleFederationPlugin.name' and 'ModuleFederationPlugin.library[.name]'."; + +/** + * @template T + * @typedef {object} LibraryContext + * @property {Compilation} compilation + * @property {ChunkGraph} chunkGraph + * @property {T} options + */ + +/** + * @template T + */ +class AbstractLibraryPlugin { + /** + * @param {object} options options + * @param {string} options.pluginName name of the plugin + * @param {LibraryType} options.type used library type + */ + constructor({ pluginName, type }) { + this._pluginName = pluginName; + this._type = type; + this._parseCache = new WeakMap(); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _pluginName } = this; + compiler.hooks.thisCompilation.tap(_pluginName, compilation => { + compilation.hooks.finishModules.tap( + { name: _pluginName, stage: 10 }, + () => { + for (const [ + name, + { + dependencies: deps, + options: { library } + } + ] of compilation.entries) { + const options = this._parseOptionsCached( + library !== undefined + ? library + : compilation.outputOptions.library + ); + if (options !== false) { + const dep = deps[deps.length - 1]; + if (dep) { + const module = compilation.moduleGraph.getModule(dep); + if (module) { + this.finishEntryModule(module, name, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + } + } + } + } + } + ); + + /** + * @param {Chunk} chunk chunk + * @returns {TODO} options for the chunk + */ + const getOptionsForChunk = chunk => { + if (compilation.chunkGraph.getNumberOfEntryModules(chunk) === 0) + return false; + const options = chunk.getEntryOptions(); + const library = options && options.library; + return this._parseOptionsCached( + library !== undefined ? library : compilation.outputOptions.library + ); + }; + + if ( + this.render !== AbstractLibraryPlugin.prototype.render || + this.runtimeRequirements !== + AbstractLibraryPlugin.prototype.runtimeRequirements + ) { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + _pluginName, + (chunk, set, { chunkGraph }) => { + const options = getOptionsForChunk(chunk); + if (options !== false) { + this.runtimeRequirements(chunk, set, { + options, + compilation, + chunkGraph + }); + } + } + ); + } + + const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); + + if (this.render !== AbstractLibraryPlugin.prototype.render) { + hooks.render.tap(_pluginName, (source, renderContext) => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return source; + return this.render(source, renderContext, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + }); + } + + if ( + this.embedInRuntimeBailout !== + AbstractLibraryPlugin.prototype.embedInRuntimeBailout + ) { + hooks.embedInRuntimeBailout.tap( + _pluginName, + (module, renderContext) => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return; + return this.embedInRuntimeBailout(module, renderContext, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + } + ); + } + + if ( + this.strictRuntimeBailout !== + AbstractLibraryPlugin.prototype.strictRuntimeBailout + ) { + hooks.strictRuntimeBailout.tap(_pluginName, renderContext => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return; + return this.strictRuntimeBailout(renderContext, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + }); + } + + if ( + this.renderStartup !== AbstractLibraryPlugin.prototype.renderStartup + ) { + hooks.renderStartup.tap( + _pluginName, + (source, module, renderContext) => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return source; + return this.renderStartup(source, module, renderContext, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + } + ); + } + + hooks.chunkHash.tap(_pluginName, (chunk, hash, context) => { + const options = getOptionsForChunk(chunk); + if (options === false) return; + this.chunkHash(chunk, hash, context, { + options, + compilation, + chunkGraph: compilation.chunkGraph + }); + }); + }); + } + + /** + * @param {LibraryOptions=} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + _parseOptionsCached(library) { + if (!library) return false; + if (library.type !== this._type) return false; + const cacheEntry = this._parseCache.get(library); + if (cacheEntry !== undefined) return cacheEntry; + const result = this.parseOptions(library); + this._parseCache.set(library, result); + return result; + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const AbstractMethodError = require("../AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @param {Module} module the exporting entry module + * @param {string} entryName the name of the entrypoint + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + finishEntryModule(module, entryName, libraryContext) {} + + /** + * @param {Module} module the exporting entry module + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {string | undefined} bailout reason + */ + embedInRuntimeBailout(module, renderContext, libraryContext) { + return undefined; + } + + /** + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {string | undefined} bailout reason + */ + strictRuntimeBailout(renderContext, libraryContext) { + return undefined; + } + + /** + * @param {Chunk} chunk the chunk + * @param {Set} set runtime requirements + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + runtimeRequirements(chunk, set, libraryContext) { + if (this.render !== AbstractLibraryPlugin.prototype.render) + set.add(RuntimeGlobals.returnExportsFromRuntime); + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render(source, renderContext, libraryContext) { + return source; + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup(source, module, renderContext, libraryContext) { + return source; + } + + /** + * @param {Chunk} chunk the chunk + * @param {Hash} hash hash + * @param {ChunkHashContext} chunkHashContext chunk hash context + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + chunkHash(chunk, hash, chunkHashContext, libraryContext) { + const options = this._parseOptionsCached( + libraryContext.compilation.outputOptions.library + ); + hash.update(this._pluginName); + hash.update(JSON.stringify(options)); + } +} + +AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE = COMMON_LIBRARY_NAME_MESSAGE; +module.exports = AbstractLibraryPlugin; diff --git a/webpack-lib/lib/library/AmdLibraryPlugin.js b/webpack-lib/lib/library/AmdLibraryPlugin.js new file mode 100644 index 00000000000..d604c036c77 --- /dev/null +++ b/webpack-lib/lib/library/AmdLibraryPlugin.js @@ -0,0 +1,178 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const ExternalModule = require("../ExternalModule"); +const Template = require("../Template"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} AmdLibraryPluginOptions + * @property {LibraryType} type + * @property {boolean=} requireAsWrapper + */ + +/** + * @typedef {object} AmdLibraryPluginParsed + * @property {string} name + * @property {string} amdContainer + */ + +/** + * @typedef {AmdLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class AmdLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {AmdLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "AmdLibraryPlugin", + type: options.type + }); + this.requireAsWrapper = options.requireAsWrapper; + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name, amdContainer } = library; + if (this.requireAsWrapper) { + if (name) { + throw new Error( + `AMD library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + } else if (name && typeof name !== "string") { + throw new Error( + `AMD library name must be a simple string or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string} */ (name); + const _amdContainer = /** @type {string} */ (amdContainer); + return { name: _name, amdContainer: _amdContainer }; + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render( + source, + { chunkGraph, chunk, runtimeTemplate }, + { options, compilation } + ) { + const modern = runtimeTemplate.supportsArrowFunction(); + const modules = chunkGraph + .getChunkModules(chunk) + .filter( + m => + m instanceof ExternalModule && + (m.externalType === "amd" || m.externalType === "amd-require") + ); + const externals = /** @type {ExternalModule[]} */ (modules); + const externalsDepsArray = JSON.stringify( + externals.map(m => + typeof m.request === "object" && !Array.isArray(m.request) + ? m.request.amd + : m.request + ) + ); + const externalsArguments = externals + .map( + m => + `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( + `${chunkGraph.getModuleId(m)}` + )}__` + ) + .join(", "); + + const iife = runtimeTemplate.isIIFE(); + const fnStart = + (modern + ? `(${externalsArguments}) => {` + : `function(${externalsArguments}) {`) + + (iife || !chunk.hasRuntime() ? " return " : "\n"); + const fnEnd = iife ? ";\n}" : "\n}"; + + let amdContainerPrefix = ""; + if (options.amdContainer) { + amdContainerPrefix = `${options.amdContainer}.`; + } + + if (this.requireAsWrapper) { + return new ConcatSource( + `${amdContainerPrefix}require(${externalsDepsArray}, ${fnStart}`, + source, + `${fnEnd});` + ); + } else if (options.name) { + const name = compilation.getPath(options.name, { + chunk + }); + + return new ConcatSource( + `${amdContainerPrefix}define(${JSON.stringify( + name + )}, ${externalsDepsArray}, ${fnStart}`, + source, + `${fnEnd});` + ); + } else if (externalsArguments) { + return new ConcatSource( + `${amdContainerPrefix}define(${externalsDepsArray}, ${fnStart}`, + source, + `${fnEnd});` + ); + } + return new ConcatSource( + `${amdContainerPrefix}define(${fnStart}`, + source, + `${fnEnd});` + ); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Hash} hash hash + * @param {ChunkHashContext} chunkHashContext chunk hash context + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { + hash.update("AmdLibraryPlugin"); + if (this.requireAsWrapper) { + hash.update("requireAsWrapper"); + } else if (options.name) { + hash.update("named"); + const name = compilation.getPath(options.name, { + chunk + }); + hash.update(name); + } else if (options.amdContainer) { + hash.update("amdContainer"); + hash.update(options.amdContainer); + } + } +} + +module.exports = AmdLibraryPlugin; diff --git a/webpack-lib/lib/library/AssignLibraryPlugin.js b/webpack-lib/lib/library/AssignLibraryPlugin.js new file mode 100644 index 00000000000..abdcfcf41a8 --- /dev/null +++ b/webpack-lib/lib/library/AssignLibraryPlugin.js @@ -0,0 +1,387 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const propertyAccess = require("../util/propertyAccess"); +const { getEntryRuntime } = require("../util/runtime"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +const KEYWORD_REGEX = + /^(await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|super|switch|static|this|throw|try|true|typeof|var|void|while|with|yield)$/; +const IDENTIFIER_REGEX = + /^[\p{L}\p{Nl}$_][\p{L}\p{Nl}$\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*$/iu; + +/** + * Validates the library name by checking for keywords and valid characters + * @param {string} name name to be validated + * @returns {boolean} true, when valid + */ +const isNameValid = name => + !KEYWORD_REGEX.test(name) && IDENTIFIER_REGEX.test(name); + +/** + * @param {string[]} accessor variable plus properties + * @param {number} existingLength items of accessor that are existing already + * @param {boolean=} initLast if the last property should also be initialized to an object + * @returns {string} code to access the accessor while initializing + */ +const accessWithInit = (accessor, existingLength, initLast = false) => { + // This generates for [a, b, c, d]: + // (((a = typeof a === "undefined" ? {} : a).b = a.b || {}).c = a.b.c || {}).d + const base = accessor[0]; + if (accessor.length === 1 && !initLast) return base; + let current = + existingLength > 0 + ? base + : `(${base} = typeof ${base} === "undefined" ? {} : ${base})`; + + // i is the current position in accessor that has been printed + let i = 1; + + // all properties printed so far (excluding base) + /** @type {string[] | undefined} */ + let propsSoFar; + + // if there is existingLength, print all properties until this position as property access + if (existingLength > i) { + propsSoFar = accessor.slice(1, existingLength); + i = existingLength; + current += propertyAccess(propsSoFar); + } else { + propsSoFar = []; + } + + // all remaining properties (except the last one when initLast is not set) + // should be printed as initializer + const initUntil = initLast ? accessor.length : accessor.length - 1; + for (; i < initUntil; i++) { + const prop = accessor[i]; + propsSoFar.push(prop); + current = `(${current}${propertyAccess([prop])} = ${base}${propertyAccess( + propsSoFar + )} || {})`; + } + + // print the last property as property access if not yet printed + if (i < accessor.length) + current = `${current}${propertyAccess([accessor[accessor.length - 1]])}`; + + return current; +}; + +/** + * @typedef {object} AssignLibraryPluginOptions + * @property {LibraryType} type + * @property {string[] | "global"} prefix name prefix + * @property {string | false} declare declare name as variable + * @property {"error"|"static"|"copy"|"assign"} unnamed behavior for unnamed library name + * @property {"copy"|"assign"=} named behavior for named library name + */ + +/** + * @typedef {object} AssignLibraryPluginParsed + * @property {string | string[]} name + * @property {string | string[] | undefined} export + */ + +/** + * @typedef {AssignLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class AssignLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {AssignLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "AssignLibraryPlugin", + type: options.type + }); + this.prefix = options.prefix; + this.declare = options.declare; + this.unnamed = options.unnamed; + this.named = options.named || "assign"; + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (this.unnamed === "error") { + if (typeof name !== "string" && !Array.isArray(name)) { + throw new Error( + `Library name must be a string or string array. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + } else if (name && typeof name !== "string" && !Array.isArray(name)) { + throw new Error( + `Library name must be a string, string array or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string | string[]} */ (name); + return { + name: _name, + export: library.export + }; + } + + /** + * @param {Module} module the exporting entry module + * @param {string} entryName the name of the entrypoint + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + finishEntryModule( + module, + entryName, + { options, compilation, compilation: { moduleGraph } } + ) { + const runtime = getEntryRuntime(compilation, entryName); + if (options.export) { + const exportsInfo = moduleGraph.getExportInfo( + module, + Array.isArray(options.export) ? options.export[0] : options.export + ); + exportsInfo.setUsed(UsageState.Used, runtime); + exportsInfo.canMangleUse = false; + } else { + const exportsInfo = moduleGraph.getExportsInfo(module); + exportsInfo.setUsedInUnknownWay(runtime); + } + moduleGraph.addExtraReason(module, "used as library export"); + } + + /** + * @param {Compilation} compilation the compilation + * @returns {string[]} the prefix + */ + _getPrefix(compilation) { + return this.prefix === "global" + ? [compilation.runtimeTemplate.globalObject] + : this.prefix; + } + + /** + * @param {AssignLibraryPluginParsed} options the library options + * @param {Chunk} chunk the chunk + * @param {Compilation} compilation the compilation + * @returns {Array} the resolved full name + */ + _getResolvedFullName(options, chunk, compilation) { + const prefix = this._getPrefix(compilation); + const fullName = options.name ? prefix.concat(options.name) : prefix; + return fullName.map(n => + compilation.getPath(n, { + chunk + }) + ); + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render(source, { chunk }, { options, compilation }) { + const fullNameResolved = this._getResolvedFullName( + options, + chunk, + compilation + ); + if (this.declare) { + const base = fullNameResolved[0]; + if (!isNameValid(base)) { + throw new Error( + `Library name base (${base}) must be a valid identifier when using a var declaring library type. Either use a valid identifier (e. g. ${Template.toIdentifier( + base + )}) or use a different library type (e. g. 'type: "global"', which assign a property on the global scope instead of declaring a variable). ${ + AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE + }` + ); + } + source = new ConcatSource(`${this.declare} ${base};\n`, source); + } + return source; + } + + /** + * @param {Module} module the exporting entry module + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {string | undefined} bailout reason + */ + embedInRuntimeBailout( + module, + { chunk, codeGenerationResults }, + { options, compilation } + ) { + const { data } = codeGenerationResults.get(module, chunk.runtime); + const topLevelDeclarations = + (data && data.get("topLevelDeclarations")) || + (module.buildInfo && module.buildInfo.topLevelDeclarations); + if (!topLevelDeclarations) + return "it doesn't tell about top level declarations."; + const fullNameResolved = this._getResolvedFullName( + options, + chunk, + compilation + ); + const base = fullNameResolved[0]; + if (topLevelDeclarations.has(base)) + return `it declares '${base}' on top-level, which conflicts with the current library output.`; + } + + /** + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {string | undefined} bailout reason + */ + strictRuntimeBailout({ chunk }, { options, compilation }) { + if ( + this.declare || + this.prefix === "global" || + this.prefix.length > 0 || + !options.name + ) { + return; + } + return "a global variable is assign and maybe created"; + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup( + source, + module, + { moduleGraph, chunk }, + { options, compilation } + ) { + const fullNameResolved = this._getResolvedFullName( + options, + chunk, + compilation + ); + const staticExports = this.unnamed === "static"; + const exportAccess = options.export + ? propertyAccess( + Array.isArray(options.export) ? options.export : [options.export] + ) + : ""; + const result = new ConcatSource(source); + if (staticExports) { + const exportsInfo = moduleGraph.getExportsInfo(module); + const exportTarget = accessWithInit( + fullNameResolved, + this._getPrefix(compilation).length, + true + ); + for (const exportInfo of exportsInfo.orderedExports) { + if (!exportInfo.provided) continue; + const nameAccess = propertyAccess([exportInfo.name]); + result.add( + `${exportTarget}${nameAccess} = ${RuntimeGlobals.exports}${exportAccess}${nameAccess};\n` + ); + } + result.add( + `Object.defineProperty(${exportTarget}, "__esModule", { value: true });\n` + ); + } else if (options.name ? this.named === "copy" : this.unnamed === "copy") { + result.add( + `var __webpack_export_target__ = ${accessWithInit( + fullNameResolved, + this._getPrefix(compilation).length, + true + )};\n` + ); + /** @type {string} */ + let exports = RuntimeGlobals.exports; + if (exportAccess) { + result.add( + `var __webpack_exports_export__ = ${RuntimeGlobals.exports}${exportAccess};\n` + ); + exports = "__webpack_exports_export__"; + } + result.add( + `for(var __webpack_i__ in ${exports}) __webpack_export_target__[__webpack_i__] = ${exports}[__webpack_i__];\n` + ); + result.add( + `if(${exports}.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });\n` + ); + } else { + result.add( + `${accessWithInit( + fullNameResolved, + this._getPrefix(compilation).length, + false + )} = ${RuntimeGlobals.exports}${exportAccess};\n` + ); + } + return result; + } + + /** + * @param {Chunk} chunk the chunk + * @param {Set} set runtime requirements + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + runtimeRequirements(chunk, set, libraryContext) { + set.add(RuntimeGlobals.exports); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Hash} hash hash + * @param {ChunkHashContext} chunkHashContext chunk hash context + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { + hash.update("AssignLibraryPlugin"); + const fullNameResolved = this._getResolvedFullName( + options, + chunk, + compilation + ); + if (options.name ? this.named === "copy" : this.unnamed === "copy") { + hash.update("copy"); + } + if (this.declare) { + hash.update(this.declare); + } + hash.update(fullNameResolved.join(".")); + if (options.export) { + hash.update(`${options.export}`); + } + } +} + +module.exports = AssignLibraryPlugin; diff --git a/webpack-lib/lib/library/EnableLibraryPlugin.js b/webpack-lib/lib/library/EnableLibraryPlugin.js new file mode 100644 index 00000000000..a772eac8ee8 --- /dev/null +++ b/webpack-lib/lib/library/EnableLibraryPlugin.js @@ -0,0 +1,279 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Compiler")} Compiler */ + +/** @type {WeakMap>} */ +const enabledTypes = new WeakMap(); + +/** + * @param {Compiler} compiler the compiler instance + * @returns {Set} enabled types + */ +const getEnabledTypes = compiler => { + let set = enabledTypes.get(compiler); + if (set === undefined) { + set = new Set(); + enabledTypes.set(compiler, set); + } + return set; +}; + +class EnableLibraryPlugin { + /** + * @param {LibraryType} type library type that should be available + */ + constructor(type) { + this.type = type; + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {LibraryType} type type of library + * @returns {void} + */ + static setEnabled(compiler, type) { + getEnabledTypes(compiler).add(type); + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {LibraryType} type type of library + * @returns {void} + */ + static checkEnabled(compiler, type) { + if (!getEnabledTypes(compiler).has(type)) { + throw new Error( + `Library type "${type}" is not enabled. ` + + "EnableLibraryPlugin need to be used to enable this type of library. " + + 'This usually happens through the "output.enabledLibraryTypes" option. ' + + 'If you are using a function as entry which sets "library", you need to add all potential library types to "output.enabledLibraryTypes". ' + + `These types are enabled: ${Array.from( + getEnabledTypes(compiler) + ).join(", ")}` + ); + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { type } = this; + + // Only enable once + const enabled = getEnabledTypes(compiler); + if (enabled.has(type)) return; + enabled.add(type); + + if (typeof type === "string") { + const enableExportProperty = () => { + const ExportPropertyTemplatePlugin = require("./ExportPropertyLibraryPlugin"); + new ExportPropertyTemplatePlugin({ + type, + nsObjectUsed: !["module", "modern-module"].includes(type), + runtimeExportsUsed: type !== "modern-module" + }).apply(compiler); + }; + switch (type) { + case "var": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: [], + declare: "var", + unnamed: "error" + }).apply(compiler); + break; + } + case "assign-properties": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: [], + declare: false, + unnamed: "error", + named: "copy" + }).apply(compiler); + break; + } + case "assign": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: [], + declare: false, + unnamed: "error" + }).apply(compiler); + break; + } + case "this": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["this"], + declare: false, + unnamed: "copy" + }).apply(compiler); + break; + } + case "window": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["window"], + declare: false, + unnamed: "copy" + }).apply(compiler); + break; + } + case "self": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["self"], + declare: false, + unnamed: "copy" + }).apply(compiler); + break; + } + case "global": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: "global", + declare: false, + unnamed: "copy" + }).apply(compiler); + break; + } + case "commonjs": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["exports"], + declare: false, + unnamed: "copy" + }).apply(compiler); + break; + } + case "commonjs-static": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["exports"], + declare: false, + unnamed: "static" + }).apply(compiler); + break; + } + case "commonjs2": + case "commonjs-module": { + // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 + const AssignLibraryPlugin = require("./AssignLibraryPlugin"); + new AssignLibraryPlugin({ + type, + prefix: ["module", "exports"], + declare: false, + unnamed: "assign" + }).apply(compiler); + break; + } + case "amd": + case "amd-require": { + enableExportProperty(); + const AmdLibraryPlugin = require("./AmdLibraryPlugin"); + new AmdLibraryPlugin({ + type, + requireAsWrapper: type === "amd-require" + }).apply(compiler); + break; + } + case "umd": + case "umd2": { + if (compiler.options.output.iife === false) { + compiler.options.output.iife = true; + + class WarnFalseIifeUmdPlugin { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "WarnFalseIifeUmdPlugin", + compilation => { + const FalseIIFEUmdWarning = require("../FalseIIFEUmdWarning"); + compilation.warnings.push(new FalseIIFEUmdWarning()); + } + ); + } + } + + new WarnFalseIifeUmdPlugin().apply(compiler); + } + enableExportProperty(); + const UmdLibraryPlugin = require("./UmdLibraryPlugin"); + new UmdLibraryPlugin({ + type, + optionalAmdExternalAsGlobal: type === "umd2" + }).apply(compiler); + break; + } + case "system": { + enableExportProperty(); + const SystemLibraryPlugin = require("./SystemLibraryPlugin"); + new SystemLibraryPlugin({ + type + }).apply(compiler); + break; + } + case "jsonp": { + enableExportProperty(); + const JsonpLibraryPlugin = require("./JsonpLibraryPlugin"); + new JsonpLibraryPlugin({ + type + }).apply(compiler); + break; + } + case "module": { + enableExportProperty(); + const ModuleLibraryPlugin = require("./ModuleLibraryPlugin"); + new ModuleLibraryPlugin({ + type + }).apply(compiler); + break; + } + case "modern-module": { + enableExportProperty(); + const ModernModuleLibraryPlugin = require("./ModernModuleLibraryPlugin"); + new ModernModuleLibraryPlugin({ + type + }).apply(compiler); + break; + } + default: + throw new Error(`Unsupported library type ${type}. +Plugins which provide custom library types must call EnableLibraryPlugin.setEnabled(compiler, type) to disable this error.`); + } + } else { + // TODO support plugin instances here + // apply them to the compiler + } + } +} + +module.exports = EnableLibraryPlugin; diff --git a/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js b/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js new file mode 100644 index 00000000000..72b92f724af --- /dev/null +++ b/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js @@ -0,0 +1,122 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const propertyAccess = require("../util/propertyAccess"); +const { getEntryRuntime } = require("../util/runtime"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} ExportPropertyLibraryPluginParsed + * @property {string | string[]} export + */ + +/** + * @typedef {object} ExportPropertyLibraryPluginOptions + * @property {LibraryType} type + * @property {boolean} nsObjectUsed the namespace object is used + * @property {boolean} runtimeExportsUsed runtime exports are used + */ +/** + * @typedef {ExportPropertyLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class ExportPropertyLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {ExportPropertyLibraryPluginOptions} options options + */ + constructor({ type, nsObjectUsed, runtimeExportsUsed }) { + super({ + pluginName: "ExportPropertyLibraryPlugin", + type + }); + this.nsObjectUsed = nsObjectUsed; + this.runtimeExportsUsed = runtimeExportsUsed; + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + return { + export: /** @type {string | string[]} */ (library.export) + }; + } + + /** + * @param {Module} module the exporting entry module + * @param {string} entryName the name of the entrypoint + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + finishEntryModule( + module, + entryName, + { options, compilation, compilation: { moduleGraph } } + ) { + const runtime = getEntryRuntime(compilation, entryName); + if (options.export) { + const exportsInfo = moduleGraph.getExportInfo( + module, + Array.isArray(options.export) ? options.export[0] : options.export + ); + exportsInfo.setUsed(UsageState.Used, runtime); + exportsInfo.canMangleUse = false; + } else { + const exportsInfo = moduleGraph.getExportsInfo(module); + if (this.nsObjectUsed) { + exportsInfo.setUsedInUnknownWay(runtime); + } else { + exportsInfo.setAllKnownExportsUsed(runtime); + } + } + moduleGraph.addExtraReason(module, "used as library export"); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Set} set runtime requirements + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + runtimeRequirements(chunk, set, libraryContext) { + if (this.runtimeExportsUsed) { + set.add(RuntimeGlobals.exports); + } + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup(source, module, renderContext, { options }) { + if (!options.export) return source; + const postfix = `${RuntimeGlobals.exports} = ${ + RuntimeGlobals.exports + }${propertyAccess( + Array.isArray(options.export) ? options.export : [options.export] + )};\n`; + return new ConcatSource(source, postfix); + } +} + +module.exports = ExportPropertyLibraryPlugin; diff --git a/webpack-lib/lib/library/JsonpLibraryPlugin.js b/webpack-lib/lib/library/JsonpLibraryPlugin.js new file mode 100644 index 00000000000..9757874232d --- /dev/null +++ b/webpack-lib/lib/library/JsonpLibraryPlugin.js @@ -0,0 +1,89 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} JsonpLibraryPluginOptions + * @property {LibraryType} type + */ + +/** + * @typedef {object} JsonpLibraryPluginParsed + * @property {string} name + */ + +/** + * @typedef {JsonpLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class JsonpLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {JsonpLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "JsonpLibraryPlugin", + type: options.type + }); + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (typeof name !== "string") { + throw new Error( + `Jsonp library name must be a simple string. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string} */ (name); + return { + name: _name + }; + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render(source, { chunk }, { options, compilation }) { + const name = compilation.getPath(options.name, { + chunk + }); + return new ConcatSource(`${name}(`, source, ")"); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Hash} hash hash + * @param {ChunkHashContext} chunkHashContext chunk hash context + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { + hash.update("JsonpLibraryPlugin"); + hash.update(compilation.getPath(options.name, { chunk })); + } +} + +module.exports = JsonpLibraryPlugin; diff --git a/webpack-lib/lib/library/ModernModuleLibraryPlugin.js b/webpack-lib/lib/library/ModernModuleLibraryPlugin.js new file mode 100644 index 00000000000..23a9510c211 --- /dev/null +++ b/webpack-lib/lib/library/ModernModuleLibraryPlugin.js @@ -0,0 +1,144 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const ConcatenatedModule = require("../optimize/ConcatenatedModule"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} ModernModuleLibraryPluginOptions + * @property {LibraryType} type + */ + +/** + * @typedef {object} ModernModuleLibraryPluginParsed + * @property {string} name + */ + +/** + * @typedef {ModernModuleLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class ModernModuleLibraryPlugin extends AbstractLibraryPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + super.apply(compiler); + + compiler.hooks.compilation.tap("ModernModuleLibraryPlugin", compilation => { + const { exportsDefinitions } = + ConcatenatedModule.getCompilationHooks(compilation); + exportsDefinitions.tap("ModernModuleLibraryPlugin", () => true); + }); + } + + /** + * @param {ModernModuleLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "ModernModuleLibraryPlugin", + type: options.type + }); + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (name) { + throw new Error( + `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string} */ (name); + return { + name: _name + }; + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup( + source, + module, + { moduleGraph, chunk }, + { options, compilation } + ) { + const result = new ConcatSource(source); + const exportsInfo = moduleGraph.getExportsInfo(module); + const definitions = + /** @type {BuildMeta} */ + (module.buildMeta).exportsFinalName; + const exports = []; + + for (const exportInfo of exportsInfo.orderedExports) { + let shouldContinue = false; + const reexport = exportInfo.findTarget(moduleGraph, _m => true); + + if (reexport) { + const exp = moduleGraph.getExportsInfo(reexport.module); + + for (const reexportInfo of exp.orderedExports) { + if ( + !reexportInfo.provided && + reexportInfo.name === /** @type {string[]} */ (reexport.export)[0] + ) { + shouldContinue = true; + } + } + } + + if (shouldContinue) continue; + + const webpackExportsProperty = exportInfo.getUsedName( + exportInfo.name, + chunk.runtime + ); + const finalName = + definitions[ + /** @type {string} */ + (webpackExportsProperty) + ]; + exports.push( + finalName === exportInfo.name + ? finalName + : `${finalName} as ${exportInfo.name}` + ); + } + + if (exports.length > 0) { + result.add(`export { ${exports.join(", ")} };\n`); + } + + return result; + } +} + +module.exports = ModernModuleLibraryPlugin; diff --git a/webpack-lib/lib/library/ModuleLibraryPlugin.js b/webpack-lib/lib/library/ModuleLibraryPlugin.js new file mode 100644 index 00000000000..57afdc3e1a4 --- /dev/null +++ b/webpack-lib/lib/library/ModuleLibraryPlugin.js @@ -0,0 +1,109 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const propertyAccess = require("../util/propertyAccess"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} ModuleLibraryPluginOptions + * @property {LibraryType} type + */ + +/** + * @typedef {object} ModuleLibraryPluginParsed + * @property {string} name + */ + +/** + * @typedef {ModuleLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class ModuleLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {ModuleLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "ModuleLibraryPlugin", + type: options.type + }); + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (name) { + throw new Error( + `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string} */ (name); + return { + name: _name + }; + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup( + source, + module, + { moduleGraph, chunk }, + { options, compilation } + ) { + const result = new ConcatSource(source); + const exportsInfo = moduleGraph.getExportsInfo(module); + const exports = []; + const isAsync = moduleGraph.isAsync(module); + if (isAsync) { + result.add( + `${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n` + ); + } + for (const exportInfo of exportsInfo.orderedExports) { + if (!exportInfo.provided) continue; + const varName = `${RuntimeGlobals.exports}${Template.toIdentifier( + exportInfo.name + )}`; + result.add( + `var ${varName} = ${RuntimeGlobals.exports}${propertyAccess([ + /** @type {string} */ + (exportInfo.getUsedName(exportInfo.name, chunk.runtime)) + ])};\n` + ); + exports.push(`${varName} as ${exportInfo.name}`); + } + if (exports.length > 0) { + result.add(`export { ${exports.join(", ")} };\n`); + } + return result; + } +} + +module.exports = ModuleLibraryPlugin; diff --git a/webpack-lib/lib/library/SystemLibraryPlugin.js b/webpack-lib/lib/library/SystemLibraryPlugin.js new file mode 100644 index 00000000000..701b870d5ea --- /dev/null +++ b/webpack-lib/lib/library/SystemLibraryPlugin.js @@ -0,0 +1,235 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Joel Denning @joeldenning +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const ExternalModule = require("../ExternalModule"); +const Template = require("../Template"); +const propertyAccess = require("../util/propertyAccess"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {object} SystemLibraryPluginOptions + * @property {LibraryType} type + */ + +/** + * @typedef {object} SystemLibraryPluginParsed + * @property {string} name + */ + +/** + * @typedef {SystemLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class SystemLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {SystemLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "SystemLibraryPlugin", + type: options.type + }); + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (name && typeof name !== "string") { + throw new Error( + `System.js library name must be a simple string or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + const _name = /** @type {string} */ (name); + return { + name: _name + }; + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render(source, { chunkGraph, moduleGraph, chunk }, { options, compilation }) { + const modules = chunkGraph + .getChunkModules(chunk) + .filter(m => m instanceof ExternalModule && m.externalType === "system"); + const externals = /** @type {ExternalModule[]} */ (modules); + + // The name this bundle should be registered as with System + const name = options.name + ? `${JSON.stringify(compilation.getPath(options.name, { chunk }))}, ` + : ""; + + // The array of dependencies that are external to webpack and will be provided by System + const systemDependencies = JSON.stringify( + externals.map(m => + typeof m.request === "object" && !Array.isArray(m.request) + ? m.request.amd + : m.request + ) + ); + + // The name of the variable provided by System for exporting + const dynamicExport = "__WEBPACK_DYNAMIC_EXPORT__"; + + // An array of the internal variable names for the webpack externals + const externalWebpackNames = externals.map( + m => + `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( + `${chunkGraph.getModuleId(m)}` + )}__` + ); + + // Declaring variables for the internal variable names for the webpack externals + const externalVarDeclarations = externalWebpackNames + .map(name => `var ${name} = {};`) + .join("\n"); + + // Define __esModule flag on all internal variables and helpers + /** @type {string[]} */ + const externalVarInitialization = []; + + // The system.register format requires an array of setter functions for externals. + const setters = + externalWebpackNames.length === 0 + ? "" + : Template.asString([ + "setters: [", + Template.indent( + externals + .map((module, i) => { + const external = externalWebpackNames[i]; + const exportsInfo = moduleGraph.getExportsInfo(module); + const otherUnused = + exportsInfo.otherExportsInfo.getUsed(chunk.runtime) === + UsageState.Unused; + const instructions = []; + const handledNames = []; + for (const exportInfo of exportsInfo.orderedExports) { + const used = exportInfo.getUsedName( + undefined, + chunk.runtime + ); + if (used) { + if (otherUnused || used !== exportInfo.name) { + instructions.push( + `${external}${propertyAccess([ + used + ])} = module${propertyAccess([exportInfo.name])};` + ); + handledNames.push(exportInfo.name); + } + } else { + handledNames.push(exportInfo.name); + } + } + if (!otherUnused) { + if ( + !Array.isArray(module.request) || + module.request.length === 1 + ) { + externalVarInitialization.push( + `Object.defineProperty(${external}, "__esModule", { value: true });` + ); + } + if (handledNames.length > 0) { + const name = `${external}handledNames`; + externalVarInitialization.push( + `var ${name} = ${JSON.stringify(handledNames)};` + ); + instructions.push( + Template.asString([ + "Object.keys(module).forEach(function(key) {", + Template.indent([ + `if(${name}.indexOf(key) >= 0)`, + Template.indent(`${external}[key] = module[key];`) + ]), + "});" + ]) + ); + } else { + instructions.push( + Template.asString([ + "Object.keys(module).forEach(function(key) {", + Template.indent([`${external}[key] = module[key];`]), + "});" + ]) + ); + } + } + if (instructions.length === 0) return "function() {}"; + return Template.asString([ + "function(module) {", + Template.indent(instructions), + "}" + ]); + }) + .join(",\n") + ), + "]," + ]); + + return new ConcatSource( + Template.asString([ + `System.register(${name}${systemDependencies}, function(${dynamicExport}, __system_context__) {`, + Template.indent([ + externalVarDeclarations, + Template.asString(externalVarInitialization), + "return {", + Template.indent([ + setters, + "execute: function() {", + Template.indent(`${dynamicExport}(`) + ]) + ]), + "" + ]), + source, + Template.asString([ + "", + Template.indent([ + Template.indent([Template.indent([");"]), "}"]), + "};" + ]), + "})" + ]) + ); + } + + /** + * @param {Chunk} chunk the chunk + * @param {Hash} hash hash + * @param {ChunkHashContext} chunkHashContext chunk hash context + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { + hash.update("SystemLibraryPlugin"); + if (options.name) { + hash.update(compilation.getPath(options.name, { chunk })); + } + } +} + +module.exports = SystemLibraryPlugin; diff --git a/webpack-lib/lib/library/UmdLibraryPlugin.js b/webpack-lib/lib/library/UmdLibraryPlugin.js new file mode 100644 index 00000000000..b21066d3934 --- /dev/null +++ b/webpack-lib/lib/library/UmdLibraryPlugin.js @@ -0,0 +1,351 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource, OriginalSource } = require("webpack-sources"); +const ExternalModule = require("../ExternalModule"); +const Template = require("../Template"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryCustomUmdCommentObject} LibraryCustomUmdCommentObject */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryCustomUmdObject} LibraryCustomUmdObject */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../ExternalModule").RequestRecord} RequestRecord */ +/** @typedef {import("../util/Hash")} Hash */ +/** + * @template T + * @typedef {import("./AbstractLibraryPlugin").LibraryContext} + * LibraryContext + */ + +/** + * @param {string[]} accessor the accessor to convert to path + * @returns {string} the path + */ +const accessorToObjectAccess = accessor => + accessor.map(a => `[${JSON.stringify(a)}]`).join(""); + +/** + * @param {string|undefined} 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 && base === undefined) + return `${a} = typeof ${a} === "object" ? ${a} : {}`; + return `${a} = ${a} || {}`; + }) + .join(joinWith); +}; + +/** @typedef {string | string[] | LibraryCustomUmdObject} UmdLibraryPluginName */ + +/** + * @typedef {object} UmdLibraryPluginOptions + * @property {LibraryType} type + * @property {boolean=} optionalAmdExternalAsGlobal + */ + +/** + * @typedef {object} UmdLibraryPluginParsed + * @property {string | string[] | undefined} name + * @property {LibraryCustomUmdObject} names + * @property {string | LibraryCustomUmdCommentObject | undefined} auxiliaryComment + * @property {boolean | undefined} namedDefine + */ + +/** + * @typedef {UmdLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class UmdLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {UmdLibraryPluginOptions} options the plugin option + */ + constructor(options) { + super({ + pluginName: "UmdLibraryPlugin", + type: options.type + }); + + this.optionalAmdExternalAsGlobal = options.optionalAmdExternalAsGlobal; + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + /** @type {LibraryName | undefined} */ + let name; + /** @type {LibraryCustomUmdObject} */ + let names; + if (typeof library.name === "object" && !Array.isArray(library.name)) { + name = library.name.root || library.name.amd || library.name.commonjs; + names = library.name; + } else { + name = library.name; + const singleName = Array.isArray(name) ? name[0] : name; + names = { + commonjs: singleName, + root: library.name, + amd: singleName + }; + } + return { + name, + names, + auxiliaryComment: library.auxiliaryComment, + namedDefine: library.umdNamedDefine + }; + } + + /** + * @param {Source} source source + * @param {RenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + render( + source, + { chunkGraph, runtimeTemplate, chunk, moduleGraph }, + { options, compilation } + ) { + const modules = chunkGraph + .getChunkModules(chunk) + .filter( + m => + m instanceof ExternalModule && + (m.externalType === "umd" || m.externalType === "umd2") + ); + let externals = /** @type {ExternalModule[]} */ (modules); + /** @type {ExternalModule[]} */ + const optionalExternals = []; + /** @type {ExternalModule[]} */ + let requiredExternals = []; + if (this.optionalAmdExternalAsGlobal) { + for (const m of externals) { + if (m.isOptional(moduleGraph)) { + optionalExternals.push(m); + } else { + requiredExternals.push(m); + } + } + externals = requiredExternals.concat(optionalExternals); + } else { + requiredExternals = externals; + } + + /** + * @param {string} str the string to replace + * @returns {string} the replaced keys + */ + const replaceKeys = str => + compilation.getPath(str, { + chunk + }); + + /** + * @param {ExternalModule[]} modules external modules + * @returns {string} result + */ + const externalsDepsArray = modules => + `[${replaceKeys( + modules + .map(m => + JSON.stringify( + typeof m.request === "object" + ? /** @type {RequestRecord} */ + (m.request).amd + : m.request + ) + ) + .join(", ") + )}]`; + + /** + * @param {ExternalModule[]} modules external modules + * @returns {string} result + */ + const externalsRootArray = modules => + replaceKeys( + modules + .map(m => { + let request = m.request; + if (typeof request === "object") + request = + /** @type {RequestRecord} */ + (request).root; + return `root${accessorToObjectAccess(/** @type {string[]} */ ([]).concat(request))}`; + }) + .join(", ") + ); + + /** + * @param {string} type the type + * @returns {string} external require array + */ + const externalsRequireArray = type => + replaceKeys( + externals + .map(m => { + let expr; + let request = m.request; + if (typeof request === "object") { + request = + /** @type {RequestRecord} */ + (request)[type]; + } + if (request === undefined) { + throw new Error( + `Missing external configuration for type:${type}` + ); + } + expr = Array.isArray(request) + ? `require(${JSON.stringify( + request[0] + )})${accessorToObjectAccess(request.slice(1))}` + : `require(${JSON.stringify(request)})`; + if (m.isOptional(moduleGraph)) { + expr = `(function webpackLoadOptionalExternalModule() { try { return ${expr}; } catch(e) {} }())`; + } + return expr; + }) + .join(", ") + ); + + /** + * @param {ExternalModule[]} modules external modules + * @returns {string} arguments + */ + const externalsArguments = modules => + modules + .map( + m => + `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( + `${chunkGraph.getModuleId(m)}` + )}__` + ) + .join(", "); + + /** + * @param {string| string[]} library library name + * @returns {string} stringified library name + */ + const libraryName = library => + JSON.stringify( + replaceKeys( + /** @type {string} */ + (/** @type {string[]} */ ([]).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, namedDefine, names } = options; + + /** + * @param {keyof LibraryCustomUmdCommentObject} type type + * @returns {string} comment + */ + const getAuxiliaryComment = 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${getAuxiliaryComment( + "commonjs2" + )} if(typeof exports === 'object' && typeof module === 'object')\n` + + ` module.exports = factory(${externalsRequireArray( + "commonjs2" + )});\n${getAuxiliaryComment( + "amd" + )} else if(typeof define === 'function' && define.amd)\n${ + requiredExternals.length > 0 + ? names.amd && namedDefine === true + ? ` define(${libraryName(names.amd)}, ${externalsDepsArray( + requiredExternals + )}, ${amdFactory});\n` + : ` define(${externalsDepsArray(requiredExternals)}, ${ + amdFactory + });\n` + : names.amd && namedDefine === true + ? ` define(${libraryName(names.amd)}, [], ${amdFactory});\n` + : ` define([], ${amdFactory});\n` + }${ + names.root || names.commonjs + ? `${getAuxiliaryComment( + "commonjs" + )} else if(typeof exports === 'object')\n` + + ` exports[${libraryName( + /** @type {string | string[]} */ + (names.commonjs || names.root) + )}] = factory(${externalsRequireArray( + "commonjs" + )});\n${getAuxiliaryComment("root")} else\n` + + ` ${replaceKeys( + accessorAccess( + "root", + /** @type {string | string[]} */ + (names.root || 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}, ${ + runtimeTemplate.supportsArrowFunction() + ? `(${externalsArguments(externals)}) =>` + : `function(${externalsArguments(externals)})` + } {\nreturn `, + "webpack/universalModuleDefinition" + ), + source, + ";\n})" + ); + } +} + +module.exports = UmdLibraryPlugin; diff --git a/webpack-lib/lib/logging/Logger.js b/webpack-lib/lib/logging/Logger.js new file mode 100644 index 00000000000..910b16f78e8 --- /dev/null +++ b/webpack-lib/lib/logging/Logger.js @@ -0,0 +1,216 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const LogType = Object.freeze({ + error: /** @type {"error"} */ ("error"), // message, c style arguments + warn: /** @type {"warn"} */ ("warn"), // message, c style arguments + info: /** @type {"info"} */ ("info"), // message, c style arguments + log: /** @type {"log"} */ ("log"), // message, c style arguments + debug: /** @type {"debug"} */ ("debug"), // message, c style arguments + + trace: /** @type {"trace"} */ ("trace"), // no arguments + + group: /** @type {"group"} */ ("group"), // [label] + groupCollapsed: /** @type {"groupCollapsed"} */ ("groupCollapsed"), // [label] + groupEnd: /** @type {"groupEnd"} */ ("groupEnd"), // [label] + + profile: /** @type {"profile"} */ ("profile"), // [profileName] + profileEnd: /** @type {"profileEnd"} */ ("profileEnd"), // [profileName] + + time: /** @type {"time"} */ ("time"), // name, time as [seconds, nanoseconds] + + clear: /** @type {"clear"} */ ("clear"), // no arguments + status: /** @type {"status"} */ ("status") // message, arguments +}); + +module.exports.LogType = LogType; + +/** @typedef {typeof LogType[keyof typeof LogType]} LogTypeEnum */ + +const LOG_SYMBOL = Symbol("webpack logger raw log method"); +const TIMERS_SYMBOL = Symbol("webpack logger times"); +const TIMERS_AGGREGATES_SYMBOL = Symbol("webpack logger aggregated times"); + +class WebpackLogger { + /** + * @param {function(LogTypeEnum, EXPECTED_ANY[]=): void} log log function + * @param {function(string | function(): string): WebpackLogger} getChildLogger function to create child logger + */ + constructor(log, getChildLogger) { + this[LOG_SYMBOL] = log; + this.getChildLogger = getChildLogger; + } + + /** + * @param {...EXPECTED_ANY} args args + */ + error(...args) { + this[LOG_SYMBOL](LogType.error, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + warn(...args) { + this[LOG_SYMBOL](LogType.warn, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + info(...args) { + this[LOG_SYMBOL](LogType.info, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + log(...args) { + this[LOG_SYMBOL](LogType.log, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + debug(...args) { + this[LOG_SYMBOL](LogType.debug, args); + } + + /** + * @param {EXPECTED_ANY} assertion assertion + * @param {...EXPECTED_ANY} args args + */ + assert(assertion, ...args) { + if (!assertion) { + this[LOG_SYMBOL](LogType.error, args); + } + } + + trace() { + this[LOG_SYMBOL](LogType.trace, ["Trace"]); + } + + clear() { + this[LOG_SYMBOL](LogType.clear); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + status(...args) { + this[LOG_SYMBOL](LogType.status, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + group(...args) { + this[LOG_SYMBOL](LogType.group, args); + } + + /** + * @param {...EXPECTED_ANY} args args + */ + groupCollapsed(...args) { + this[LOG_SYMBOL](LogType.groupCollapsed, args); + } + + groupEnd() { + this[LOG_SYMBOL](LogType.groupEnd); + } + + /** + * @param {string=} label label + */ + profile(label) { + this[LOG_SYMBOL](LogType.profile, [label]); + } + + /** + * @param {string=} label label + */ + profileEnd(label) { + this[LOG_SYMBOL](LogType.profileEnd, [label]); + } + + /** + * @param {string} label label + */ + time(label) { + /** @type {Map} */ + this[TIMERS_SYMBOL] = this[TIMERS_SYMBOL] || new Map(); + this[TIMERS_SYMBOL].set(label, process.hrtime()); + } + + /** + * @param {string=} label label + */ + timeLog(label) { + const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); + if (!prev) { + throw new Error(`No such label '${label}' for WebpackLogger.timeLog()`); + } + const time = process.hrtime(prev); + this[LOG_SYMBOL](LogType.time, [label, ...time]); + } + + /** + * @param {string=} label label + */ + timeEnd(label) { + const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); + if (!prev) { + throw new Error(`No such label '${label}' for WebpackLogger.timeEnd()`); + } + const time = process.hrtime(prev); + /** @type {Map} */ + (this[TIMERS_SYMBOL]).delete(label); + this[LOG_SYMBOL](LogType.time, [label, ...time]); + } + + /** + * @param {string=} label label + */ + timeAggregate(label) { + const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); + if (!prev) { + throw new Error( + `No such label '${label}' for WebpackLogger.timeAggregate()` + ); + } + const time = process.hrtime(prev); + /** @type {Map} */ + (this[TIMERS_SYMBOL]).delete(label); + /** @type {Map} */ + this[TIMERS_AGGREGATES_SYMBOL] = + this[TIMERS_AGGREGATES_SYMBOL] || new Map(); + const current = this[TIMERS_AGGREGATES_SYMBOL].get(label); + if (current !== undefined) { + if (time[1] + current[1] > 1e9) { + time[0] += current[0] + 1; + time[1] = time[1] - 1e9 + current[1]; + } else { + time[0] += current[0]; + time[1] += current[1]; + } + } + this[TIMERS_AGGREGATES_SYMBOL].set(label, time); + } + + /** + * @param {string=} label label + */ + timeAggregateEnd(label) { + if (this[TIMERS_AGGREGATES_SYMBOL] === undefined) return; + const time = this[TIMERS_AGGREGATES_SYMBOL].get(label); + if (time === undefined) return; + this[TIMERS_AGGREGATES_SYMBOL].delete(label); + this[LOG_SYMBOL](LogType.time, [label, ...time]); + } +} + +module.exports.Logger = WebpackLogger; diff --git a/webpack-lib/lib/logging/createConsoleLogger.js b/webpack-lib/lib/logging/createConsoleLogger.js new file mode 100644 index 00000000000..3b8ffd83897 --- /dev/null +++ b/webpack-lib/lib/logging/createConsoleLogger.js @@ -0,0 +1,213 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { LogType } = require("./Logger"); + +/** @typedef {import("../../declarations/WebpackOptions").FilterItemTypes} FilterItemTypes */ +/** @typedef {import("../../declarations/WebpackOptions").FilterTypes} FilterTypes */ +/** @typedef {import("./Logger").LogTypeEnum} LogTypeEnum */ + +/** @typedef {function(string): boolean} FilterFunction */ +/** @typedef {function(string, LogTypeEnum, EXPECTED_ANY[]=): void} LoggingFunction */ + +/** + * @typedef {object} LoggerConsole + * @property {function(): void} clear + * @property {function(): void} trace + * @property {(...args: EXPECTED_ANY[]) => void} info + * @property {(...args: EXPECTED_ANY[]) => void} log + * @property {(...args: EXPECTED_ANY[]) => void} warn + * @property {(...args: EXPECTED_ANY[]) => void} error + * @property {(...args: EXPECTED_ANY[]) => void=} debug + * @property {(...args: EXPECTED_ANY[]) => void=} group + * @property {(...args: EXPECTED_ANY[]) => void=} groupCollapsed + * @property {(...args: EXPECTED_ANY[]) => void=} groupEnd + * @property {(...args: EXPECTED_ANY[]) => void=} status + * @property {(...args: EXPECTED_ANY[]) => void=} profile + * @property {(...args: EXPECTED_ANY[]) => void=} profileEnd + * @property {(...args: EXPECTED_ANY[]) => void=} logTime + */ + +/** + * @typedef {object} LoggerOptions + * @property {false|true|"none"|"error"|"warn"|"info"|"log"|"verbose"} level loglevel + * @property {FilterTypes|boolean} debug filter for debug logging + * @property {LoggerConsole} console the console to log to + */ + +/** + * @param {FilterItemTypes} item an input item + * @returns {FilterFunction | undefined} filter function + */ +const filterToFunction = item => { + if (typeof item === "string") { + const regExp = new RegExp( + `[\\\\/]${item.replace(/[-[\]{}()*+?.\\^$|]/g, "\\$&")}([\\\\/]|$|!|\\?)` + ); + 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; + } +}; + +/** + * @enum {number} + */ +const LogLevel = { + none: 6, + false: 6, + error: 5, + warn: 4, + info: 3, + log: 2, + true: 2, + verbose: 1 +}; + +/** + * @param {LoggerOptions} options options object + * @returns {LoggingFunction} logging function + */ +module.exports = ({ level = "info", debug = false, console }) => { + const debugFilters = + /** @type {FilterFunction[]} */ + ( + typeof debug === "boolean" + ? [() => debug] + : /** @type {FilterItemTypes[]} */ ([]) + .concat(debug) + .map(filterToFunction) + ); + /** @type {number} */ + const loglevel = LogLevel[`${level}`] || 0; + + /** + * @param {string} name name of the logger + * @param {LogTypeEnum} type type of the log entry + * @param {EXPECTED_ANY[]=} args arguments of the log entry + * @returns {void} + */ + const logger = (name, type, args) => { + const labeledArgs = () => { + if (Array.isArray(args)) { + if (args.length > 0 && typeof args[0] === "string") { + return [`[${name}] ${args[0]}`, ...args.slice(1)]; + } + return [`[${name}]`, ...args]; + } + return []; + }; + const debug = debugFilters.some(f => f(name)); + switch (type) { + case LogType.debug: + if (!debug) return; + if (typeof console.debug === "function") { + console.debug(...labeledArgs()); + } else { + console.log(...labeledArgs()); + } + break; + case LogType.log: + if (!debug && loglevel > LogLevel.log) return; + console.log(...labeledArgs()); + break; + case LogType.info: + if (!debug && loglevel > LogLevel.info) return; + console.info(...labeledArgs()); + break; + case LogType.warn: + if (!debug && loglevel > LogLevel.warn) return; + console.warn(...labeledArgs()); + break; + case LogType.error: + if (!debug && loglevel > LogLevel.error) return; + console.error(...labeledArgs()); + break; + case LogType.trace: + if (!debug) return; + console.trace(); + break; + case LogType.groupCollapsed: + if (!debug && loglevel > LogLevel.log) return; + if (!debug && loglevel > LogLevel.verbose) { + if (typeof console.groupCollapsed === "function") { + console.groupCollapsed(...labeledArgs()); + } else { + console.log(...labeledArgs()); + } + break; + } + // falls through + case LogType.group: + if (!debug && loglevel > LogLevel.log) return; + if (typeof console.group === "function") { + console.group(...labeledArgs()); + } else { + console.log(...labeledArgs()); + } + break; + case LogType.groupEnd: + if (!debug && loglevel > LogLevel.log) return; + if (typeof console.groupEnd === "function") { + console.groupEnd(); + } + break; + case LogType.time: { + if (!debug && loglevel > LogLevel.log) return; + const [label, start, end] = + /** @type {[string, number, number]} */ + (args); + const ms = start * 1000 + end / 1000000; + const msg = `[${name}] ${label}: ${ms} ms`; + if (typeof console.logTime === "function") { + console.logTime(msg); + } else { + console.log(msg); + } + break; + } + case LogType.profile: + if (typeof console.profile === "function") { + console.profile(...labeledArgs()); + } + break; + case LogType.profileEnd: + if (typeof console.profileEnd === "function") { + console.profileEnd(...labeledArgs()); + } + break; + case LogType.clear: + if (!debug && loglevel > LogLevel.log) return; + if (typeof console.clear === "function") { + console.clear(); + } + break; + case LogType.status: + if (!debug && loglevel > LogLevel.info) return; + if (typeof console.status === "function") { + if (!args || args.length === 0) { + console.status(); + } else { + console.status(...labeledArgs()); + } + } else if (args && args.length !== 0) { + console.info(...labeledArgs()); + } + break; + default: + throw new Error(`Unexpected LogType ${type}`); + } + }; + return logger; +}; diff --git a/webpack-lib/lib/logging/runtime.js b/webpack-lib/lib/logging/runtime.js new file mode 100644 index 00000000000..b0c614081f0 --- /dev/null +++ b/webpack-lib/lib/logging/runtime.js @@ -0,0 +1,45 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncBailHook } = require("tapable"); +const { Logger } = require("./Logger"); +const createConsoleLogger = require("./createConsoleLogger"); + +/** @type {createConsoleLogger.LoggerOptions} */ +const currentDefaultLoggerOptions = { + level: "info", + debug: false, + console +}; +let currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); + +/** + * @param {string} name name of the logger + * @returns {Logger} a logger + */ +module.exports.getLogger = name => + new Logger( + (type, args) => { + if (module.exports.hooks.log.call(name, type, args) === undefined) { + currentDefaultLogger(name, type, args); + } + }, + childName => module.exports.getLogger(`${name}/${childName}`) + ); + +/** + * @param {createConsoleLogger.LoggerOptions} options new options, merge with old options + * @returns {void} + */ +module.exports.configureDefaultLogger = options => { + Object.assign(currentDefaultLoggerOptions, options); + currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); +}; + +module.exports.hooks = { + log: new SyncBailHook(["origin", "type", "args"]) +}; diff --git a/webpack-lib/lib/logging/truncateArgs.js b/webpack-lib/lib/logging/truncateArgs.js new file mode 100644 index 00000000000..148ac7ae12b --- /dev/null +++ b/webpack-lib/lib/logging/truncateArgs.js @@ -0,0 +1,83 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @param {Array} array array of numbers + * @returns {number} sum of all numbers in array + */ +const arraySum = array => { + let sum = 0; + for (const item of array) sum += item; + return sum; +}; + +/** + * @param {EXPECTED_ANY[]} args items to be truncated + * @param {number} maxLength maximum length of args including spaces between + * @returns {string[]} truncated args + */ +const truncateArgs = (args, maxLength) => { + const lengths = args.map(a => `${a}`.length); + const availableLength = maxLength - lengths.length + 1; + + if (availableLength > 0 && args.length === 1) { + if (availableLength >= args[0].length) { + return args; + } else if (availableLength > 3) { + return [`...${args[0].slice(-availableLength + 3)}`]; + } + return [args[0].slice(-availableLength)]; + } + + // Check if there is space for at least 4 chars per arg + if (availableLength < arraySum(lengths.map(i => Math.min(i, 6)))) { + // remove args + if (args.length > 1) return truncateArgs(args.slice(0, -1), maxLength); + return []; + } + + let currentLength = arraySum(lengths); + + // Check if all fits into maxLength + if (currentLength <= availableLength) return args; + + // Try to remove chars from the longest items until it fits + while (currentLength > availableLength) { + const maxLength = Math.max(...lengths); + const shorterItems = lengths.filter(l => l !== maxLength); + const nextToMaxLength = + shorterItems.length > 0 ? Math.max(...shorterItems) : 0; + const maxReduce = maxLength - nextToMaxLength; + let maxItems = lengths.length - shorterItems.length; + let overrun = currentLength - availableLength; + for (let i = 0; i < lengths.length; i++) { + if (lengths[i] === maxLength) { + const reduce = Math.min(Math.floor(overrun / maxItems), maxReduce); + lengths[i] -= reduce; + currentLength -= reduce; + overrun -= reduce; + maxItems--; + } + } + } + + // Return args reduced to length in lengths + return args.map((a, i) => { + const str = `${a}`; + const length = lengths[i]; + if (str.length === length) { + return str; + } else if (length > 5) { + return `...${str.slice(-length + 3)}`; + } else if (length > 0) { + return str.slice(-length); + } + return ""; + }); +}; + +module.exports = truncateArgs; diff --git a/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js b/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js new file mode 100644 index 00000000000..cd7f787281a --- /dev/null +++ b/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js @@ -0,0 +1,121 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const StartupChunkDependenciesPlugin = require("../runtime/StartupChunkDependenciesPlugin"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} CommonJsChunkLoadingPluginOptions + * @property {boolean} [asyncChunkLoading] enable async chunk loading + */ + +class CommonJsChunkLoadingPlugin { + /** + * @param {CommonJsChunkLoadingPluginOptions} [options] options + */ + constructor(options = {}) { + this._asyncChunkLoading = options.asyncChunkLoading; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const ChunkLoadingRuntimeModule = this._asyncChunkLoading + ? require("./ReadFileChunkLoadingRuntimeModule") + : require("./RequireChunkLoadingRuntimeModule"); + const chunkLoadingValue = this._asyncChunkLoading + ? "async-node" + : "require"; + new StartupChunkDependenciesPlugin({ + chunkLoading: chunkLoadingValue, + asyncChunkLoading: this._asyncChunkLoading + }).apply(compiler); + compiler.hooks.thisCompilation.tap( + "CommonJsChunkLoadingPlugin", + compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === chunkLoadingValue; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk + * @param {Set} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.hasOwnProperty); + compilation.addRuntimeModule( + chunk, + new ChunkLoadingRuntimeModule(set) + ); + }; + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("CommonJsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("CommonJsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("CommonJsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.baseURI) + .tap("CommonJsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.externalInstallChunk) + .tap("CommonJsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.onChunksLoaded) + .tap("CommonJsChunkLoadingPlugin", handler); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.getChunkScriptFilename); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.getChunkUpdateScriptFilename); + set.add(RuntimeGlobals.moduleCache); + set.add(RuntimeGlobals.hmrModuleData); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.getUpdateManifestFilename); + }); + } + ); + } +} + +module.exports = CommonJsChunkLoadingPlugin; diff --git a/webpack-lib/lib/node/NodeEnvironmentPlugin.js b/webpack-lib/lib/node/NodeEnvironmentPlugin.js new file mode 100644 index 00000000000..221b1af0efa --- /dev/null +++ b/webpack-lib/lib/node/NodeEnvironmentPlugin.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const CachedInputFileSystem = require("enhanced-resolve").CachedInputFileSystem; +const fs = require("graceful-fs"); +const createConsoleLogger = require("../logging/createConsoleLogger"); +const NodeWatchFileSystem = require("./NodeWatchFileSystem"); +const nodeConsole = require("./nodeConsole"); + +/** @typedef {import("../../declarations/WebpackOptions").InfrastructureLogging} InfrastructureLogging */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ + +class NodeEnvironmentPlugin { + /** + * @param {object} options options + * @param {InfrastructureLogging} options.infrastructureLogging infrastructure logging options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { infrastructureLogging } = this.options; + compiler.infrastructureLogger = createConsoleLogger({ + level: infrastructureLogging.level || "info", + debug: infrastructureLogging.debug || false, + console: + infrastructureLogging.console || + nodeConsole({ + colors: infrastructureLogging.colors, + appendOnly: infrastructureLogging.appendOnly, + stream: + /** @type {NodeJS.WritableStream} */ + (infrastructureLogging.stream) + }) + }); + compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000); + const inputFileSystem = + /** @type {InputFileSystem} */ + (compiler.inputFileSystem); + compiler.outputFileSystem = fs; + compiler.intermediateFileSystem = fs; + compiler.watchFileSystem = new NodeWatchFileSystem(inputFileSystem); + compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => { + if ( + compiler.inputFileSystem === inputFileSystem && + inputFileSystem.purge + ) { + compiler.fsStartTime = Date.now(); + inputFileSystem.purge(); + } + }); + } +} + +module.exports = NodeEnvironmentPlugin; diff --git a/webpack-lib/lib/node/NodeSourcePlugin.js b/webpack-lib/lib/node/NodeSourcePlugin.js new file mode 100644 index 00000000000..fca1fc9caaf --- /dev/null +++ b/webpack-lib/lib/node/NodeSourcePlugin.js @@ -0,0 +1,19 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Compiler")} Compiler */ + +class NodeSourcePlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) {} +} + +module.exports = NodeSourcePlugin; diff --git a/webpack-lib/lib/node/NodeTargetPlugin.js b/webpack-lib/lib/node/NodeTargetPlugin.js new file mode 100644 index 00000000000..1cc01810daa --- /dev/null +++ b/webpack-lib/lib/node/NodeTargetPlugin.js @@ -0,0 +1,85 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ExternalsPlugin = require("../ExternalsPlugin"); + +/** @typedef {import("../Compiler")} Compiler */ + +const builtins = [ + "assert", + "assert/strict", + "async_hooks", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "diagnostics_channel", + "dns", + "dns/promises", + "domain", + "events", + "fs", + "fs/promises", + "http", + "http2", + "https", + "inspector", + "inspector/promises", + "module", + "net", + "os", + "path", + "path/posix", + "path/win32", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "readline/promises", + "repl", + "stream", + "stream/consumers", + "stream/promises", + "stream/web", + "string_decoder", + "sys", + "timers", + "timers/promises", + "tls", + "trace_events", + "tty", + "url", + "util", + "util/types", + "v8", + "vm", + "wasi", + "worker_threads", + "zlib", + /^node:/, + + // cspell:word pnpapi + // Yarn PnP adds pnpapi as "builtin" + "pnpapi" +]; + +class NodeTargetPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + new ExternalsPlugin("node-commonjs", builtins).apply(compiler); + } +} + +module.exports = NodeTargetPlugin; diff --git a/webpack-lib/lib/node/NodeTemplatePlugin.js b/webpack-lib/lib/node/NodeTemplatePlugin.js new file mode 100644 index 00000000000..c784368e373 --- /dev/null +++ b/webpack-lib/lib/node/NodeTemplatePlugin.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const CommonJsChunkFormatPlugin = require("../javascript/CommonJsChunkFormatPlugin"); +const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); + +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} NodeTemplatePluginOptions + * @property {boolean} [asyncChunkLoading] enable async chunk loading + */ + +class NodeTemplatePlugin { + /** + * @param {NodeTemplatePluginOptions} [options] options object + */ + constructor(options = {}) { + this._options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const chunkLoading = this._options.asyncChunkLoading + ? "async-node" + : "require"; + compiler.options.output.chunkLoading = chunkLoading; + new CommonJsChunkFormatPlugin().apply(compiler); + new EnableChunkLoadingPlugin(chunkLoading).apply(compiler); + } +} + +module.exports = NodeTemplatePlugin; diff --git a/webpack-lib/lib/node/NodeWatchFileSystem.js b/webpack-lib/lib/node/NodeWatchFileSystem.js new file mode 100644 index 00000000000..091864a2b94 --- /dev/null +++ b/webpack-lib/lib/node/NodeWatchFileSystem.js @@ -0,0 +1,192 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const Watchpack = require("watchpack"); + +/** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */ +/** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ +/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("../util/fs").WatchMethod} WatchMethod */ + +class NodeWatchFileSystem { + /** + * @param {InputFileSystem} inputFileSystem input filesystem + */ + constructor(inputFileSystem) { + this.inputFileSystem = inputFileSystem; + this.watcherOptions = { + aggregateTimeout: 0 + }; + /** @type {Watchpack | null} */ + this.watcher = new Watchpack(this.watcherOptions); + } + + /** @type {WatchMethod} */ + watch( + files, + directories, + missing, + startTime, + options, + callback, + callbackUndelayed + ) { + if (!files || typeof files[Symbol.iterator] !== "function") { + throw new Error("Invalid arguments: 'files'"); + } + if (!directories || typeof directories[Symbol.iterator] !== "function") { + throw new Error("Invalid arguments: 'directories'"); + } + if (!missing || typeof missing[Symbol.iterator] !== "function") { + throw new Error("Invalid arguments: 'missing'"); + } + if (typeof callback !== "function") { + throw new Error("Invalid arguments: 'callback'"); + } + if (typeof startTime !== "number" && startTime) { + throw new Error("Invalid arguments: 'startTime'"); + } + if (typeof options !== "object") { + throw new Error("Invalid arguments: 'options'"); + } + if (typeof callbackUndelayed !== "function" && callbackUndelayed) { + throw new Error("Invalid arguments: 'callbackUndelayed'"); + } + const oldWatcher = this.watcher; + this.watcher = new Watchpack(options); + + if (callbackUndelayed) { + this.watcher.once("change", callbackUndelayed); + } + + const fetchTimeInfo = () => { + const fileTimeInfoEntries = new Map(); + const contextTimeInfoEntries = new Map(); + if (this.watcher) { + this.watcher.collectTimeInfoEntries( + fileTimeInfoEntries, + contextTimeInfoEntries + ); + } + return { fileTimeInfoEntries, contextTimeInfoEntries }; + }; + this.watcher.once( + "aggregated", + /** + * @param {Set} changes changes + * @param {Set} removals removals + */ + (changes, removals) => { + // pause emitting events (avoids clearing aggregated changes and removals on timeout) + /** @type {Watchpack} */ + (this.watcher).pause(); + + const fs = this.inputFileSystem; + if (fs && fs.purge) { + for (const item of changes) { + fs.purge(item); + } + for (const item of removals) { + fs.purge(item); + } + } + const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo(); + callback( + null, + fileTimeInfoEntries, + contextTimeInfoEntries, + changes, + removals + ); + } + ); + + this.watcher.watch({ files, directories, missing, startTime }); + + if (oldWatcher) { + oldWatcher.close(); + } + return { + close: () => { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + }, + pause: () => { + if (this.watcher) { + this.watcher.pause(); + } + }, + getAggregatedRemovals: util.deprecate( + () => { + const items = this.watcher && this.watcher.aggregatedRemovals; + const fs = this.inputFileSystem; + if (items && fs && fs.purge) { + for (const item of items) { + fs.purge(item); + } + } + return items; + }, + "Watcher.getAggregatedRemovals is deprecated in favor of Watcher.getInfo since that's more performant.", + "DEP_WEBPACK_WATCHER_GET_AGGREGATED_REMOVALS" + ), + getAggregatedChanges: util.deprecate( + () => { + const items = this.watcher && this.watcher.aggregatedChanges; + const fs = this.inputFileSystem; + if (items && fs && fs.purge) { + for (const item of items) { + fs.purge(item); + } + } + return items; + }, + "Watcher.getAggregatedChanges is deprecated in favor of Watcher.getInfo since that's more performant.", + "DEP_WEBPACK_WATCHER_GET_AGGREGATED_CHANGES" + ), + getFileTimeInfoEntries: util.deprecate( + () => fetchTimeInfo().fileTimeInfoEntries, + "Watcher.getFileTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.", + "DEP_WEBPACK_WATCHER_FILE_TIME_INFO_ENTRIES" + ), + getContextTimeInfoEntries: util.deprecate( + () => fetchTimeInfo().contextTimeInfoEntries, + "Watcher.getContextTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.", + "DEP_WEBPACK_WATCHER_CONTEXT_TIME_INFO_ENTRIES" + ), + getInfo: () => { + const removals = this.watcher && this.watcher.aggregatedRemovals; + const changes = this.watcher && this.watcher.aggregatedChanges; + const fs = this.inputFileSystem; + if (fs && fs.purge) { + if (removals) { + for (const item of removals) { + fs.purge(item); + } + } + if (changes) { + for (const item of changes) { + fs.purge(item); + } + } + } + const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo(); + return { + changes, + removals, + fileTimeInfoEntries, + contextTimeInfoEntries + }; + } + }; + } +} + +module.exports = NodeWatchFileSystem; diff --git a/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js b/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..fd138b2e899 --- /dev/null +++ b/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js @@ -0,0 +1,301 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + chunkHasJs, + getChunkFilenameTemplate +} = require("../javascript/JavascriptModulesPlugin"); +const { getInitialChunkIds } = require("../javascript/StartupHelpers"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +class ReadFileChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("readFile chunk loading", RuntimeModule.STAGE_ATTACH); + this.runtimeRequirements = runtimeRequirements; + } + + /** + * @private + * @param {Chunk} chunk chunk + * @param {string} rootOutputDir root output directory + * @returns {string} generated code + */ + _generateBaseUri(chunk, rootOutputDir) { + const options = chunk.getEntryOptions(); + if (options && options.baseUri) { + return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; + } + + return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ + rootOutputDir + ? `__dirname + ${JSON.stringify(`/${rootOutputDir}`)}` + : "__filename" + });`; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.ensureChunkHandlers; + const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); + const withExternalInstallChunk = this.runtimeRequirements.has( + RuntimeGlobals.externalInstallChunk + ); + const withOnChunkLoad = this.runtimeRequirements.has( + RuntimeGlobals.onChunksLoaded + ); + const withLoading = this.runtimeRequirements.has( + RuntimeGlobals.ensureChunkHandlers + ); + const withHmr = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const withHmrManifest = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadManifest + ); + const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); + const hasJsMatcher = compileBooleanMatcher(conditionMap); + const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); + + const outputName = compilation.getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ); + const rootOutputDir = getUndoPath( + outputName, + /** @type {string} */ (compilation.outputOptions.path), + false + ); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_readFileVm` + : undefined; + + return Template.asString([ + withBaseURI + ? this._generateBaseUri(chunk, rootOutputDir) + : "// no baseURI", + "", + "// object to store loaded chunks", + '// "0" means "already loaded", Promise means loading', + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( + ",\n" + ) + ), + "};", + "", + withOnChunkLoad + ? `${ + RuntimeGlobals.onChunksLoaded + }.readFileVm = ${runtimeTemplate.returningFunction( + "installedChunks[chunkId] === 0", + "chunkId" + )};` + : "// no on chunks loaded", + "", + withLoading || withExternalInstallChunk + ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ + "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", + "for(var moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent([ + `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` + ]), + "}" + ]), + "}", + `if(runtime) runtime(${RuntimeGlobals.require});`, + "for(var i = 0; i < chunkIds.length; i++) {", + Template.indent([ + "if(installedChunks[chunkIds[i]]) {", + Template.indent(["installedChunks[chunkIds[i]][0]();"]), + "}", + "installedChunks[chunkIds[i]] = 0;" + ]), + "}", + withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" + ])};` + : "// no chunk install function needed", + "", + withLoading + ? Template.asString([ + "// ReadFile + VM.run chunk loading for javascript", + `${fn}.readFileVm = function(chunkId, promises) {`, + hasJsMatcher !== false + ? Template.indent([ + "", + "var installedChunkData = installedChunks[chunkId];", + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + '// array of [resolve, reject, promise] means "currently loading"', + "if(installedChunkData) {", + Template.indent(["promises.push(installedChunkData[2]);"]), + "} else {", + Template.indent([ + hasJsMatcher === true + ? "if(true) { // all chunks have JS" + : `if(${hasJsMatcher("chunkId")}) {`, + Template.indent([ + "// load the chunk and return promise to it", + "var promise = new Promise(function(resolve, reject) {", + Template.indent([ + "installedChunkData = installedChunks[chunkId] = [resolve, reject];", + `var filename = require('path').join(__dirname, ${JSON.stringify( + rootOutputDir + )} + ${ + RuntimeGlobals.getChunkScriptFilename + }(chunkId));`, + "require('fs').readFile(filename, 'utf-8', function(err, content) {", + Template.indent([ + "if(err) return reject(err);", + "var chunk = {};", + "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + + "(chunk, require, require('path').dirname(filename), filename);", + "installChunk(chunk);" + ]), + "});" + ]), + "});", + "promises.push(installedChunkData[2] = promise);" + ]), + hasJsMatcher === true + ? "}" + : "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ]) + : Template.indent(["installedChunks[chunkId] = 0;"]), + "};" + ]) + : "// no chunk loading", + "", + withExternalInstallChunk + ? Template.asString([ + `module.exports = ${RuntimeGlobals.require};`, + `${RuntimeGlobals.externalInstallChunk} = installChunk;` + ]) + : "// no external install chunk", + "", + withHmr + ? Template.asString([ + "function loadUpdateChunk(chunkId, updatedModulesList) {", + Template.indent([ + "return new Promise(function(resolve, reject) {", + Template.indent([ + `var filename = require('path').join(__dirname, ${JSON.stringify( + rootOutputDir + )} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId));`, + "require('fs').readFile(filename, 'utf-8', function(err, content) {", + Template.indent([ + "if(err) return reject(err);", + "var update = {};", + "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + + "(update, require, require('path').dirname(filename), filename);", + "var updatedModules = update.modules;", + "var runtime = update.runtime;", + "for(var moduleId in updatedModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, + Template.indent([ + "currentUpdate[moduleId] = updatedModules[moduleId];", + "if(updatedModulesList) updatedModulesList.push(moduleId);" + ]), + "}" + ]), + "}", + "if(runtime) currentUpdateRuntime.push(runtime);", + "resolve();" + ]), + "});" + ]), + "});" + ]), + "}", + "", + Template.getFunctionContent( + require("../hmr/JavascriptHotModuleReplacement.runtime.js") + ) + .replace(/\$key\$/g, "readFileVm") + .replace(/\$installedChunks\$/g, "installedChunks") + .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") + .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) + .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) + .replace( + /\$ensureChunkHandlers\$/g, + RuntimeGlobals.ensureChunkHandlers + ) + .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) + .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) + .replace( + /\$hmrDownloadUpdateHandlers\$/g, + RuntimeGlobals.hmrDownloadUpdateHandlers + ) + .replace( + /\$hmrInvalidateModuleHandlers\$/g, + RuntimeGlobals.hmrInvalidateModuleHandlers + ) + ]) + : "// no HMR", + "", + withHmrManifest + ? Template.asString([ + `${RuntimeGlobals.hmrDownloadManifest} = function() {`, + Template.indent([ + "return new Promise(function(resolve, reject) {", + Template.indent([ + `var filename = require('path').join(__dirname, ${JSON.stringify( + rootOutputDir + )} + ${RuntimeGlobals.getUpdateManifestFilename}());`, + "require('fs').readFile(filename, 'utf-8', function(err, content) {", + Template.indent([ + "if(err) {", + Template.indent([ + 'if(err.code === "ENOENT") return resolve();', + "return reject(err);" + ]), + "}", + "try { resolve(JSON.parse(content)); }", + "catch(e) { reject(e); }" + ]), + "});" + ]), + "});" + ]), + "}" + ]) + : "// no HMR manifest" + ]); + } +} + +module.exports = ReadFileChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js b/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js new file mode 100644 index 00000000000..d53f1a83dd1 --- /dev/null +++ b/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js @@ -0,0 +1,123 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} ReadFileCompileAsyncWasmPluginOptions + * @property {boolean} [import] use import? + */ + +const PLUGIN_NAME = "ReadFileCompileAsyncWasmPlugin"; + +class ReadFileCompileAsyncWasmPlugin { + /** + * @param {ReadFileCompileAsyncWasmPluginOptions} [options] options object + */ + constructor({ import: useImport = false } = {}) { + this._import = useImport; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const globalWasmLoading = compilation.outputOptions.wasmLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const wasmLoading = + options && options.wasmLoading !== undefined + ? options.wasmLoading + : globalWasmLoading; + return wasmLoading === "async-node"; + }; + + /** + * @param {string} path path to wasm file + * @returns {string} generated code to load the wasm file + */ + const generateLoadBinaryCode = this._import + ? path => + Template.asString([ + "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", + Template.indent([ + `readFile(new URL(${path}, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, + Template.indent([ + "if (err) return reject(err);", + "", + "// Fake fetch response", + "resolve({", + Template.indent(["arrayBuffer() { return buffer; }"]), + "});" + ]), + "});" + ]), + "}))" + ]) + : path => + Template.asString([ + "new Promise(function (resolve, reject) {", + Template.indent([ + "try {", + Template.indent([ + "var { readFile } = require('fs');", + "var { join } = require('path');", + "", + `readFile(join(__dirname, ${path}), function(err, buffer){`, + Template.indent([ + "if (err) return reject(err);", + "", + "// Fake fetch response", + "resolve({", + Template.indent(["arrayBuffer() { return buffer; }"]), + "});" + ]), + "});" + ]), + "} catch (err) { reject(err); }" + ]), + "})" + ]); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.instantiateWasm) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC + ) + ) { + return; + } + compilation.addRuntimeModule( + chunk, + new AsyncWasmLoadingRuntimeModule({ + generateLoadBinaryCode, + supportsStreaming: false + }) + ); + }); + }); + } +} + +module.exports = ReadFileCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js b/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js new file mode 100644 index 00000000000..59e58b5f30b --- /dev/null +++ b/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js @@ -0,0 +1,129 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const WasmChunkLoadingRuntimeModule = require("../wasm-sync/WasmChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} ReadFileCompileWasmPluginOptions + * @property {boolean} [mangleImports] mangle imports + * @property {boolean} [import] use import? + */ + +// TODO webpack 6 remove + +const PLUGIN_NAME = "ReadFileCompileWasmPlugin"; + +class ReadFileCompileWasmPlugin { + /** + * @param {ReadFileCompileWasmPluginOptions} [options] options object + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const globalWasmLoading = compilation.outputOptions.wasmLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, when wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const wasmLoading = + options && options.wasmLoading !== undefined + ? options.wasmLoading + : globalWasmLoading; + return wasmLoading === "async-node"; + }; + + /** + * @param {string} path path to wasm file + * @returns {string} generated code to load the wasm file + */ + const generateLoadBinaryCode = this.options.import + ? path => + Template.asString([ + "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", + Template.indent([ + `readFile(new URL(${path}, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, + Template.indent([ + "if (err) return reject(err);", + "", + "// Fake fetch response", + "resolve({", + Template.indent(["arrayBuffer() { return buffer; }"]), + "});" + ]), + "});" + ]), + "}))" + ]) + : path => + Template.asString([ + "new Promise(function (resolve, reject) {", + Template.indent([ + "var { readFile } = require('fs');", + "var { join } = require('path');", + "", + "try {", + Template.indent([ + `readFile(join(__dirname, ${path}), function(err, buffer){`, + Template.indent([ + "if (err) return reject(err);", + "", + "// Fake fetch response", + "resolve({", + Template.indent(["arrayBuffer() { return buffer; }"]), + "});" + ]), + "});" + ]), + "} catch (err) { reject(err); }" + ]), + "})" + ]); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => m.type === WEBASSEMBLY_MODULE_TYPE_SYNC + ) + ) { + return; + } + set.add(RuntimeGlobals.moduleCache); + compilation.addRuntimeModule( + chunk, + new WasmChunkLoadingRuntimeModule({ + generateLoadBinaryCode, + supportsStreaming: false, + mangleImports: this.options.mangleImports, + runtimeRequirements: set + }) + ); + }); + }); + } +} + +module.exports = ReadFileCompileWasmPlugin; diff --git a/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js b/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..1d4959459d5 --- /dev/null +++ b/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js @@ -0,0 +1,246 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + chunkHasJs, + getChunkFilenameTemplate +} = require("../javascript/JavascriptModulesPlugin"); +const { getInitialChunkIds } = require("../javascript/StartupHelpers"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +class RequireChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("require chunk loading", RuntimeModule.STAGE_ATTACH); + this.runtimeRequirements = runtimeRequirements; + } + + /** + * @private + * @param {Chunk} chunk chunk + * @param {string} rootOutputDir root output directory + * @returns {string} generated code + */ + _generateBaseUri(chunk, rootOutputDir) { + const options = chunk.getEntryOptions(); + if (options && options.baseUri) { + return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; + } + + return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ + rootOutputDir !== "./" + ? `__dirname + ${JSON.stringify(`/${rootOutputDir}`)}` + : "__filename" + });`; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.ensureChunkHandlers; + const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); + const withExternalInstallChunk = this.runtimeRequirements.has( + RuntimeGlobals.externalInstallChunk + ); + const withOnChunkLoad = this.runtimeRequirements.has( + RuntimeGlobals.onChunksLoaded + ); + const withLoading = this.runtimeRequirements.has( + RuntimeGlobals.ensureChunkHandlers + ); + const withHmr = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const withHmrManifest = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadManifest + ); + const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); + const hasJsMatcher = compileBooleanMatcher(conditionMap); + const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); + + const outputName = compilation.getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ); + const rootOutputDir = getUndoPath( + outputName, + /** @type {string} */ (compilation.outputOptions.path), + true + ); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_require` + : undefined; + + return Template.asString([ + withBaseURI + ? this._generateBaseUri(chunk, rootOutputDir) + : "// no baseURI", + "", + "// object to store loaded chunks", + '// "1" means "loaded", otherwise not loaded yet', + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join( + ",\n" + ) + ), + "};", + "", + withOnChunkLoad + ? `${ + RuntimeGlobals.onChunksLoaded + }.require = ${runtimeTemplate.returningFunction( + "installedChunks[chunkId]", + "chunkId" + )};` + : "// no on chunks loaded", + "", + withLoading || withExternalInstallChunk + ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ + "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", + "for(var moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent([ + `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` + ]), + "}" + ]), + "}", + `if(runtime) runtime(${RuntimeGlobals.require});`, + "for(var i = 0; i < chunkIds.length; i++)", + Template.indent("installedChunks[chunkIds[i]] = 1;"), + withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" + ])};` + : "// no chunk install function needed", + "", + withLoading + ? Template.asString([ + "// require() chunk loading for javascript", + `${fn}.require = ${runtimeTemplate.basicFunction( + "chunkId, promises", + hasJsMatcher !== false + ? [ + '// "1" is the signal for "already loaded"', + "if(!installedChunks[chunkId]) {", + Template.indent([ + hasJsMatcher === true + ? "if(true) { // all chunks have JS" + : `if(${hasJsMatcher("chunkId")}) {`, + Template.indent([ + `installChunk(require(${JSON.stringify( + rootOutputDir + )} + ${ + RuntimeGlobals.getChunkScriptFilename + }(chunkId)));` + ]), + "} else installedChunks[chunkId] = 1;", + "" + ]), + "}" + ] + : "installedChunks[chunkId] = 1;" + )};` + ]) + : "// no chunk loading", + "", + withExternalInstallChunk + ? Template.asString([ + `module.exports = ${RuntimeGlobals.require};`, + `${RuntimeGlobals.externalInstallChunk} = installChunk;` + ]) + : "// no external install chunk", + "", + withHmr + ? Template.asString([ + "function loadUpdateChunk(chunkId, updatedModulesList) {", + Template.indent([ + `var update = require(${JSON.stringify(rootOutputDir)} + ${ + RuntimeGlobals.getChunkUpdateScriptFilename + }(chunkId));`, + "var updatedModules = update.modules;", + "var runtime = update.runtime;", + "for(var moduleId in updatedModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, + Template.indent([ + "currentUpdate[moduleId] = updatedModules[moduleId];", + "if(updatedModulesList) updatedModulesList.push(moduleId);" + ]), + "}" + ]), + "}", + "if(runtime) currentUpdateRuntime.push(runtime);" + ]), + "}", + "", + Template.getFunctionContent( + require("../hmr/JavascriptHotModuleReplacement.runtime.js") + ) + .replace(/\$key\$/g, "require") + .replace(/\$installedChunks\$/g, "installedChunks") + .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") + .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) + .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) + .replace( + /\$ensureChunkHandlers\$/g, + RuntimeGlobals.ensureChunkHandlers + ) + .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) + .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) + .replace( + /\$hmrDownloadUpdateHandlers\$/g, + RuntimeGlobals.hmrDownloadUpdateHandlers + ) + .replace( + /\$hmrInvalidateModuleHandlers\$/g, + RuntimeGlobals.hmrInvalidateModuleHandlers + ) + ]) + : "// no HMR", + "", + withHmrManifest + ? Template.asString([ + `${RuntimeGlobals.hmrDownloadManifest} = function() {`, + Template.indent([ + "return Promise.resolve().then(function() {", + Template.indent([ + `return require(${JSON.stringify(rootOutputDir)} + ${ + RuntimeGlobals.getUpdateManifestFilename + }());` + ]), + "})['catch'](function(err) { if(err.code !== 'MODULE_NOT_FOUND') throw err; });" + ]), + "}" + ]) + : "// no HMR manifest" + ]); + } +} + +module.exports = RequireChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/node/nodeConsole.js b/webpack-lib/lib/node/nodeConsole.js new file mode 100644 index 00000000000..9a1125ee543 --- /dev/null +++ b/webpack-lib/lib/node/nodeConsole.js @@ -0,0 +1,163 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const truncateArgs = require("../logging/truncateArgs"); + +/** @typedef {import("../logging/createConsoleLogger").LoggerConsole} LoggerConsole */ + +/** + * @param {object} options options + * @param {boolean=} options.colors colors + * @param {boolean=} options.appendOnly append only + * @param {NodeJS.WritableStream} options.stream stream + * @returns {LoggerConsole} logger function + */ +module.exports = ({ colors, appendOnly, stream }) => { + /** @type {string[] | undefined} */ + let currentStatusMessage; + let hasStatusMessage = false; + let currentIndent = ""; + let currentCollapsed = 0; + + /** + * @param {string} str string + * @param {string} prefix prefix + * @param {string} colorPrefix color prefix + * @param {string} colorSuffix color suffix + * @returns {string} indented string + */ + const indent = (str, prefix, colorPrefix, colorSuffix) => { + if (str === "") return str; + prefix = currentIndent + prefix; + if (colors) { + return ( + prefix + + colorPrefix + + str.replace(/\n/g, `${colorSuffix}\n${prefix}${colorPrefix}`) + + colorSuffix + ); + } + + return prefix + str.replace(/\n/g, `\n${prefix}`); + }; + + const clearStatusMessage = () => { + if (hasStatusMessage) { + stream.write("\u001B[2K\r"); + hasStatusMessage = false; + } + }; + + const writeStatusMessage = () => { + if (!currentStatusMessage) return; + const l = /** @type {TODO} */ (stream).columns || 40; + const args = truncateArgs(currentStatusMessage, l - 1); + const str = args.join(" "); + const coloredStr = `\u001B[1m${str}\u001B[39m\u001B[22m`; + stream.write(`\u001B[2K\r${coloredStr}`); + hasStatusMessage = true; + }; + + /** + * @param {string} prefix prefix + * @param {string} colorPrefix color prefix + * @param {string} colorSuffix color suffix + * @returns {(function(...EXPECTED_ANY[]): void)} function to write with colors + */ + const writeColored = + (prefix, colorPrefix, colorSuffix) => + (...args) => { + if (currentCollapsed > 0) return; + clearStatusMessage(); + const str = indent( + util.format(...args), + prefix, + colorPrefix, + colorSuffix + ); + stream.write(`${str}\n`); + writeStatusMessage(); + }; + + const writeGroupMessage = writeColored( + "<-> ", + "\u001B[1m\u001B[36m", + "\u001B[39m\u001B[22m" + ); + + const writeGroupCollapsedMessage = writeColored( + "<+> ", + "\u001B[1m\u001B[36m", + "\u001B[39m\u001B[22m" + ); + + return { + log: writeColored(" ", "\u001B[1m", "\u001B[22m"), + debug: writeColored(" ", "", ""), + trace: writeColored(" ", "", ""), + info: writeColored(" ", "\u001B[1m\u001B[32m", "\u001B[39m\u001B[22m"), + warn: writeColored(" ", "\u001B[1m\u001B[33m", "\u001B[39m\u001B[22m"), + error: writeColored(" ", "\u001B[1m\u001B[31m", "\u001B[39m\u001B[22m"), + logTime: writeColored( + " ", + "\u001B[1m\u001B[35m", + "\u001B[39m\u001B[22m" + ), + group: (...args) => { + writeGroupMessage(...args); + if (currentCollapsed > 0) { + currentCollapsed++; + } else { + currentIndent += " "; + } + }, + groupCollapsed: (...args) => { + writeGroupCollapsedMessage(...args); + currentCollapsed++; + }, + groupEnd: () => { + if (currentCollapsed > 0) currentCollapsed--; + else if (currentIndent.length >= 2) + currentIndent = currentIndent.slice(0, -2); + }, + profile: console.profile && (name => console.profile(name)), + profileEnd: console.profileEnd && (name => console.profileEnd(name)), + clear: + /** @type {() => void} */ + ( + !appendOnly && + console.clear && + (() => { + clearStatusMessage(); + console.clear(); + writeStatusMessage(); + }) + ), + status: appendOnly + ? writeColored(" ", "", "") + : (name, ...args) => { + args = args.filter(Boolean); + if (name === undefined && args.length === 0) { + clearStatusMessage(); + currentStatusMessage = undefined; + } else if ( + typeof name === "string" && + name.startsWith("[webpack.Progress] ") + ) { + currentStatusMessage = [name.slice(19), ...args]; + writeStatusMessage(); + } else if (name === "[webpack.Progress]") { + currentStatusMessage = [...args]; + writeStatusMessage(); + } else { + currentStatusMessage = [name, ...args]; + writeStatusMessage(); + } + } + }; +}; diff --git a/webpack-lib/lib/optimize/AggressiveMergingPlugin.js b/webpack-lib/lib/optimize/AggressiveMergingPlugin.js new file mode 100644 index 00000000000..e0d766a7db0 --- /dev/null +++ b/webpack-lib/lib/optimize/AggressiveMergingPlugin.js @@ -0,0 +1,98 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_ADVANCED } = require("../OptimizationStages"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} AggressiveMergingPluginOptions + * @property {number=} minSizeReduce minimal size reduction to trigger merging + */ + +class AggressiveMergingPlugin { + /** + * @param {AggressiveMergingPluginOptions=} [options] options object + */ + constructor(options) { + if ( + (options !== undefined && typeof options !== "object") || + Array.isArray(options) + ) { + throw new Error( + "Argument should be an options object. To use defaults, pass in nothing.\nFor more info on options, see https://webpack.js.org/plugins/" + ); + } + this.options = options || {}; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + const minSizeReduce = options.minSizeReduce || 1.5; + + compiler.hooks.thisCompilation.tap( + "AggressiveMergingPlugin", + compilation => { + compilation.hooks.optimizeChunks.tap( + { + name: "AggressiveMergingPlugin", + stage: STAGE_ADVANCED + }, + chunks => { + const chunkGraph = compilation.chunkGraph; + /** @type {{a: Chunk, b: Chunk, improvement: number}[]} */ + const combinations = []; + for (const a of chunks) { + if (a.canBeInitial()) continue; + for (const b of chunks) { + if (b.canBeInitial()) continue; + if (b === a) break; + if (!chunkGraph.canChunksBeIntegrated(a, b)) { + continue; + } + const aSize = chunkGraph.getChunkSize(b, { + chunkOverhead: 0 + }); + const bSize = chunkGraph.getChunkSize(a, { + chunkOverhead: 0 + }); + const abSize = chunkGraph.getIntegratedChunksSize(b, a, { + chunkOverhead: 0 + }); + const improvement = (aSize + bSize) / abSize; + combinations.push({ + a, + b, + improvement + }); + } + } + + combinations.sort((a, b) => b.improvement - a.improvement); + + const pair = combinations[0]; + + if (!pair) return; + if (pair.improvement < minSizeReduce) return; + + chunkGraph.integrateChunks(pair.b, pair.a); + compilation.chunks.delete(pair.a); + return true; + } + ); + } + ); + } +} + +module.exports = AggressiveMergingPlugin; diff --git a/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js b/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js new file mode 100644 index 00000000000..f17ec25297c --- /dev/null +++ b/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js @@ -0,0 +1,348 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_ADVANCED } = require("../OptimizationStages"); +const { intersect } = require("../util/SetHelpers"); +const { + compareModulesByIdentifier, + compareChunks +} = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); +const identifierUtils = require("../util/identifier"); + +/** @typedef {import("../../declarations/plugins/optimize/AggressiveSplittingPlugin").AggressiveSplittingPluginOptions} AggressiveSplittingPluginOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.check.js"), + () => + require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.json"), + { + name: "Aggressive Splitting Plugin", + baseDataPath: "options" + } +); + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Chunk} oldChunk the old chunk + * @param {Chunk} newChunk the new chunk + * @returns {(module: Module) => void} function to move module between chunks + */ +const moveModuleBetween = (chunkGraph, oldChunk, newChunk) => module => { + chunkGraph.disconnectChunkAndModule(oldChunk, module); + chunkGraph.connectChunkAndModule(newChunk, module); +}; + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Chunk} chunk the chunk + * @returns {function(Module): boolean} filter for entry module + */ +const isNotAEntryModule = (chunkGraph, chunk) => module => + !chunkGraph.isEntryModuleInChunk(module, chunk); + +/** @type {WeakSet} */ +const recordedChunks = new WeakSet(); + +class AggressiveSplittingPlugin { + /** + * @param {AggressiveSplittingPluginOptions=} options options object + */ + constructor(options = {}) { + validate(options); + + this.options = options; + if (typeof this.options.minSize !== "number") { + this.options.minSize = 30 * 1024; + } + if (typeof this.options.maxSize !== "number") { + this.options.maxSize = 50 * 1024; + } + if (typeof this.options.chunkOverhead !== "number") { + this.options.chunkOverhead = 0; + } + if (typeof this.options.entryChunkMultiplicator !== "number") { + this.options.entryChunkMultiplicator = 1; + } + } + + /** + * @param {Chunk} chunk the chunk to test + * @returns {boolean} true if the chunk was recorded + */ + static wasChunkRecorded(chunk) { + return recordedChunks.has(chunk); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "AggressiveSplittingPlugin", + compilation => { + let needAdditionalSeal = false; + /** @typedef {{ id?: NonNullable, hash?: NonNullable, modules: Module[], size: number }} SplitData */ + /** @type {SplitData[]} */ + let newSplits; + /** @type {Set} */ + let fromAggressiveSplittingSet; + /** @type {Map} */ + let chunkSplitDataMap; + compilation.hooks.optimize.tap("AggressiveSplittingPlugin", () => { + newSplits = []; + fromAggressiveSplittingSet = new Set(); + chunkSplitDataMap = new Map(); + }); + compilation.hooks.optimizeChunks.tap( + { + name: "AggressiveSplittingPlugin", + stage: STAGE_ADVANCED + }, + chunks => { + const chunkGraph = compilation.chunkGraph; + // Precompute stuff + const nameToModuleMap = new Map(); + const moduleToNameMap = new Map(); + const makePathsRelative = + identifierUtils.makePathsRelative.bindContextCache( + compiler.context, + compiler.root + ); + for (const m of compilation.modules) { + const name = makePathsRelative(m.identifier()); + nameToModuleMap.set(name, m); + moduleToNameMap.set(m, name); + } + + // Check used chunk ids + const usedIds = new Set(); + for (const chunk of chunks) { + usedIds.add(chunk.id); + } + + const recordedSplits = + (compilation.records && compilation.records.aggressiveSplits) || + []; + const usedSplits = newSplits + ? recordedSplits.concat(newSplits) + : recordedSplits; + + const minSize = /** @type {number} */ (this.options.minSize); + const maxSize = /** @type {number} */ (this.options.maxSize); + + /** + * @param {SplitData} splitData split data + * @returns {boolean} true when applied, otherwise false + */ + const applySplit = splitData => { + // Cannot split if id is already taken + if (splitData.id !== undefined && usedIds.has(splitData.id)) { + return false; + } + + // Get module objects from names + const selectedModules = splitData.modules.map(name => + nameToModuleMap.get(name) + ); + + // Does the modules exist at all? + if (!selectedModules.every(Boolean)) return false; + + // Check if size matches (faster than waiting for hash) + let size = 0; + for (const m of selectedModules) size += m.size(); + if (size !== splitData.size) return false; + + // get chunks with all modules + const selectedChunks = intersect( + selectedModules.map( + m => new Set(chunkGraph.getModuleChunksIterable(m)) + ) + ); + + // No relevant chunks found + if (selectedChunks.size === 0) return false; + + // The found chunk is already the split or similar + if ( + selectedChunks.size === 1 && + chunkGraph.getNumberOfChunkModules( + Array.from(selectedChunks)[0] + ) === selectedModules.length + ) { + const chunk = Array.from(selectedChunks)[0]; + if (fromAggressiveSplittingSet.has(chunk)) return false; + fromAggressiveSplittingSet.add(chunk); + chunkSplitDataMap.set(chunk, splitData); + return true; + } + + // split the chunk into two parts + const newChunk = compilation.addChunk(); + newChunk.chunkReason = "aggressive splitted"; + for (const chunk of selectedChunks) { + for (const module of selectedModules) { + moveModuleBetween(chunkGraph, chunk, newChunk)(module); + } + chunk.split(newChunk); + chunk.name = /** @type {TODO} */ (null); + } + fromAggressiveSplittingSet.add(newChunk); + chunkSplitDataMap.set(newChunk, splitData); + + if (splitData.id !== null && splitData.id !== undefined) { + newChunk.id = splitData.id; + newChunk.ids = [splitData.id]; + } + return true; + }; + + // try to restore to recorded splitting + let changed = false; + for (let j = 0; j < usedSplits.length; j++) { + const splitData = usedSplits[j]; + if (applySplit(splitData)) changed = true; + } + + // for any chunk which isn't splitted yet, split it and create a new entry + // start with the biggest chunk + const cmpFn = compareChunks(chunkGraph); + const sortedChunks = Array.from(chunks).sort((a, b) => { + const diff1 = + chunkGraph.getChunkModulesSize(b) - + chunkGraph.getChunkModulesSize(a); + if (diff1) return diff1; + const diff2 = + chunkGraph.getNumberOfChunkModules(a) - + chunkGraph.getNumberOfChunkModules(b); + if (diff2) return diff2; + return cmpFn(a, b); + }); + for (const chunk of sortedChunks) { + if (fromAggressiveSplittingSet.has(chunk)) continue; + const size = chunkGraph.getChunkModulesSize(chunk); + if ( + size > maxSize && + chunkGraph.getNumberOfChunkModules(chunk) > 1 + ) { + const modules = chunkGraph + .getOrderedChunkModules(chunk, compareModulesByIdentifier) + .filter(isNotAEntryModule(chunkGraph, chunk)); + const selectedModules = []; + let selectedModulesSize = 0; + for (let k = 0; k < modules.length; k++) { + const module = modules[k]; + const newSize = selectedModulesSize + module.size(); + if (newSize > maxSize && selectedModulesSize >= minSize) { + break; + } + selectedModulesSize = newSize; + selectedModules.push(module); + } + if (selectedModules.length === 0) continue; + /** @type {SplitData} */ + const splitData = { + modules: selectedModules + .map(m => moduleToNameMap.get(m)) + .sort(), + size: selectedModulesSize + }; + + if (applySplit(splitData)) { + newSplits = (newSplits || []).concat(splitData); + changed = true; + } + } + } + if (changed) return true; + } + ); + compilation.hooks.recordHash.tap( + "AggressiveSplittingPlugin", + records => { + // 4. save made splittings to records + const allSplits = new Set(); + /** @type {Set} */ + const invalidSplits = new Set(); + + // Check if some splittings are invalid + // We remove invalid splittings and try again + for (const chunk of compilation.chunks) { + const splitData = chunkSplitDataMap.get(chunk); + if ( + splitData !== undefined && + splitData.hash && + chunk.hash !== splitData.hash + ) { + // Split was successful, but hash doesn't equal + // We can throw away the split since it's useless now + invalidSplits.add(splitData); + } + } + + if (invalidSplits.size > 0) { + records.aggressiveSplits = + /** @type {SplitData[]} */ + (records.aggressiveSplits).filter( + splitData => !invalidSplits.has(splitData) + ); + needAdditionalSeal = true; + } else { + // set hash and id values on all (new) splittings + for (const chunk of compilation.chunks) { + const splitData = chunkSplitDataMap.get(chunk); + if (splitData !== undefined) { + splitData.hash = + /** @type {NonNullable} */ + (chunk.hash); + splitData.id = + /** @type {NonNullable} */ + (chunk.id); + allSplits.add(splitData); + // set flag for stats + recordedChunks.add(chunk); + } + } + + // Also add all unused historical splits (after the used ones) + // They can still be used in some future compilation + const recordedSplits = + compilation.records && compilation.records.aggressiveSplits; + if (recordedSplits) { + for (const splitData of recordedSplits) { + if (!invalidSplits.has(splitData)) allSplits.add(splitData); + } + } + + // record all splits + records.aggressiveSplits = Array.from(allSplits); + + needAdditionalSeal = false; + } + } + ); + compilation.hooks.needAdditionalSeal.tap( + "AggressiveSplittingPlugin", + () => { + if (needAdditionalSeal) { + needAdditionalSeal = false; + return true; + } + } + ); + } + ); + } +} +module.exports = AggressiveSplittingPlugin; diff --git a/webpack-lib/lib/optimize/ConcatenatedModule.js b/webpack-lib/lib/optimize/ConcatenatedModule.js new file mode 100644 index 00000000000..c305c3ddedf --- /dev/null +++ b/webpack-lib/lib/optimize/ConcatenatedModule.js @@ -0,0 +1,1904 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const eslintScope = require("eslint-scope"); +const Referencer = require("eslint-scope/lib/referencer"); +const { SyncBailHook } = require("tapable"); +const { + CachedSource, + ConcatSource, + ReplaceSource +} = require("webpack-sources"); +const ConcatenationScope = require("../ConcatenationScope"); +const { UsageState } = require("../ExportsInfo"); +const Module = require("../Module"); +const { JS_TYPES } = require("../ModuleSourceTypesConstants"); +const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); +const JavascriptParser = require("../javascript/JavascriptParser"); +const { equals } = require("../util/ArrayHelpers"); +const LazySet = require("../util/LazySet"); +const { concatComparators } = require("../util/comparators"); +const { + RESERVED_NAMES, + findNewName, + addScopeSymbols, + getAllReferences, + getPathInAst, + getUsedNamesInScopeInfo +} = require("../util/concatenate"); +const createHash = require("../util/createHash"); +const { makePathsRelative } = require("../util/identifier"); +const makeSerializable = require("../util/makeSerializable"); +const propertyAccess = require("../util/propertyAccess"); +const { propertyName } = require("../util/propertyName"); +const { + filterRuntime, + intersectRuntime, + mergeRuntimeCondition, + mergeRuntimeConditionNonFalse, + runtimeConditionToString, + subtractRuntimeCondition +} = require("../util/runtime"); + +/** @typedef {import("eslint-scope").Reference} Reference */ +/** @typedef {import("eslint-scope").Scope} Scope */ +/** @typedef {import("eslint-scope").Variable} Variable */ +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../ModuleParseError")} ModuleParseError */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ +/** @typedef {import("../javascript/JavascriptParser").Program} Program */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @typedef {typeof import("../util/Hash")} HashConstructor */ +/** @typedef {import("../util/concatenate").UsedNames} UsedNames */ +/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @template T + * @typedef {import("../InitFragment")} InitFragment + */ + +/** + * @template T + * @typedef {import("../util/comparators").Comparator} Comparator + */ + +// fix eslint-scope to support class properties correctly +// cspell:word Referencer +const ReferencerClass = /** @type {any} */ (Referencer); +if (!ReferencerClass.prototype.PropertyDefinition) { + ReferencerClass.prototype.PropertyDefinition = + ReferencerClass.prototype.Property; +} + +/** + * @typedef {object} ReexportInfo + * @property {Module} module + * @property {string[]} export + */ + +/** @typedef {RawBinding | SymbolBinding} Binding */ + +/** + * @typedef {object} RawBinding + * @property {ModuleInfo} info + * @property {string} rawName + * @property {string=} comment + * @property {string[]} ids + * @property {string[]} exportName + */ + +/** + * @typedef {object} SymbolBinding + * @property {ConcatenatedModuleInfo} info + * @property {string} name + * @property {string=} comment + * @property {string[]} ids + * @property {string[]} exportName + */ + +/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo } ModuleInfo */ +/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo | ReferenceToModuleInfo } ModuleInfoOrReference */ + +/** + * @typedef {object} ConcatenatedModuleInfo + * @property {"concatenated"} type + * @property {Module} module + * @property {number} index + * @property {Program | undefined} ast + * @property {Source | undefined} internalSource + * @property {ReplaceSource | undefined} source + * @property {InitFragment[]=} chunkInitFragments + * @property {ReadOnlyRuntimeRequirements | undefined} runtimeRequirements + * @property {Scope | undefined} globalScope + * @property {Scope | undefined} moduleScope + * @property {Map} internalNames + * @property {Map | undefined} exportMap + * @property {Map | undefined} rawExportMap + * @property {string=} namespaceExportSymbol + * @property {string | undefined} namespaceObjectName + * @property {boolean} interopNamespaceObjectUsed + * @property {string | undefined} interopNamespaceObjectName + * @property {boolean} interopNamespaceObject2Used + * @property {string | undefined} interopNamespaceObject2Name + * @property {boolean} interopDefaultAccessUsed + * @property {string | undefined} interopDefaultAccessName + */ + +/** + * @typedef {object} ExternalModuleInfo + * @property {"external"} type + * @property {Module} module + * @property {RuntimeSpec | boolean} runtimeCondition + * @property {number} index + * @property {string | undefined} name + * @property {boolean} interopNamespaceObjectUsed + * @property {string | undefined} interopNamespaceObjectName + * @property {boolean} interopNamespaceObject2Used + * @property {string | undefined} interopNamespaceObject2Name + * @property {boolean} interopDefaultAccessUsed + * @property {string | undefined} interopDefaultAccessName + */ + +/** + * @typedef {object} ReferenceToModuleInfo + * @property {"reference"} type + * @property {RuntimeSpec | boolean} runtimeCondition + * @property {ModuleInfo} target + */ + +/** + * @template T + * @param {string} property property + * @param {function(T[keyof T], T[keyof T]): 0 | 1 | -1} comparator comparator + * @returns {Comparator} comparator + */ + +const createComparator = (property, comparator) => (a, b) => + comparator( + a[/** @type {keyof T} */ (property)], + b[/** @type {keyof T} */ (property)] + ); + +/** + * @param {number} a a + * @param {number} b b + * @returns {0 | 1 | -1} result + */ +const compareNumbers = (a, b) => { + if (Number.isNaN(a)) { + if (!Number.isNaN(b)) { + return 1; + } + } else { + if (Number.isNaN(b)) { + return -1; + } + if (a !== b) { + return a < b ? -1 : 1; + } + } + return 0; +}; +const bySourceOrder = createComparator("sourceOrder", compareNumbers); +const byRangeStart = createComparator("rangeStart", compareNumbers); + +/** + * @param {Iterable} iterable iterable object + * @returns {string} joined iterable object + */ +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; +}; + +/** + * @typedef {object} ConcatenationEntry + * @property {"concatenated" | "external"} type + * @property {Module} module + * @property {RuntimeSpec | boolean} runtimeCondition + */ + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {ModuleInfo} info module info + * @param {string[]} exportName exportName + * @param {Map} moduleToInfoMap moduleToInfoMap + * @param {RuntimeSpec} runtime for which runtime + * @param {RequestShortener} requestShortener the request shortener + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated + * @param {boolean} asCall asCall + * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule + * @param {boolean | undefined} asiSafe asiSafe + * @param {Set} alreadyVisited alreadyVisited + * @returns {Binding} the final variable + */ +const getFinalBinding = ( + moduleGraph, + info, + exportName, + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + asCall, + strictHarmonyModule, + asiSafe, + alreadyVisited = new Set() +) => { + const exportsType = info.module.getExportsType( + moduleGraph, + strictHarmonyModule + ); + if (exportName.length === 0) { + switch (exportsType) { + case "default-only": + info.interopNamespaceObject2Used = true; + return { + info, + rawName: /** @type {string} */ (info.interopNamespaceObject2Name), + ids: exportName, + exportName + }; + case "default-with-named": + info.interopNamespaceObjectUsed = true; + return { + info, + rawName: /** @type {string} */ (info.interopNamespaceObjectName), + ids: exportName, + exportName + }; + case "namespace": + case "dynamic": + break; + default: + throw new Error(`Unexpected exportsType ${exportsType}`); + } + } else { + switch (exportsType) { + case "namespace": + break; + case "default-with-named": + switch (exportName[0]) { + case "default": + exportName = exportName.slice(1); + break; + case "__esModule": + return { + info, + rawName: "/* __esModule */true", + ids: exportName.slice(1), + exportName + }; + } + break; + case "default-only": { + const exportId = exportName[0]; + if (exportId === "__esModule") { + return { + info, + rawName: "/* __esModule */true", + ids: exportName.slice(1), + exportName + }; + } + exportName = exportName.slice(1); + if (exportId !== "default") { + return { + info, + rawName: + "/* non-default import from default-exporting module */undefined", + ids: exportName, + exportName + }; + } + break; + } + case "dynamic": + switch (exportName[0]) { + case "default": { + exportName = exportName.slice(1); + info.interopDefaultAccessUsed = true; + const defaultExport = asCall + ? `${info.interopDefaultAccessName}()` + : asiSafe + ? `(${info.interopDefaultAccessName}())` + : asiSafe === false + ? `;(${info.interopDefaultAccessName}())` + : `${info.interopDefaultAccessName}.a`; + return { + info, + rawName: defaultExport, + ids: exportName, + exportName + }; + } + case "__esModule": + return { + info, + rawName: "/* __esModule */true", + ids: exportName.slice(1), + exportName + }; + } + break; + default: + throw new Error(`Unexpected exportsType ${exportsType}`); + } + } + if (exportName.length === 0) { + switch (info.type) { + case "concatenated": + neededNamespaceObjects.add(info); + return { + info, + rawName: + /** @type {NonNullable} */ + (info.namespaceObjectName), + ids: exportName, + exportName + }; + case "external": + return { + info, + rawName: + /** @type {NonNullable} */ + (info.name), + ids: exportName, + exportName + }; + } + } + const exportsInfo = moduleGraph.getExportsInfo(info.module); + const exportInfo = exportsInfo.getExportInfo(exportName[0]); + if (alreadyVisited.has(exportInfo)) { + return { + info, + rawName: "/* circular reexport */ Object(function x() { x() }())", + ids: [], + exportName + }; + } + alreadyVisited.add(exportInfo); + switch (info.type) { + case "concatenated": { + const exportId = exportName[0]; + if (exportInfo.provided === false) { + // It's not provided, but it could be on the prototype + neededNamespaceObjects.add(info); + return { + info, + rawName: /** @type {string} */ (info.namespaceObjectName), + ids: exportName, + exportName + }; + } + const directExport = info.exportMap && info.exportMap.get(exportId); + if (directExport) { + const usedName = /** @type {string[]} */ ( + exportsInfo.getUsedName(exportName, runtime) + ); + if (!usedName) { + return { + info, + rawName: "/* unused export */ undefined", + ids: exportName.slice(1), + exportName + }; + } + return { + info, + name: directExport, + ids: usedName.slice(1), + exportName + }; + } + const rawExport = info.rawExportMap && info.rawExportMap.get(exportId); + if (rawExport) { + return { + info, + rawName: rawExport, + ids: exportName.slice(1), + exportName + }; + } + const reexport = exportInfo.findTarget(moduleGraph, module => + moduleToInfoMap.has(module) + ); + if (reexport === false) { + throw new Error( + `Target module of reexport from '${info.module.readableIdentifier( + requestShortener + )}' is not part of the concatenation (export '${exportId}')\nModules in the concatenation:\n${Array.from( + moduleToInfoMap, + ([m, info]) => + ` * ${info.type} ${m.readableIdentifier(requestShortener)}` + ).join("\n")}` + ); + } + if (reexport) { + const refInfo = moduleToInfoMap.get(reexport.module); + return getFinalBinding( + moduleGraph, + /** @type {ModuleInfo} */ (refInfo), + reexport.export + ? [...reexport.export, ...exportName.slice(1)] + : exportName.slice(1), + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + asCall, + /** @type {BuildMeta} */ + (info.module.buildMeta).strictHarmonyModule, + asiSafe, + alreadyVisited + ); + } + if (info.namespaceExportSymbol) { + const usedName = /** @type {string[]} */ ( + exportsInfo.getUsedName(exportName, runtime) + ); + return { + info, + rawName: /** @type {string} */ (info.namespaceObjectName), + ids: usedName, + exportName + }; + } + throw new Error( + `Cannot get final name for export '${exportName.join( + "." + )}' of ${info.module.readableIdentifier(requestShortener)}` + ); + } + + case "external": { + const used = /** @type {string[]} */ ( + exportsInfo.getUsedName(exportName, runtime) + ); + if (!used) { + return { + info, + rawName: "/* unused export */ undefined", + ids: exportName.slice(1), + exportName + }; + } + const comment = equals(used, exportName) + ? "" + : Template.toNormalComment(`${exportName.join(".")}`); + return { info, rawName: info.name + comment, ids: used, exportName }; + } + } +}; + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {ModuleInfo} info module info + * @param {string[]} exportName exportName + * @param {Map} moduleToInfoMap moduleToInfoMap + * @param {RuntimeSpec} runtime for which runtime + * @param {RequestShortener} requestShortener the request shortener + * @param {RuntimeTemplate} runtimeTemplate the runtime template + * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated + * @param {boolean} asCall asCall + * @param {boolean | undefined} callContext callContext + * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule + * @param {boolean | undefined} asiSafe asiSafe + * @returns {string} the final name + */ +const getFinalName = ( + moduleGraph, + info, + exportName, + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + asCall, + callContext, + strictHarmonyModule, + asiSafe +) => { + const binding = getFinalBinding( + moduleGraph, + info, + exportName, + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + asCall, + strictHarmonyModule, + asiSafe + ); + { + const { ids, comment } = binding; + let reference; + let isPropertyAccess; + if ("rawName" in binding) { + reference = `${binding.rawName}${comment || ""}${propertyAccess(ids)}`; + isPropertyAccess = ids.length > 0; + } else { + const { info, name: exportId } = binding; + const name = info.internalNames.get(exportId); + if (!name) { + throw new Error( + `The export "${exportId}" in "${info.module.readableIdentifier( + requestShortener + )}" has no internal name (existing names: ${ + Array.from( + info.internalNames, + ([name, symbol]) => `${name}: ${symbol}` + ).join(", ") || "none" + })` + ); + } + reference = `${name}${comment || ""}${propertyAccess(ids)}`; + isPropertyAccess = ids.length > 1; + } + if (isPropertyAccess && asCall && callContext === false) { + return asiSafe + ? `(0,${reference})` + : asiSafe === false + ? `;(0,${reference})` + : `/*#__PURE__*/Object(${reference})`; + } + return reference; + } +}; + +/** + * @typedef {object} ConcatenateModuleHooks + * @property {SyncBailHook<[Record], boolean | void>} exportsDefinitions + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class ConcatenatedModule extends Module { + /** + * @param {Module} rootModule the root module of the concatenation + * @param {Set} modules all modules in the concatenation (including the root module) + * @param {RuntimeSpec} runtime the runtime + * @param {Compilation} compilation the compilation + * @param {object=} associatedObjectForCache object for caching + * @param {string | HashConstructor=} hashFunction hash function to use + * @returns {ConcatenatedModule} the module + */ + static create( + rootModule, + modules, + runtime, + compilation, + associatedObjectForCache, + hashFunction = "md4" + ) { + const identifier = ConcatenatedModule._createIdentifier( + rootModule, + modules, + associatedObjectForCache, + hashFunction + ); + return new ConcatenatedModule({ + identifier, + rootModule, + modules, + runtime, + compilation + }); + } + + /** + * @param {Compilation} compilation the compilation + * @returns {ConcatenateModuleHooks} the attached hooks + */ + static getCompilationHooks(compilation) { + let hooks = compilationHooksMap.get(compilation); + if (hooks === undefined) { + hooks = { + exportsDefinitions: new SyncBailHook(["definitions"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {object} options options + * @param {string} options.identifier the identifier of the module + * @param {Module} options.rootModule the root module of the concatenation + * @param {RuntimeSpec} options.runtime the selected runtime + * @param {Set} options.modules all concatenated modules + * @param {Compilation} options.compilation the compilation + */ + constructor({ identifier, rootModule, modules, runtime, compilation }) { + super(JAVASCRIPT_MODULE_TYPE_ESM, null, rootModule && rootModule.layer); + + // Info from Factory + /** @type {string} */ + this._identifier = identifier; + /** @type {Module} */ + this.rootModule = rootModule; + /** @type {Set} */ + this._modules = modules; + this._runtime = runtime; + this.factoryMeta = rootModule && rootModule.factoryMeta; + /** @type {Compilation | undefined} */ + this.compilation = compilation; + } + + /** + * 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) { + throw new Error("Must not be called"); + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return JS_TYPES; + } + + get modules() { + return Array.from(this._modules); + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return this._identifier; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `${this.rootModule.readableIdentifier( + requestShortener + )} + ${this._modules.size - 1} modules`; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return this.rootModule.libIdent(options); + } + + /** + * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) + */ + nameForCondition() { + return this.rootModule.nameForCondition(); + } + + /** + * @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 this.rootModule.getSideEffectsConnectionState(moduleGraph); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + const { rootModule } = this; + const { moduleArgument, exportsArgument } = + /** @type {BuildInfo} */ + (rootModule.buildInfo); + this.buildInfo = { + strict: true, + cacheable: true, + moduleArgument, + exportsArgument, + fileDependencies: new LazySet(), + contextDependencies: new LazySet(), + missingDependencies: new LazySet(), + topLevelDeclarations: new Set(), + assets: undefined + }; + this.buildMeta = rootModule.buildMeta; + this.clearDependenciesAndBlocks(); + this.clearWarningsAndErrors(); + + for (const m of this._modules) { + // populate cacheable + if (!(/** @type {BuildInfo} */ (m.buildInfo).cacheable)) { + this.buildInfo.cacheable = false; + } + + // populate dependencies + for (const d of m.dependencies.filter( + dep => + !(dep instanceof HarmonyImportDependency) || + !this._modules.has( + /** @type {Module} */ (compilation.moduleGraph.getModule(dep)) + ) + )) { + this.dependencies.push(d); + } + // populate blocks + for (const d of m.blocks) { + this.blocks.push(d); + } + + // populate warnings + const warnings = m.getWarnings(); + if (warnings !== undefined) { + for (const warning of warnings) { + this.addWarning(warning); + } + } + + // populate errors + const errors = m.getErrors(); + if (errors !== undefined) { + for (const error of errors) { + this.addError(error); + } + } + + const { assets, assetsInfo, topLevelDeclarations } = + /** @type {BuildInfo} */ (m.buildInfo); + + // populate topLevelDeclarations + if (topLevelDeclarations) { + const topLevelDeclarations = this.buildInfo.topLevelDeclarations; + if (topLevelDeclarations !== undefined) { + for (const decl of topLevelDeclarations) { + topLevelDeclarations.add(decl); + } + } + } else { + this.buildInfo.topLevelDeclarations = undefined; + } + + // populate assets + if (assets) { + if (this.buildInfo.assets === undefined) { + this.buildInfo.assets = Object.create(null); + } + Object.assign( + /** @type {NonNullable} */ + ( + /** @type {BuildInfo} */ + (this.buildInfo).assets + ), + assets + ); + } + if (assetsInfo) { + if (this.buildInfo.assetsInfo === undefined) { + this.buildInfo.assetsInfo = new Map(); + } + for (const [key, value] of assetsInfo) { + this.buildInfo.assetsInfo.set(key, value); + } + } + } + callback(); + } + + /** + * @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) { + // Guess size from embedded modules + let size = 0; + for (const module of this._modules) { + size += module.size(type); + } + return size; + } + + /** + * @private + * @param {Module} rootModule the root of the concatenation + * @param {Set} modulesSet a set of modules which should be concatenated + * @param {RuntimeSpec} runtime for this runtime + * @param {ModuleGraph} moduleGraph the module graph + * @returns {ConcatenationEntry[]} concatenation list + */ + _createConcatenationList(rootModule, modulesSet, runtime, moduleGraph) { + /** @type {ConcatenationEntry[]} */ + const list = []; + /** @type {Map} */ + const existingEntries = new Map(); + + /** + * @param {Module} module a module + * @returns {Iterable<{ connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true }>} imported modules in order + */ + const getConcatenatedImports = module => { + const connections = Array.from( + moduleGraph.getOutgoingConnections(module) + ); + if (module === rootModule) { + for (const c of moduleGraph.getOutgoingConnections(this)) + connections.push(c); + } + /** + * @type {Array<{ connection: ModuleGraphConnection, sourceOrder: number, rangeStart: number }>} + */ + const references = connections + .filter(connection => { + if (!(connection.dependency instanceof HarmonyImportDependency)) + return false; + return ( + connection && + connection.resolvedOriginModule === module && + connection.module && + connection.isTargetActive(runtime) + ); + }) + .map(connection => { + const dep = /** @type {HarmonyImportDependency} */ ( + connection.dependency + ); + return { + connection, + sourceOrder: dep.sourceOrder, + rangeStart: dep.range && dep.range[0] + }; + }); + /** + * bySourceOrder + * @example + * import a from "a"; // sourceOrder=1 + * import b from "b"; // sourceOrder=2 + * + * byRangeStart + * @example + * import {a, b} from "a"; // sourceOrder=1 + * a.a(); // first range + * b.b(); // second range + * + * If there is no reexport, we have the same source. + * If there is reexport, but module has side effects, this will lead to reexport module only. + * If there is side-effects-free reexport, we can get simple deterministic result with range start comparison. + */ + references.sort(concatComparators(bySourceOrder, byRangeStart)); + /** @type {Map} */ + const referencesMap = new Map(); + for (const { connection } of references) { + const runtimeCondition = filterRuntime(runtime, r => + connection.isTargetActive(r) + ); + if (runtimeCondition === false) continue; + const module = connection.module; + const entry = referencesMap.get(module); + if (entry === undefined) { + referencesMap.set(module, { connection, runtimeCondition }); + continue; + } + entry.runtimeCondition = mergeRuntimeConditionNonFalse( + entry.runtimeCondition, + runtimeCondition, + runtime + ); + } + return referencesMap.values(); + }; + + /** + * @param {ModuleGraphConnection} connection graph connection + * @param {RuntimeSpec | true} runtimeCondition runtime condition + * @returns {void} + */ + const enterModule = (connection, runtimeCondition) => { + const module = connection.module; + if (!module) return; + const existingEntry = existingEntries.get(module); + if (existingEntry === true) { + return; + } + if (modulesSet.has(module)) { + existingEntries.set(module, true); + if (runtimeCondition !== true) { + throw new Error( + `Cannot runtime-conditional concatenate a module (${module.identifier()} in ${this.rootModule.identifier()}, ${runtimeConditionToString( + runtimeCondition + )}). This should not happen.` + ); + } + const imports = getConcatenatedImports(module); + for (const { connection, runtimeCondition } of imports) + enterModule(connection, runtimeCondition); + list.push({ + type: "concatenated", + module: connection.module, + runtimeCondition + }); + } else { + if (existingEntry !== undefined) { + const reducedRuntimeCondition = subtractRuntimeCondition( + runtimeCondition, + existingEntry, + runtime + ); + if (reducedRuntimeCondition === false) return; + runtimeCondition = reducedRuntimeCondition; + existingEntries.set( + connection.module, + mergeRuntimeConditionNonFalse( + existingEntry, + runtimeCondition, + runtime + ) + ); + } else { + existingEntries.set(connection.module, runtimeCondition); + } + if (list.length > 0) { + const lastItem = list[list.length - 1]; + if ( + lastItem.type === "external" && + lastItem.module === connection.module + ) { + lastItem.runtimeCondition = mergeRuntimeCondition( + lastItem.runtimeCondition, + runtimeCondition, + runtime + ); + return; + } + } + list.push({ + type: "external", + get module() { + // We need to use a getter here, because the module in the dependency + // could be replaced by some other process (i. e. also replaced with a + // concatenated module) + return connection.module; + }, + runtimeCondition + }); + } + }; + + existingEntries.set(rootModule, true); + const imports = getConcatenatedImports(rootModule); + for (const { connection, runtimeCondition } of imports) + enterModule(connection, runtimeCondition); + list.push({ + type: "concatenated", + module: rootModule, + runtimeCondition: true + }); + + return list; + } + + /** + * @param {Module} rootModule the root module of the concatenation + * @param {Set} modules all modules in the concatenation (including the root module) + * @param {object=} associatedObjectForCache object for caching + * @param {string | HashConstructor=} hashFunction hash function to use + * @returns {string} the identifier + */ + static _createIdentifier( + rootModule, + modules, + associatedObjectForCache, + hashFunction = "md4" + ) { + const cachedMakePathsRelative = makePathsRelative.bindContextCache( + /** @type {string} */ (rootModule.context), + associatedObjectForCache + ); + const identifiers = []; + for (const module of modules) { + identifiers.push(cachedMakePathsRelative(module.identifier())); + } + identifiers.sort(); + const hash = createHash(hashFunction); + hash.update(identifiers.join(" ")); + return `${rootModule.identifier()}|${hash.digest("hex")}`; + } + + /** + * @param {LazySet} fileDependencies set where file dependencies are added to + * @param {LazySet} contextDependencies set where context dependencies are added to + * @param {LazySet} missingDependencies set where missing dependencies are added to + * @param {LazySet} buildDependencies set where build dependencies are added to + */ + addCacheDependencies( + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + ) { + for (const module of this._modules) { + module.addCacheDependencies( + fileDependencies, + contextDependencies, + missingDependencies, + buildDependencies + ); + } + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime: generationRuntime, + codeGenerationResults + }) { + /** @type {RuntimeRequirements} */ + const runtimeRequirements = new Set(); + const runtime = intersectRuntime(generationRuntime, this._runtime); + + const requestShortener = runtimeTemplate.requestShortener; + // Meta info for each module + const [modulesWithInfo, moduleToInfoMap] = this._getModulesWithInfo( + moduleGraph, + runtime + ); + + // Set with modules that need a generated namespace object + /** @type {Set} */ + const neededNamespaceObjects = new Set(); + + // Generate source code and analyse scopes + // Prepare a ReplaceSource for the final source + for (const info of moduleToInfoMap.values()) { + this._analyseModule( + moduleToInfoMap, + info, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime, + /** @type {CodeGenerationResults} */ + (codeGenerationResults) + ); + } + + // List of all used names to avoid conflicts + const allUsedNames = new Set(RESERVED_NAMES); + // Updated Top level declarations are created by renaming + const topLevelDeclarations = new Set(); + + // List of additional names in scope for module references + /** @type {Map }>} */ + const usedNamesInScopeInfo = new Map(); + /** + * @param {string} module module identifier + * @param {string} id export id + * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set }} info + */ + + // Set of already checked scopes + const ignoredScopes = new Set(); + + // get all global names + for (const info of modulesWithInfo) { + if (info.type === "concatenated") { + // ignore symbols from moduleScope + if (info.moduleScope) { + ignoredScopes.add(info.moduleScope); + } + + // The super class expression in class scopes behaves weird + // We get ranges of all super class expressions to make + // renaming to work correctly + const superClassCache = new WeakMap(); + /** + * @param {Scope} scope scope + * @returns {TODO} result + */ + const getSuperClassExpressions = scope => { + const cacheEntry = superClassCache.get(scope); + if (cacheEntry !== undefined) return cacheEntry; + const superClassExpressions = []; + for (const childScope of scope.childScopes) { + if (childScope.type !== "class") continue; + const block = childScope.block; + if ( + (block.type === "ClassDeclaration" || + block.type === "ClassExpression") && + block.superClass + ) { + superClassExpressions.push({ + range: block.superClass.range, + variables: childScope.variables + }); + } + } + superClassCache.set(scope, superClassExpressions); + return superClassExpressions; + }; + + // add global symbols + if (info.globalScope) { + for (const reference of info.globalScope.through) { + const name = reference.identifier.name; + if (ConcatenationScope.isModuleReference(name)) { + const match = ConcatenationScope.matchModuleReference(name); + if (!match) continue; + const referencedInfo = modulesWithInfo[match.index]; + if (referencedInfo.type === "reference") + throw new Error("Module reference can't point to a reference"); + const binding = getFinalBinding( + moduleGraph, + referencedInfo, + match.ids, + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + false, + /** @type {BuildMeta} */ + (info.module.buildMeta).strictHarmonyModule, + true + ); + if (!binding.ids) continue; + const { usedNames, alreadyCheckedScopes } = + getUsedNamesInScopeInfo( + usedNamesInScopeInfo, + binding.info.module.identifier(), + "name" in binding ? binding.name : "" + ); + for (const expr of getSuperClassExpressions(reference.from)) { + if ( + expr.range[0] <= + /** @type {Range} */ (reference.identifier.range)[0] && + expr.range[1] >= + /** @type {Range} */ (reference.identifier.range)[1] + ) { + for (const variable of expr.variables) { + usedNames.add(variable.name); + } + } + } + addScopeSymbols( + reference.from, + usedNames, + alreadyCheckedScopes, + ignoredScopes + ); + } else { + allUsedNames.add(name); + } + } + } + } + } + + // generate names for symbols + for (const info of moduleToInfoMap.values()) { + const { usedNames: namespaceObjectUsedNames } = getUsedNamesInScopeInfo( + usedNamesInScopeInfo, + info.module.identifier(), + "" + ); + switch (info.type) { + case "concatenated": { + const variables = /** @type {Scope} */ (info.moduleScope).variables; + for (const variable of variables) { + const name = variable.name; + const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( + usedNamesInScopeInfo, + info.module.identifier(), + name + ); + if (allUsedNames.has(name) || usedNames.has(name)) { + const references = getAllReferences(variable); + for (const ref of references) { + addScopeSymbols( + ref.from, + usedNames, + alreadyCheckedScopes, + ignoredScopes + ); + } + const newName = findNewName( + name, + allUsedNames, + usedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(newName); + info.internalNames.set(name, newName); + topLevelDeclarations.add(newName); + const source = /** @type {ReplaceSource} */ (info.source); + const allIdentifiers = new Set( + references.map(r => r.identifier).concat(variable.identifiers) + ); + for (const identifier of allIdentifiers) { + const r = /** @type {Range} */ (identifier.range); + const path = getPathInAst( + /** @type {NonNullable} */ + (info.ast), + identifier + ); + if (path && path.length > 1) { + const maybeProperty = + path[1].type === "AssignmentPattern" && + path[1].left === path[0] + ? path[2] + : path[1]; + if ( + maybeProperty.type === "Property" && + maybeProperty.shorthand + ) { + source.insert(r[1], `: ${newName}`); + continue; + } + } + source.replace(r[0], r[1] - 1, newName); + } + } else { + allUsedNames.add(name); + info.internalNames.set(name, name); + topLevelDeclarations.add(name); + } + } + let namespaceObjectName; + if (info.namespaceExportSymbol) { + namespaceObjectName = info.internalNames.get( + info.namespaceExportSymbol + ); + } else { + namespaceObjectName = findNewName( + "namespaceObject", + allUsedNames, + namespaceObjectUsedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(namespaceObjectName); + } + info.namespaceObjectName = + /** @type {string} */ + (namespaceObjectName); + topLevelDeclarations.add(namespaceObjectName); + break; + } + case "external": { + const externalName = findNewName( + "", + allUsedNames, + namespaceObjectUsedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(externalName); + info.name = externalName; + topLevelDeclarations.add(externalName); + break; + } + } + const buildMeta = /** @type {BuildMeta} */ (info.module.buildMeta); + if (buildMeta.exportsType !== "namespace") { + const externalNameInterop = findNewName( + "namespaceObject", + allUsedNames, + namespaceObjectUsedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(externalNameInterop); + info.interopNamespaceObjectName = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); + } + if ( + buildMeta.exportsType === "default" && + buildMeta.defaultObject !== "redirect" + ) { + const externalNameInterop = findNewName( + "namespaceObject2", + allUsedNames, + namespaceObjectUsedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(externalNameInterop); + info.interopNamespaceObject2Name = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); + } + if (buildMeta.exportsType === "dynamic" || !buildMeta.exportsType) { + const externalNameInterop = findNewName( + "default", + allUsedNames, + namespaceObjectUsedNames, + info.module.readableIdentifier(requestShortener) + ); + allUsedNames.add(externalNameInterop); + info.interopDefaultAccessName = externalNameInterop; + topLevelDeclarations.add(externalNameInterop); + } + } + + // Find and replace references to modules + for (const info of moduleToInfoMap.values()) { + if (info.type === "concatenated") { + const globalScope = /** @type {Scope} */ (info.globalScope); + for (const reference of globalScope.through) { + const name = reference.identifier.name; + const match = ConcatenationScope.matchModuleReference(name); + if (match) { + const referencedInfo = modulesWithInfo[match.index]; + if (referencedInfo.type === "reference") + throw new Error("Module reference can't point to a reference"); + const finalName = getFinalName( + moduleGraph, + referencedInfo, + match.ids, + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + match.call, + !match.directImport, + /** @type {BuildMeta} */ + (info.module.buildMeta).strictHarmonyModule, + match.asiSafe + ); + const r = /** @type {Range} */ (reference.identifier.range); + const source = /** @type {ReplaceSource} */ (info.source); + // range is extended by 2 chars to cover the appended "._" + source.replace(r[0], r[1] + 1, finalName); + } + } + } + } + + // Map with all root exposed used exports + /** @type {Map} */ + const exportsMap = new Map(); + + // Set with all root exposed unused exports + /** @type {Set} */ + const unusedExports = new Set(); + + const rootInfo = /** @type {ConcatenatedModuleInfo} */ ( + moduleToInfoMap.get(this.rootModule) + ); + const strictHarmonyModule = + /** @type {BuildMeta} */ + (rootInfo.module.buildMeta).strictHarmonyModule; + const exportsInfo = moduleGraph.getExportsInfo(rootInfo.module); + /** @type {Record} */ + const exportsFinalName = {}; + for (const exportInfo of exportsInfo.orderedExports) { + const name = exportInfo.name; + if (exportInfo.provided === false) continue; + const used = exportInfo.getUsedName(undefined, runtime); + if (!used) { + unusedExports.add(name); + continue; + } + exportsMap.set(used, requestShortener => { + try { + const finalName = getFinalName( + moduleGraph, + rootInfo, + [name], + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + false, + false, + strictHarmonyModule, + true + ); + exportsFinalName[used] = finalName; + return `/* ${ + exportInfo.isReexport() ? "reexport" : "binding" + } */ ${finalName}`; + } catch (err) { + /** @type {Error} */ + (err).message += + `\nwhile generating the root export '${name}' (used name: '${used}')`; + throw err; + } + }); + } + + const result = new ConcatSource(); + + // add harmony compatibility flag (must be first because of possible circular dependencies) + let shouldAddHarmonyFlag = false; + if ( + moduleGraph.getExportsInfo(this).otherExportsInfo.getUsed(runtime) !== + UsageState.Unused + ) { + shouldAddHarmonyFlag = true; + } + + // define exports + if (exportsMap.size > 0) { + const { exportsDefinitions } = ConcatenatedModule.getCompilationHooks( + /** @type {Compilation} */ (this.compilation) + ); + + const definitions = []; + for (const [key, value] of exportsMap) { + definitions.push( + `\n ${propertyName(key)}: ${runtimeTemplate.returningFunction( + value(requestShortener) + )}` + ); + } + const shouldSkipRenderDefinitions = + exportsDefinitions.call(exportsFinalName); + + if (!shouldSkipRenderDefinitions) { + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + + if (shouldAddHarmonyFlag) { + result.add("// ESM COMPAT FLAG\n"); + result.add( + runtimeTemplate.defineEsModuleFlagStatement({ + exportsArgument: this.exportsArgument, + runtimeRequirements + }) + ); + } + + result.add("\n// EXPORTS\n"); + result.add( + `${RuntimeGlobals.definePropertyGetters}(${ + this.exportsArgument + }, {${definitions.join(",")}\n});\n` + ); + } else { + /** @type {BuildMeta} */ + (this.buildMeta).exportsFinalName = exportsFinalName; + } + } + + // list unused exports + if (unusedExports.size > 0) { + result.add( + `\n// UNUSED EXPORTS: ${joinIterableWithComma(unusedExports)}\n` + ); + } + + // generate namespace objects + const namespaceObjectSources = new Map(); + for (const info of neededNamespaceObjects) { + if (info.namespaceExportSymbol) continue; + const nsObj = []; + const exportsInfo = moduleGraph.getExportsInfo(info.module); + for (const exportInfo of exportsInfo.orderedExports) { + if (exportInfo.provided === false) continue; + const usedName = exportInfo.getUsedName(undefined, runtime); + if (usedName) { + const finalName = getFinalName( + moduleGraph, + info, + [exportInfo.name], + moduleToInfoMap, + runtime, + requestShortener, + runtimeTemplate, + neededNamespaceObjects, + false, + undefined, + /** @type {BuildMeta} */ + (info.module.buildMeta).strictHarmonyModule, + true + ); + nsObj.push( + `\n ${propertyName(usedName)}: ${runtimeTemplate.returningFunction( + finalName + )}` + ); + } + } + const name = info.namespaceObjectName; + const defineGetters = + nsObj.length > 0 + ? `${RuntimeGlobals.definePropertyGetters}(${name}, {${nsObj.join( + "," + )}\n});\n` + : ""; + if (nsObj.length > 0) + runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); + namespaceObjectSources.set( + info, + ` +// NAMESPACE OBJECT: ${info.module.readableIdentifier(requestShortener)} +var ${name} = {}; +${RuntimeGlobals.makeNamespaceObject}(${name}); +${defineGetters}` + ); + runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); + } + + // define required namespace objects (must be before evaluation modules) + for (const info of modulesWithInfo) { + if (info.type === "concatenated") { + const source = namespaceObjectSources.get(info); + if (!source) continue; + result.add(source); + } + } + + const chunkInitFragments = []; + + // evaluate modules in order + for (const rawInfo of modulesWithInfo) { + let name; + let isConditional = false; + const info = rawInfo.type === "reference" ? rawInfo.target : rawInfo; + switch (info.type) { + case "concatenated": { + result.add( + `\n;// ${info.module.readableIdentifier(requestShortener)}\n` + ); + result.add(/** @type {ReplaceSource} */ (info.source)); + if (info.chunkInitFragments) { + for (const f of info.chunkInitFragments) chunkInitFragments.push(f); + } + if (info.runtimeRequirements) { + for (const r of info.runtimeRequirements) { + runtimeRequirements.add(r); + } + } + name = info.namespaceObjectName; + break; + } + case "external": { + result.add( + `\n// EXTERNAL MODULE: ${info.module.readableIdentifier( + requestShortener + )}\n` + ); + runtimeRequirements.add(RuntimeGlobals.require); + const { runtimeCondition } = + /** @type {ExternalModuleInfo | ReferenceToModuleInfo} */ (rawInfo); + const condition = runtimeTemplate.runtimeConditionExpression({ + chunkGraph, + runtimeCondition, + runtime, + runtimeRequirements + }); + if (condition !== "true") { + isConditional = true; + result.add(`if (${condition}) {\n`); + } + result.add( + `var ${info.name} = ${RuntimeGlobals.require}(${JSON.stringify( + chunkGraph.getModuleId(info.module) + )});` + ); + name = info.name; + break; + } + default: + // @ts-expect-error never is expected here + throw new Error(`Unsupported concatenation entry type ${info.type}`); + } + if (info.interopNamespaceObjectUsed) { + runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); + result.add( + `\nvar ${info.interopNamespaceObjectName} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name}, 2);` + ); + } + if (info.interopNamespaceObject2Used) { + runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); + result.add( + `\nvar ${info.interopNamespaceObject2Name} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name});` + ); + } + if (info.interopDefaultAccessUsed) { + runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport); + result.add( + `\nvar ${info.interopDefaultAccessName} = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${name});` + ); + } + if (isConditional) { + result.add("\n}"); + } + } + + const data = new Map(); + if (chunkInitFragments.length > 0) + data.set("chunkInitFragments", chunkInitFragments); + data.set("topLevelDeclarations", topLevelDeclarations); + + /** @type {CodeGenerationResult} */ + const resultEntry = { + sources: new Map([["javascript", new CachedSource(result)]]), + data, + runtimeRequirements + }; + + return resultEntry; + } + + /** + * @param {Map} modulesMap modulesMap + * @param {ModuleInfo} info info + * @param {DependencyTemplates} dependencyTemplates dependencyTemplates + * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate + * @param {ModuleGraph} moduleGraph moduleGraph + * @param {ChunkGraph} chunkGraph chunkGraph + * @param {RuntimeSpec} runtime runtime + * @param {CodeGenerationResults} codeGenerationResults codeGenerationResults + */ + _analyseModule( + modulesMap, + info, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime, + codeGenerationResults + ) { + if (info.type === "concatenated") { + const m = info.module; + try { + // Create a concatenation scope to track and capture information + const concatenationScope = new ConcatenationScope(modulesMap, info); + + // TODO cache codeGeneration results + const codeGenResult = m.codeGeneration({ + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + runtime, + concatenationScope, + codeGenerationResults, + sourceTypes: JS_TYPES + }); + const source = /** @type {Source} */ ( + codeGenResult.sources.get("javascript") + ); + const data = codeGenResult.data; + const chunkInitFragments = data && data.get("chunkInitFragments"); + const code = source.source().toString(); + let ast; + try { + ast = JavascriptParser._parse(code, { + sourceType: "module" + }); + } catch (_err) { + const err = /** @type {TODO} */ (_err); + if ( + err.loc && + typeof err.loc === "object" && + typeof err.loc.line === "number" + ) { + const lineNumber = err.loc.line; + const lines = code.split("\n"); + err.message += `\n| ${lines + .slice(Math.max(0, lineNumber - 3), lineNumber + 2) + .join("\n| ")}`; + } + throw err; + } + const scopeManager = eslintScope.analyze(ast, { + ecmaVersion: 6, + sourceType: "module", + optimistic: true, + ignoreEval: true, + impliedStrict: true + }); + const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast)); + const moduleScope = globalScope.childScopes[0]; + const resultSource = new ReplaceSource(source); + info.runtimeRequirements = + /** @type {ReadOnlyRuntimeRequirements} */ + (codeGenResult.runtimeRequirements); + info.ast = ast; + info.internalSource = source; + info.source = resultSource; + info.chunkInitFragments = chunkInitFragments; + info.globalScope = globalScope; + info.moduleScope = moduleScope; + } catch (err) { + /** @type {Error} */ + (err).message += + `\nwhile analyzing module ${m.identifier()} for concatenation`; + throw err; + } + } + } + + /** + * @param {ModuleGraph} moduleGraph the module graph + * @param {RuntimeSpec} runtime the runtime + * @returns {[ModuleInfoOrReference[], Map]} module info items + */ + _getModulesWithInfo(moduleGraph, runtime) { + const orderedConcatenationList = this._createConcatenationList( + this.rootModule, + this._modules, + runtime, + moduleGraph + ); + /** @type {Map} */ + const map = new Map(); + const list = orderedConcatenationList.map((info, index) => { + let item = map.get(info.module); + if (item === undefined) { + switch (info.type) { + case "concatenated": + item = { + type: "concatenated", + module: info.module, + index, + ast: undefined, + internalSource: undefined, + runtimeRequirements: undefined, + source: undefined, + globalScope: undefined, + moduleScope: undefined, + internalNames: new Map(), + exportMap: undefined, + rawExportMap: undefined, + namespaceExportSymbol: undefined, + namespaceObjectName: undefined, + interopNamespaceObjectUsed: false, + interopNamespaceObjectName: undefined, + interopNamespaceObject2Used: false, + interopNamespaceObject2Name: undefined, + interopDefaultAccessUsed: false, + interopDefaultAccessName: undefined + }; + break; + case "external": + item = { + type: "external", + module: info.module, + runtimeCondition: info.runtimeCondition, + index, + name: undefined, + interopNamespaceObjectUsed: false, + interopNamespaceObjectName: undefined, + interopNamespaceObject2Used: false, + interopNamespaceObject2Name: undefined, + interopDefaultAccessUsed: false, + interopDefaultAccessName: undefined + }; + break; + default: + throw new Error( + `Unsupported concatenation entry type ${info.type}` + ); + } + map.set( + /** @type {ModuleInfo} */ (item).module, + /** @type {ModuleInfo} */ (item) + ); + return /** @type {ModuleInfo} */ (item); + } + /** @type {ReferenceToModuleInfo} */ + const ref = { + type: "reference", + runtimeCondition: info.runtimeCondition, + target: item + }; + return ref; + }); + return [list, map]; + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + const { chunkGraph, runtime } = context; + for (const info of this._createConcatenationList( + this.rootModule, + this._modules, + intersectRuntime(runtime, this._runtime), + chunkGraph.moduleGraph + )) { + switch (info.type) { + case "concatenated": + info.module.updateHash(hash, context); + break; + case "external": + hash.update(`${chunkGraph.getModuleId(info.module)}`); + // TODO runtimeCondition + break; + } + } + super.updateHash(hash, context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ConcatenatedModule} ConcatenatedModule + */ + static deserialize(context) { + const obj = new ConcatenatedModule({ + identifier: /** @type {EXPECTED_ANY} */ (undefined), + rootModule: /** @type {EXPECTED_ANY} */ (undefined), + modules: /** @type {EXPECTED_ANY} */ (undefined), + runtime: undefined, + compilation: /** @type {EXPECTED_ANY} */ (undefined) + }); + obj.deserialize(context); + return obj; + } +} + +makeSerializable(ConcatenatedModule, "webpack/lib/optimize/ConcatenatedModule"); + +module.exports = ConcatenatedModule; diff --git a/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js b/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js new file mode 100644 index 00000000000..0bc8a384bb6 --- /dev/null +++ b/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js @@ -0,0 +1,88 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_BASIC } = require("../OptimizationStages"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compiler")} Compiler */ + +class EnsureChunkConditionsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "EnsureChunkConditionsPlugin", + compilation => { + /** + * @param {Iterable} chunks the chunks + */ + const handler = chunks => { + const chunkGraph = compilation.chunkGraph; + // These sets are hoisted here to save memory + // They are cleared at the end of every loop + /** @type {Set} */ + const sourceChunks = new Set(); + /** @type {Set} */ + const chunkGroups = new Set(); + for (const module of compilation.modules) { + if (!module.hasChunkCondition()) continue; + for (const chunk of chunkGraph.getModuleChunksIterable(module)) { + if (!module.chunkCondition(chunk, compilation)) { + sourceChunks.add(chunk); + for (const group of chunk.groupsIterable) { + chunkGroups.add(group); + } + } + } + if (sourceChunks.size === 0) continue; + /** @type {Set} */ + const targetChunks = new Set(); + chunkGroupLoop: for (const chunkGroup of chunkGroups) { + // Can module be placed in a chunk of this group? + for (const chunk of chunkGroup.chunks) { + if (module.chunkCondition(chunk, compilation)) { + targetChunks.add(chunk); + continue chunkGroupLoop; + } + } + // We reached the entrypoint: fail + if (chunkGroup.isInitial()) { + throw new Error( + `Cannot fulfil chunk condition of ${module.identifier()}` + ); + } + // Try placing in all parents + for (const group of chunkGroup.parentsIterable) { + chunkGroups.add(group); + } + } + for (const sourceChunk of sourceChunks) { + chunkGraph.disconnectChunkAndModule(sourceChunk, module); + } + for (const targetChunk of targetChunks) { + chunkGraph.connectChunkAndModule(targetChunk, module); + } + sourceChunks.clear(); + chunkGroups.clear(); + } + }; + compilation.hooks.optimizeChunks.tap( + { + name: "EnsureChunkConditionsPlugin", + stage: STAGE_BASIC + }, + handler + ); + } + ); + } +} +module.exports = EnsureChunkConditionsPlugin; diff --git a/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js b/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js new file mode 100644 index 00000000000..2e4adb84e72 --- /dev/null +++ b/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js @@ -0,0 +1,130 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareIds } = require("../util/comparators"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +class FlagIncludedChunksPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("FlagIncludedChunksPlugin", compilation => { + compilation.hooks.optimizeChunkIds.tap( + "FlagIncludedChunksPlugin", + chunks => { + const chunkGraph = compilation.chunkGraph; + + // prepare two bit integers for each module + // 2^31 is the max number represented as SMI in v8 + // we want the bits distributed this way: + // the bit 2^31 is pretty rar and only one module should get it + // so it has a probability of 1 / modulesCount + // the first bit (2^0) is the easiest and every module could get it + // if it doesn't get a better bit + // from bit 2^n to 2^(n+1) there is a probability of p + // so 1 / modulesCount == p^31 + // <=> p = sqrt31(1 / modulesCount) + // so we use a modulo of 1 / sqrt31(1 / modulesCount) + /** @type {WeakMap} */ + const moduleBits = new WeakMap(); + const modulesCount = compilation.modules.size; + + // precalculate the modulo values for each bit + const modulo = 1 / (1 / modulesCount) ** (1 / 31); + const modulos = Array.from( + { length: 31 }, + (x, i) => (modulo ** i) | 0 + ); + + // iterate all modules to generate bit values + let i = 0; + for (const module of compilation.modules) { + let bit = 30; + while (i % modulos[bit] !== 0) { + bit--; + } + moduleBits.set(module, 1 << bit); + i++; + } + + // iterate all chunks to generate bitmaps + /** @type {WeakMap} */ + const chunkModulesHash = new WeakMap(); + for (const chunk of chunks) { + let hash = 0; + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { + hash |= /** @type {number} */ (moduleBits.get(module)); + } + chunkModulesHash.set(chunk, hash); + } + + for (const chunkA of chunks) { + const chunkAHash = + /** @type {number} */ + (chunkModulesHash.get(chunkA)); + const chunkAModulesCount = + chunkGraph.getNumberOfChunkModules(chunkA); + if (chunkAModulesCount === 0) continue; + let bestModule; + for (const module of chunkGraph.getChunkModulesIterable(chunkA)) { + if ( + bestModule === undefined || + chunkGraph.getNumberOfModuleChunks(bestModule) > + chunkGraph.getNumberOfModuleChunks(module) + ) + bestModule = module; + } + loopB: for (const chunkB of chunkGraph.getModuleChunksIterable( + /** @type {Module} */ (bestModule) + )) { + // as we iterate the same iterables twice + // skip if we find ourselves + if (chunkA === chunkB) continue; + + const chunkBModulesCount = + chunkGraph.getNumberOfChunkModules(chunkB); + + // ids for empty chunks are not included + if (chunkBModulesCount === 0) continue; + + // instead of swapping A and B just bail + // as we loop twice the current A will be B and B then A + if (chunkAModulesCount > chunkBModulesCount) continue; + + // is chunkA in chunkB? + + // we do a cheap check for the hash value + const chunkBHash = + /** @type {number} */ + (chunkModulesHash.get(chunkB)); + if ((chunkBHash & chunkAHash) !== chunkAHash) continue; + + // compare all modules + for (const m of chunkGraph.getChunkModulesIterable(chunkA)) { + if (!chunkGraph.isModuleInChunk(m, chunkB)) continue loopB; + } + + /** @type {ChunkId[]} */ + (chunkB.ids).push(/** @type {ChunkId} */ (chunkA.id)); + // https://github.com/webpack/webpack/issues/18837 + /** @type {ChunkId[]} */ + (chunkB.ids).sort(compareIds); + } + } + } + ); + }); + } +} +module.exports = FlagIncludedChunksPlugin; diff --git a/webpack-lib/lib/optimize/InnerGraph.js b/webpack-lib/lib/optimize/InnerGraph.js new file mode 100644 index 00000000000..099c5eb1847 --- /dev/null +++ b/webpack-lib/lib/optimize/InnerGraph.js @@ -0,0 +1,351 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); + +/** @typedef {import("estree").Node} AnyNode */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** @typedef {Map | true | undefined>} InnerGraph */ +/** @typedef {function(boolean | Set | undefined): void} UsageCallback */ + +/** + * @typedef {object} StateObject + * @property {InnerGraph} innerGraph + * @property {TopLevelSymbol=} currentTopLevelSymbol + * @property {Map>} usageCallbackMap + */ + +/** @typedef {false|StateObject} State */ + +/** @type {WeakMap} */ +const parserStateMap = new WeakMap(); +const topLevelSymbolTag = Symbol("top level symbol"); + +/** + * @param {ParserState} parserState parser state + * @returns {State | undefined} state + */ +function getState(parserState) { + return parserStateMap.get(parserState); +} + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.bailout = parserState => { + parserStateMap.set(parserState, false); +}; + +/** + * @param {ParserState} parserState parser state + * @returns {void} + */ +module.exports.enable = parserState => { + const state = parserStateMap.get(parserState); + if (state === false) { + return; + } + parserStateMap.set(parserState, { + innerGraph: new Map(), + currentTopLevelSymbol: undefined, + usageCallbackMap: new Map() + }); +}; + +/** + * @param {ParserState} parserState parser state + * @returns {boolean} true, when enabled + */ +module.exports.isEnabled = parserState => { + const state = parserStateMap.get(parserState); + return Boolean(state); +}; + +/** + * @param {ParserState} state parser state + * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols + * @param {string | TopLevelSymbol | true} usage usage data + * @returns {void} + */ +module.exports.addUsage = (state, symbol, usage) => { + const innerGraphState = getState(state); + + if (innerGraphState) { + const { innerGraph } = innerGraphState; + const info = innerGraph.get(symbol); + if (usage === true) { + innerGraph.set(symbol, true); + } else if (info === undefined) { + innerGraph.set(symbol, new Set([usage])); + } else if (info !== true) { + info.add(usage); + } + } +}; + +/** + * @param {JavascriptParser} parser the parser + * @param {string} name name of variable + * @param {string | TopLevelSymbol | true} usage usage data + * @returns {void} + */ +module.exports.addVariableUsage = (parser, name, usage) => { + const symbol = + /** @type {TopLevelSymbol} */ ( + parser.getTagData(name, topLevelSymbolTag) + ) || module.exports.tagTopLevelSymbol(parser, name); + if (symbol) { + module.exports.addUsage(parser.state, symbol, usage); + } +}; + +/** + * @param {ParserState} state parser state + * @returns {void} + */ +module.exports.inferDependencyUsage = state => { + const innerGraphState = getState(state); + + if (!innerGraphState) { + return; + } + + const { innerGraph, usageCallbackMap } = innerGraphState; + const processed = new Map(); + // flatten graph to terminal nodes (string, undefined or true) + const nonTerminal = new Set(innerGraph.keys()); + while (nonTerminal.size > 0) { + for (const key of nonTerminal) { + /** @type {Set | true} */ + let newSet = new Set(); + let isTerminal = true; + const value = innerGraph.get(key); + let alreadyProcessed = processed.get(key); + if (alreadyProcessed === undefined) { + alreadyProcessed = new Set(); + processed.set(key, alreadyProcessed); + } + if (value !== true && value !== undefined) { + for (const item of value) { + alreadyProcessed.add(item); + } + for (const item of value) { + if (typeof item === "string") { + newSet.add(item); + } else { + const itemValue = innerGraph.get(item); + if (itemValue === true) { + newSet = true; + break; + } + if (itemValue !== undefined) { + for (const i of itemValue) { + if (i === key) continue; + if (alreadyProcessed.has(i)) continue; + newSet.add(i); + if (typeof i !== "string") { + isTerminal = false; + } + } + } + } + } + if (newSet === true) { + innerGraph.set(key, true); + } else if (newSet.size === 0) { + innerGraph.set(key, undefined); + } else { + innerGraph.set(key, newSet); + } + } + if (isTerminal) { + nonTerminal.delete(key); + + // For the global key, merge with all other keys + if (key === null) { + const globalValue = innerGraph.get(null); + if (globalValue) { + for (const [key, value] of innerGraph) { + if (key !== null && value !== true) { + if (globalValue === true) { + innerGraph.set(key, true); + } else { + const newSet = new Set(value); + for (const item of globalValue) { + newSet.add(item); + } + innerGraph.set(key, newSet); + } + } + } + } + } + } + } + } + + /** @type {Map>} */ + for (const [symbol, callbacks] of usageCallbackMap) { + const usage = /** @type {true | Set | undefined} */ ( + innerGraph.get(symbol) + ); + for (const callback of callbacks) { + callback(usage === undefined ? false : usage); + } + } +}; + +/** + * @param {ParserState} state parser state + * @param {UsageCallback} onUsageCallback on usage callback + */ +module.exports.onUsage = (state, onUsageCallback) => { + const innerGraphState = getState(state); + + if (innerGraphState) { + const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState; + if (currentTopLevelSymbol) { + let callbacks = usageCallbackMap.get(currentTopLevelSymbol); + + if (callbacks === undefined) { + callbacks = new Set(); + usageCallbackMap.set(currentTopLevelSymbol, callbacks); + } + + callbacks.add(onUsageCallback); + } else { + onUsageCallback(true); + } + } else { + onUsageCallback(undefined); + } +}; + +/** + * @param {ParserState} state parser state + * @param {TopLevelSymbol | undefined} symbol the symbol + */ +module.exports.setTopLevelSymbol = (state, symbol) => { + const innerGraphState = getState(state); + + if (innerGraphState) { + innerGraphState.currentTopLevelSymbol = symbol; + } +}; + +/** + * @param {ParserState} state parser state + * @returns {TopLevelSymbol|void} usage data + */ +module.exports.getTopLevelSymbol = state => { + const innerGraphState = getState(state); + + if (innerGraphState) { + return innerGraphState.currentTopLevelSymbol; + } +}; + +/** + * @param {JavascriptParser} parser parser + * @param {string} name name of variable + * @returns {TopLevelSymbol | undefined} symbol + */ +module.exports.tagTopLevelSymbol = (parser, name) => { + const innerGraphState = getState(parser.state); + if (!innerGraphState) return; + + parser.defineVariable(name); + + const existingTag = /** @type {TopLevelSymbol} */ ( + parser.getTagData(name, topLevelSymbolTag) + ); + if (existingTag) { + return existingTag; + } + + const fn = new TopLevelSymbol(name); + parser.tagVariable(name, topLevelSymbolTag, fn); + return fn; +}; + +/** + * @param {Dependency} dependency the dependency + * @param {Set | boolean} usedByExports usedByExports info + * @param {ModuleGraph} moduleGraph moduleGraph + * @param {RuntimeSpec} runtime runtime + * @returns {boolean} false, when unused. Otherwise true + */ +module.exports.isDependencyUsedByExports = ( + dependency, + usedByExports, + moduleGraph, + runtime +) => { + if (usedByExports === false) return false; + if (usedByExports !== true && usedByExports !== undefined) { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(dependency)); + const exportsInfo = moduleGraph.getExportsInfo(selfModule); + let used = false; + for (const exportName of usedByExports) { + if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) + used = true; + } + if (!used) return false; + } + return true; +}; + +/** + * @param {Dependency} dependency the dependency + * @param {Set | boolean | undefined} usedByExports usedByExports info + * @param {ModuleGraph} moduleGraph moduleGraph + * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active + */ +module.exports.getDependencyUsedByExportsCondition = ( + dependency, + usedByExports, + moduleGraph +) => { + if (usedByExports === false) return false; + if (usedByExports !== true && usedByExports !== undefined) { + const selfModule = + /** @type {Module} */ + (moduleGraph.getParentModule(dependency)); + const exportsInfo = moduleGraph.getExportsInfo(selfModule); + return (connections, runtime) => { + for (const exportName of usedByExports) { + if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) + return true; + } + return false; + }; + } + return null; +}; + +class TopLevelSymbol { + /** + * @param {string} name name of the variable + */ + constructor(name) { + this.name = name; + } +} + +module.exports.TopLevelSymbol = TopLevelSymbol; +module.exports.topLevelSymbolTag = topLevelSymbolTag; diff --git a/webpack-lib/lib/optimize/InnerGraphPlugin.js b/webpack-lib/lib/optimize/InnerGraphPlugin.js new file mode 100644 index 00000000000..7900a4160da --- /dev/null +++ b/webpack-lib/lib/optimize/InnerGraphPlugin.js @@ -0,0 +1,450 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM +} = require("../ModuleTypeConstants"); +const PureExpressionDependency = require("../dependencies/PureExpressionDependency"); +const InnerGraph = require("./InnerGraph"); + +/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */ +/** @typedef {import("estree").ClassExpression} ClassExpression */ +/** @typedef {import("estree").Expression} Expression */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("estree").VariableDeclarator} VariableDeclaratorNode */ +/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("./InnerGraph").InnerGraph} InnerGraph */ +/** @typedef {import("./InnerGraph").TopLevelSymbol} TopLevelSymbol */ + +const { topLevelSymbolTag } = InnerGraph; + +const PLUGIN_NAME = "InnerGraphPlugin"; + +class InnerGraphPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const logger = compilation.getLogger("webpack.InnerGraphPlugin"); + + compilation.dependencyTemplates.set( + PureExpressionDependency, + new PureExpressionDependency.Template() + ); + + /** + * @param {JavascriptParser} parser the parser + * @param {JavascriptParserOptions} parserOptions options + * @returns {void} + */ + const handler = (parser, parserOptions) => { + /** + * @param {Expression} sup sup + */ + const onUsageSuper = sup => { + InnerGraph.onUsage(parser.state, usedByExports => { + switch (usedByExports) { + case undefined: + case true: + return; + default: { + const dep = new PureExpressionDependency( + /** @type {Range} */ + (sup.range) + ); + dep.loc = /** @type {DependencyLocation} */ (sup.loc); + dep.usedByExports = usedByExports; + parser.state.module.addDependency(dep); + break; + } + } + }); + }; + + parser.hooks.program.tap(PLUGIN_NAME, () => { + InnerGraph.enable(parser.state); + }); + + parser.hooks.finish.tap(PLUGIN_NAME, () => { + if (!InnerGraph.isEnabled(parser.state)) return; + + logger.time("infer dependency usage"); + InnerGraph.inferDependencyUsage(parser.state); + logger.timeAggregate("infer dependency usage"); + }); + + // During prewalking the following datastructures are filled with + // nodes that have a TopLevelSymbol assigned and + // variables are tagged with the assigned TopLevelSymbol + + // We differ 3 types of nodes: + // 1. full statements (export default, function declaration) + // 2. classes (class declaration, class expression) + // 3. variable declarators (const x = ...) + + /** @type {WeakMap} */ + const statementWithTopLevelSymbol = new WeakMap(); + /** @type {WeakMap} */ + const statementPurePart = new WeakMap(); + + /** @type {WeakMap} */ + const classWithTopLevelSymbol = new WeakMap(); + + /** @type {WeakMap} */ + const declWithTopLevelSymbol = new WeakMap(); + /** @type {WeakSet} */ + const pureDeclarators = new WeakSet(); + + // The following hooks are used during prewalking: + + parser.hooks.preStatement.tap(PLUGIN_NAME, statement => { + if (!InnerGraph.isEnabled(parser.state)) return; + + if ( + parser.scope.topLevelScope === true && + statement.type === "FunctionDeclaration" + ) { + const name = statement.id ? statement.id.name : "*default*"; + const fn = + /** @type {TopLevelSymbol} */ + (InnerGraph.tagTopLevelSymbol(parser, name)); + statementWithTopLevelSymbol.set(statement, fn); + return true; + } + }); + + parser.hooks.blockPreStatement.tap(PLUGIN_NAME, statement => { + if (!InnerGraph.isEnabled(parser.state)) return; + + if (parser.scope.topLevelScope === true) { + if ( + statement.type === "ClassDeclaration" && + parser.isPure( + statement, + /** @type {Range} */ (statement.range)[0] + ) + ) { + const name = statement.id ? statement.id.name : "*default*"; + const fn = /** @type {TopLevelSymbol} */ ( + InnerGraph.tagTopLevelSymbol(parser, name) + ); + classWithTopLevelSymbol.set(statement, fn); + return true; + } + if (statement.type === "ExportDefaultDeclaration") { + const name = "*default*"; + const fn = + /** @type {TopLevelSymbol} */ + (InnerGraph.tagTopLevelSymbol(parser, name)); + const decl = statement.declaration; + if ( + (decl.type === "ClassExpression" || + decl.type === "ClassDeclaration") && + parser.isPure( + /** @type {ClassExpression | ClassDeclaration} */ + (decl), + /** @type {Range} */ + (decl.range)[0] + ) + ) { + classWithTopLevelSymbol.set( + /** @type {ClassExpression | ClassDeclaration} */ + (decl), + fn + ); + } else if ( + parser.isPure( + /** @type {Expression} */ + (decl), + /** @type {Range} */ + (statement.range)[0] + ) + ) { + statementWithTopLevelSymbol.set(statement, fn); + if ( + !decl.type.endsWith("FunctionExpression") && + !decl.type.endsWith("Declaration") && + decl.type !== "Literal" + ) { + statementPurePart.set( + statement, + /** @type {Expression} */ + (decl) + ); + } + } + } + } + }); + + parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => { + if (!InnerGraph.isEnabled(parser.state)) return; + if ( + parser.scope.topLevelScope === true && + decl.init && + decl.id.type === "Identifier" + ) { + const name = decl.id.name; + if ( + decl.init.type === "ClassExpression" && + parser.isPure( + decl.init, + /** @type {Range} */ (decl.id.range)[1] + ) + ) { + const fn = + /** @type {TopLevelSymbol} */ + (InnerGraph.tagTopLevelSymbol(parser, name)); + classWithTopLevelSymbol.set(decl.init, fn); + } else if ( + parser.isPure( + decl.init, + /** @type {Range} */ (decl.id.range)[1] + ) + ) { + const fn = + /** @type {TopLevelSymbol} */ + (InnerGraph.tagTopLevelSymbol(parser, name)); + declWithTopLevelSymbol.set(decl, fn); + if ( + !decl.init.type.endsWith("FunctionExpression") && + decl.init.type !== "Literal" + ) { + pureDeclarators.add(decl); + } + } + } + }); + + // During real walking we set the TopLevelSymbol state to the assigned + // TopLevelSymbol by using the fill datastructures. + + // In addition to tracking TopLevelSymbols, we sometimes need to + // add a PureExpressionDependency. This is needed to skip execution + // of pure expressions, even when they are not dropped due to + // minimizing. Otherwise symbols used there might not exist anymore + // as they are removed as unused by this optimization + + // When we find a reference to a TopLevelSymbol, we register a + // TopLevelSymbol dependency from TopLevelSymbol in state to the + // referenced TopLevelSymbol. This way we get a graph of all + // TopLevelSymbols. + + // The following hooks are called during walking: + + parser.hooks.statement.tap(PLUGIN_NAME, statement => { + if (!InnerGraph.isEnabled(parser.state)) return; + if (parser.scope.topLevelScope === true) { + InnerGraph.setTopLevelSymbol(parser.state, undefined); + + const fn = statementWithTopLevelSymbol.get(statement); + if (fn) { + InnerGraph.setTopLevelSymbol(parser.state, fn); + const purePart = statementPurePart.get(statement); + if (purePart) { + InnerGraph.onUsage(parser.state, usedByExports => { + switch (usedByExports) { + case undefined: + case true: + return; + default: { + const dep = new PureExpressionDependency( + /** @type {Range} */ (purePart.range) + ); + dep.loc = + /** @type {DependencyLocation} */ + (statement.loc); + dep.usedByExports = usedByExports; + parser.state.module.addDependency(dep); + break; + } + } + }); + } + } + } + }); + + parser.hooks.classExtendsExpression.tap( + PLUGIN_NAME, + (expr, statement) => { + if (!InnerGraph.isEnabled(parser.state)) return; + if (parser.scope.topLevelScope === true) { + const fn = classWithTopLevelSymbol.get(statement); + if ( + fn && + parser.isPure( + expr, + statement.id + ? /** @type {Range} */ (statement.id.range)[1] + : /** @type {Range} */ (statement.range)[0] + ) + ) { + InnerGraph.setTopLevelSymbol(parser.state, fn); + onUsageSuper(expr); + } + } + } + ); + + parser.hooks.classBodyElement.tap( + PLUGIN_NAME, + (element, classDefinition) => { + if (!InnerGraph.isEnabled(parser.state)) return; + if (parser.scope.topLevelScope === true) { + const fn = classWithTopLevelSymbol.get(classDefinition); + if (fn) { + InnerGraph.setTopLevelSymbol(parser.state, undefined); + } + } + } + ); + + parser.hooks.classBodyValue.tap( + PLUGIN_NAME, + (expression, element, classDefinition) => { + if (!InnerGraph.isEnabled(parser.state)) return; + if (parser.scope.topLevelScope === true) { + const fn = classWithTopLevelSymbol.get(classDefinition); + if (fn) { + if ( + !element.static || + parser.isPure( + expression, + element.key + ? /** @type {Range} */ (element.key.range)[1] + : /** @type {Range} */ (element.range)[0] + ) + ) { + InnerGraph.setTopLevelSymbol(parser.state, fn); + if (element.type !== "MethodDefinition" && element.static) { + InnerGraph.onUsage(parser.state, usedByExports => { + switch (usedByExports) { + case undefined: + case true: + return; + default: { + const dep = new PureExpressionDependency( + /** @type {Range} */ (expression.range) + ); + dep.loc = + /** @type {DependencyLocation} */ + (expression.loc); + dep.usedByExports = usedByExports; + parser.state.module.addDependency(dep); + break; + } + } + }); + } + } else { + InnerGraph.setTopLevelSymbol(parser.state, undefined); + } + } + } + } + ); + + parser.hooks.declarator.tap(PLUGIN_NAME, (decl, statement) => { + if (!InnerGraph.isEnabled(parser.state)) return; + const fn = declWithTopLevelSymbol.get(decl); + + if (fn) { + InnerGraph.setTopLevelSymbol(parser.state, fn); + if (pureDeclarators.has(decl)) { + if ( + /** @type {ClassExpression} */ + (decl.init).type === "ClassExpression" + ) { + if (decl.init.superClass) { + onUsageSuper(decl.init.superClass); + } + } else { + InnerGraph.onUsage(parser.state, usedByExports => { + switch (usedByExports) { + case undefined: + case true: + return; + default: { + const dep = new PureExpressionDependency( + /** @type {Range} */ ( + /** @type {ClassExpression} */ + (decl.init).range + ) + ); + dep.loc = /** @type {DependencyLocation} */ (decl.loc); + dep.usedByExports = usedByExports; + parser.state.module.addDependency(dep); + break; + } + } + }); + } + } + parser.walkExpression(decl.init); + InnerGraph.setTopLevelSymbol(parser.state, undefined); + return true; + } else if ( + decl.id.type === "Identifier" && + decl.init && + decl.init.type === "ClassExpression" && + classWithTopLevelSymbol.has(decl.init) + ) { + parser.walkExpression(decl.init); + InnerGraph.setTopLevelSymbol(parser.state, undefined); + return true; + } + }); + + parser.hooks.expression + .for(topLevelSymbolTag) + .tap(PLUGIN_NAME, () => { + const topLevelSymbol = /** @type {TopLevelSymbol} */ ( + parser.currentTagData + ); + const currentTopLevelSymbol = InnerGraph.getTopLevelSymbol( + parser.state + ); + InnerGraph.addUsage( + parser.state, + topLevelSymbol, + currentTopLevelSymbol || true + ); + }); + parser.hooks.assign.for(topLevelSymbolTag).tap(PLUGIN_NAME, expr => { + if (!InnerGraph.isEnabled(parser.state)) return; + if (expr.operator === "=") return true; + }); + }; + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_AUTO) + .tap(PLUGIN_NAME, handler); + normalModuleFactory.hooks.parser + .for(JAVASCRIPT_MODULE_TYPE_ESM) + .tap(PLUGIN_NAME, handler); + + compilation.hooks.finishModules.tap(PLUGIN_NAME, () => { + logger.timeAggregateEnd("infer dependency usage"); + }); + } + ); + } +} + +module.exports = InnerGraphPlugin; diff --git a/webpack-lib/lib/optimize/LimitChunkCountPlugin.js b/webpack-lib/lib/optimize/LimitChunkCountPlugin.js new file mode 100644 index 00000000000..9b18c9b3b27 --- /dev/null +++ b/webpack-lib/lib/optimize/LimitChunkCountPlugin.js @@ -0,0 +1,277 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_ADVANCED } = require("../OptimizationStages"); +const LazyBucketSortedSet = require("../util/LazyBucketSortedSet"); +const { compareChunks } = require("../util/comparators"); +const createSchemaValidation = require("../util/create-schema-validation"); + +/** @typedef {import("../../declarations/plugins/optimize/LimitChunkCountPlugin").LimitChunkCountPluginOptions} LimitChunkCountPluginOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/optimize/LimitChunkCountPlugin.check.js"), + () => require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json"), + { + name: "Limit Chunk Count Plugin", + baseDataPath: "options" + } +); + +/** + * @typedef {object} ChunkCombination + * @property {boolean} deleted this is set to true when combination was removed + * @property {number} sizeDiff + * @property {number} integratedSize + * @property {Chunk} a + * @property {Chunk} b + * @property {number} aIdx + * @property {number} bIdx + * @property {number} aSize + * @property {number} bSize + */ + +/** + * @template K, V + * @param {Map>} map map + * @param {K} key key + * @param {V} value value + */ +const addToSetMap = (map, key, value) => { + const set = map.get(key); + if (set === undefined) { + map.set(key, new Set([value])); + } else { + set.add(value); + } +}; + +class LimitChunkCountPlugin { + /** + * @param {LimitChunkCountPluginOptions=} options options object + */ + constructor(options) { + validate(options); + this.options = /** @type {LimitChunkCountPluginOptions} */ (options); + } + + /** + * @param {Compiler} compiler the webpack compiler + * @returns {void} + */ + apply(compiler) { + const options = this.options; + compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => { + compilation.hooks.optimizeChunks.tap( + { + name: "LimitChunkCountPlugin", + stage: STAGE_ADVANCED + }, + chunks => { + const chunkGraph = compilation.chunkGraph; + const maxChunks = options.maxChunks; + if (!maxChunks) return; + if (maxChunks < 1) return; + if (compilation.chunks.size <= maxChunks) return; + + let remainingChunksToMerge = compilation.chunks.size - maxChunks; + + // order chunks in a deterministic way + const compareChunksWithGraph = compareChunks(chunkGraph); + const orderedChunks = Array.from(chunks).sort(compareChunksWithGraph); + + // create a lazy sorted data structure to keep all combinations + // this is large. Size = chunks * (chunks - 1) / 2 + // It uses a multi layer bucket sort plus normal sort in the last layer + // It's also lazy so only accessed buckets are sorted + const combinations = new LazyBucketSortedSet( + // Layer 1: ordered by largest size benefit + c => c.sizeDiff, + (a, b) => b - a, + // Layer 2: ordered by smallest combined size + /** + * @param {ChunkCombination} c combination + * @returns {number} integrated size + */ + c => c.integratedSize, + (a, b) => a - b, + // Layer 3: ordered by position difference in orderedChunk (-> to be deterministic) + /** + * @param {ChunkCombination} c combination + * @returns {number} position difference + */ + c => c.bIdx - c.aIdx, + (a, b) => a - b, + // Layer 4: ordered by position in orderedChunk (-> to be deterministic) + (a, b) => a.bIdx - b.bIdx + ); + + // we keep a mapping from chunk to all combinations + // but this mapping is not kept up-to-date with deletions + // so `deleted` flag need to be considered when iterating this + /** @type {Map>} */ + const combinationsByChunk = new Map(); + + for (const [bIdx, b] of orderedChunks.entries()) { + // create combination pairs with size and integrated size + for (let aIdx = 0; aIdx < bIdx; aIdx++) { + const a = orderedChunks[aIdx]; + // filter pairs that can not be integrated! + if (!chunkGraph.canChunksBeIntegrated(a, b)) continue; + + const integratedSize = chunkGraph.getIntegratedChunksSize( + a, + b, + options + ); + + const aSize = chunkGraph.getChunkSize(a, options); + const bSize = chunkGraph.getChunkSize(b, options); + const c = { + deleted: false, + sizeDiff: aSize + bSize - integratedSize, + integratedSize, + a, + b, + aIdx, + bIdx, + aSize, + bSize + }; + combinations.add(c); + addToSetMap(combinationsByChunk, a, c); + addToSetMap(combinationsByChunk, b, c); + } + } + + // list of modified chunks during this run + // combinations affected by this change are skipped to allow + // further optimizations + /** @type {Set} */ + const modifiedChunks = new Set(); + + let changed = false; + loop: while (true) { + const combination = combinations.popFirst(); + if (combination === undefined) break; + + combination.deleted = true; + const { a, b, integratedSize } = combination; + + // skip over pair when + // one of the already merged chunks is a parent of one of the chunks + if (modifiedChunks.size > 0) { + const queue = new Set(a.groupsIterable); + for (const group of b.groupsIterable) { + queue.add(group); + } + for (const group of queue) { + for (const mChunk of modifiedChunks) { + if (mChunk !== a && mChunk !== b && mChunk.isInGroup(group)) { + // This is a potential pair which needs recalculation + // We can't do that now, but it merge before following pairs + // so we leave space for it, and consider chunks as modified + // just for the worse case + remainingChunksToMerge--; + if (remainingChunksToMerge <= 0) break loop; + modifiedChunks.add(a); + modifiedChunks.add(b); + continue loop; + } + } + for (const parent of group.parentsIterable) { + queue.add(parent); + } + } + } + + // merge the chunks + if (chunkGraph.canChunksBeIntegrated(a, b)) { + chunkGraph.integrateChunks(a, b); + compilation.chunks.delete(b); + + // flag chunk a as modified as further optimization are possible for all children here + modifiedChunks.add(a); + + changed = true; + remainingChunksToMerge--; + if (remainingChunksToMerge <= 0) break; + + // Update all affected combinations + // delete all combination with the removed chunk + // we will use combinations with the kept chunk instead + for (const combination of /** @type {Set} */ ( + combinationsByChunk.get(a) + )) { + if (combination.deleted) continue; + combination.deleted = true; + combinations.delete(combination); + } + + // Update combinations with the kept chunk with new sizes + for (const combination of /** @type {Set} */ ( + combinationsByChunk.get(b) + )) { + if (combination.deleted) continue; + if (combination.a === b) { + if (!chunkGraph.canChunksBeIntegrated(a, combination.b)) { + combination.deleted = true; + combinations.delete(combination); + continue; + } + // Update size + const newIntegratedSize = chunkGraph.getIntegratedChunksSize( + a, + combination.b, + options + ); + const finishUpdate = combinations.startUpdate(combination); + combination.a = a; + combination.integratedSize = newIntegratedSize; + combination.aSize = integratedSize; + combination.sizeDiff = + combination.bSize + integratedSize - newIntegratedSize; + finishUpdate(); + } else if (combination.b === b) { + if (!chunkGraph.canChunksBeIntegrated(combination.a, a)) { + combination.deleted = true; + combinations.delete(combination); + continue; + } + // Update size + const newIntegratedSize = chunkGraph.getIntegratedChunksSize( + combination.a, + a, + options + ); + + const finishUpdate = combinations.startUpdate(combination); + combination.b = a; + combination.integratedSize = newIntegratedSize; + combination.bSize = integratedSize; + combination.sizeDiff = + integratedSize + combination.aSize - newIntegratedSize; + finishUpdate(); + } + } + combinationsByChunk.set( + a, + /** @type {Set} */ ( + combinationsByChunk.get(b) + ) + ); + combinationsByChunk.delete(b); + } + } + if (changed) return true; + } + ); + }); + } +} +module.exports = LimitChunkCountPlugin; diff --git a/webpack-lib/lib/optimize/MangleExportsPlugin.js b/webpack-lib/lib/optimize/MangleExportsPlugin.js new file mode 100644 index 00000000000..b1dbff26989 --- /dev/null +++ b/webpack-lib/lib/optimize/MangleExportsPlugin.js @@ -0,0 +1,182 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { UsageState } = require("../ExportsInfo"); +const { + numberToIdentifier, + NUMBER_OF_IDENTIFIER_START_CHARS, + NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS +} = require("../Template"); +const { assignDeterministicIds } = require("../ids/IdHelpers"); +const { compareSelect, compareStringsNumeric } = require("../util/comparators"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../ExportsInfo")} ExportsInfo */ +/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ + +/** + * @param {ExportsInfo} exportsInfo exports info + * @returns {boolean} mangle is possible + */ +const canMangle = exportsInfo => { + if (exportsInfo.otherExportsInfo.getUsed(undefined) !== UsageState.Unused) + return false; + let hasSomethingToMangle = false; + for (const exportInfo of exportsInfo.exports) { + if (exportInfo.canMangle === true) { + hasSomethingToMangle = true; + } + } + return hasSomethingToMangle; +}; + +// Sort by name +const comparator = compareSelect(e => e.name, compareStringsNumeric); +/** + * @param {boolean} deterministic use deterministic names + * @param {ExportsInfo} exportsInfo exports info + * @param {boolean | undefined} isNamespace is namespace object + * @returns {void} + */ +const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => { + if (!canMangle(exportsInfo)) return; + const usedNames = new Set(); + /** @type {ExportInfo[]} */ + const mangleableExports = []; + + // Avoid to renamed exports that are not provided when + // 1. it's not a namespace export: non-provided exports can be found in prototype chain + // 2. there are other provided exports and deterministic mode is chosen: + // non-provided exports would break the determinism + let avoidMangleNonProvided = !isNamespace; + if (!avoidMangleNonProvided && deterministic) { + for (const exportInfo of exportsInfo.ownedExports) { + if (exportInfo.provided !== false) { + avoidMangleNonProvided = true; + break; + } + } + } + for (const exportInfo of exportsInfo.ownedExports) { + const name = exportInfo.name; + if (!exportInfo.hasUsedName()) { + if ( + // Can the export be mangled? + exportInfo.canMangle !== true || + // Never rename 1 char exports + (name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) || + // Don't rename 2 char exports in deterministic mode + (deterministic && + name.length === 2 && + /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) || + // Don't rename exports that are not provided + (avoidMangleNonProvided && exportInfo.provided !== true) + ) { + exportInfo.setUsedName(name); + usedNames.add(name); + } else { + mangleableExports.push(exportInfo); + } + } + if (exportInfo.exportsInfoOwned) { + const used = exportInfo.getUsed(undefined); + if ( + used === UsageState.OnlyPropertiesUsed || + used === UsageState.Unused + ) { + mangleExportsInfo( + deterministic, + /** @type {ExportsInfo} */ (exportInfo.exportsInfo), + false + ); + } + } + } + if (deterministic) { + assignDeterministicIds( + mangleableExports, + e => e.name, + comparator, + (e, id) => { + const name = numberToIdentifier(id); + const size = usedNames.size; + usedNames.add(name); + if (size === usedNames.size) return false; + e.setUsedName(name); + return true; + }, + [ + NUMBER_OF_IDENTIFIER_START_CHARS, + NUMBER_OF_IDENTIFIER_START_CHARS * + NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS + ], + NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS, + usedNames.size + ); + } else { + const usedExports = []; + const unusedExports = []; + for (const exportInfo of mangleableExports) { + if (exportInfo.getUsed(undefined) === UsageState.Unused) { + unusedExports.push(exportInfo); + } else { + usedExports.push(exportInfo); + } + } + usedExports.sort(comparator); + unusedExports.sort(comparator); + let i = 0; + for (const list of [usedExports, unusedExports]) { + for (const exportInfo of list) { + let name; + do { + name = numberToIdentifier(i++); + } while (usedNames.has(name)); + exportInfo.setUsedName(name); + } + } + } +}; + +class MangleExportsPlugin { + /** + * @param {boolean} deterministic use deterministic names + */ + constructor(deterministic) { + this._deterministic = deterministic; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _deterministic: deterministic } = this; + compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => { + const moduleGraph = compilation.moduleGraph; + compilation.hooks.optimizeCodeGeneration.tap( + "MangleExportsPlugin", + modules => { + if (compilation.moduleMemCaches) { + throw new Error( + "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect" + ); + } + for (const module of modules) { + const isNamespace = + module.buildMeta && module.buildMeta.exportsType === "namespace"; + const exportsInfo = moduleGraph.getExportsInfo(module); + mangleExportsInfo(deterministic, exportsInfo, isNamespace); + } + } + ); + }); + } +} + +module.exports = MangleExportsPlugin; diff --git a/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js b/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js new file mode 100644 index 00000000000..08db56823a2 --- /dev/null +++ b/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js @@ -0,0 +1,135 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_BASIC } = require("../OptimizationStages"); +const createSchemaValidation = require("../util/create-schema-validation"); +const { runtimeEqual } = require("../util/runtime"); + +/** @typedef {import("../../declarations/plugins/optimize/MergeDuplicateChunksPlugin").MergeDuplicateChunksPluginOptions} MergeDuplicateChunksPluginOptions */ +/** @typedef {import("../Compiler")} Compiler */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.check.js"), + () => + require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.json"), + { + name: "Merge Duplicate Chunks Plugin", + baseDataPath: "options" + } +); + +class MergeDuplicateChunksPlugin { + /** + * @param {MergeDuplicateChunksPluginOptions} options options object + */ + constructor(options = { stage: STAGE_BASIC }) { + validate(options); + this.options = options; + } + + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "MergeDuplicateChunksPlugin", + compilation => { + compilation.hooks.optimizeChunks.tap( + { + name: "MergeDuplicateChunksPlugin", + stage: this.options.stage + }, + chunks => { + const { chunkGraph, moduleGraph } = compilation; + + // remember already tested chunks for performance + const notDuplicates = new Set(); + + // for each chunk + for (const chunk of chunks) { + // track a Set of all chunk that could be duplicates + let possibleDuplicates; + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { + if (possibleDuplicates === undefined) { + // when possibleDuplicates is not yet set, + // create a new Set from chunks of the current module + // including only chunks with the same number of modules + for (const dup of chunkGraph.getModuleChunksIterable( + module + )) { + if ( + dup !== chunk && + chunkGraph.getNumberOfChunkModules(chunk) === + chunkGraph.getNumberOfChunkModules(dup) && + !notDuplicates.has(dup) + ) { + // delay allocating the new Set until here, reduce memory pressure + if (possibleDuplicates === undefined) { + possibleDuplicates = new Set(); + } + possibleDuplicates.add(dup); + } + } + // when no chunk is possible we can break here + if (possibleDuplicates === undefined) break; + } else { + // validate existing possible duplicates + for (const dup of possibleDuplicates) { + // remove possible duplicate when module is not contained + if (!chunkGraph.isModuleInChunk(module, dup)) { + possibleDuplicates.delete(dup); + } + } + // when all chunks has been removed we can break here + if (possibleDuplicates.size === 0) break; + } + } + + // when we found duplicates + if ( + possibleDuplicates !== undefined && + possibleDuplicates.size > 0 + ) { + outer: for (const otherChunk of possibleDuplicates) { + if (otherChunk.hasRuntime() !== chunk.hasRuntime()) continue; + if (chunkGraph.getNumberOfEntryModules(chunk) > 0) continue; + if (chunkGraph.getNumberOfEntryModules(otherChunk) > 0) + continue; + if (!runtimeEqual(chunk.runtime, otherChunk.runtime)) { + for (const module of chunkGraph.getChunkModulesIterable( + chunk + )) { + const exportsInfo = moduleGraph.getExportsInfo(module); + if ( + !exportsInfo.isEquallyUsed( + chunk.runtime, + otherChunk.runtime + ) + ) { + continue outer; + } + } + } + // merge them + if (chunkGraph.canChunksBeIntegrated(chunk, otherChunk)) { + chunkGraph.integrateChunks(chunk, otherChunk); + compilation.chunks.delete(otherChunk); + } + } + } + + // don't check already processed chunks twice + notDuplicates.add(chunk); + } + } + ); + } + ); + } +} +module.exports = MergeDuplicateChunksPlugin; diff --git a/webpack-lib/lib/optimize/MinChunkSizePlugin.js b/webpack-lib/lib/optimize/MinChunkSizePlugin.js new file mode 100644 index 00000000000..b51164c27d9 --- /dev/null +++ b/webpack-lib/lib/optimize/MinChunkSizePlugin.js @@ -0,0 +1,113 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_ADVANCED } = require("../OptimizationStages"); +const createSchemaValidation = require("../util/create-schema-validation"); + +/** @typedef {import("../../declarations/plugins/optimize/MinChunkSizePlugin").MinChunkSizePluginOptions} MinChunkSizePluginOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/optimize/MinChunkSizePlugin.check.js"), + () => require("../../schemas/plugins/optimize/MinChunkSizePlugin.json"), + { + name: "Min Chunk Size Plugin", + baseDataPath: "options" + } +); + +class MinChunkSizePlugin { + /** + * @param {MinChunkSizePluginOptions} options options object + */ + constructor(options) { + validate(options); + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const options = this.options; + const minChunkSize = options.minChunkSize; + compiler.hooks.compilation.tap("MinChunkSizePlugin", compilation => { + compilation.hooks.optimizeChunks.tap( + { + name: "MinChunkSizePlugin", + stage: STAGE_ADVANCED + }, + chunks => { + const chunkGraph = compilation.chunkGraph; + const equalOptions = { + chunkOverhead: 1, + entryChunkMultiplicator: 1 + }; + + const chunkSizesMap = new Map(); + /** @type {[Chunk, Chunk][]} */ + const combinations = []; + /** @type {Chunk[]} */ + const smallChunks = []; + const visitedChunks = []; + for (const a of chunks) { + // check if one of the chunks sizes is smaller than the minChunkSize + // and filter pairs that can NOT be integrated! + if (chunkGraph.getChunkSize(a, equalOptions) < minChunkSize) { + smallChunks.push(a); + for (const b of visitedChunks) { + if (chunkGraph.canChunksBeIntegrated(b, a)) + combinations.push([b, a]); + } + } else { + for (const b of smallChunks) { + if (chunkGraph.canChunksBeIntegrated(b, a)) + combinations.push([b, a]); + } + } + chunkSizesMap.set(a, chunkGraph.getChunkSize(a, options)); + visitedChunks.push(a); + } + + const sortedSizeFilteredExtendedPairCombinations = combinations + .map(pair => { + // extend combination pairs with size and integrated size + const a = chunkSizesMap.get(pair[0]); + const b = chunkSizesMap.get(pair[1]); + const ab = chunkGraph.getIntegratedChunksSize( + pair[0], + pair[1], + options + ); + /** @type {[number, number, Chunk, Chunk]} */ + const extendedPair = [a + b - ab, ab, pair[0], pair[1]]; + return extendedPair; + }) + .sort((a, b) => { + // sadly javascript does an in place sort here + // sort by size + const diff = b[0] - a[0]; + if (diff !== 0) return diff; + return a[1] - b[1]; + }); + + if (sortedSizeFilteredExtendedPairCombinations.length === 0) return; + + const pair = sortedSizeFilteredExtendedPairCombinations[0]; + + chunkGraph.integrateChunks(pair[2], pair[3]); + compilation.chunks.delete(pair[3]); + return true; + } + ); + }); + } +} +module.exports = MinChunkSizePlugin; diff --git a/webpack-lib/lib/optimize/MinMaxSizeWarning.js b/webpack-lib/lib/optimize/MinMaxSizeWarning.js new file mode 100644 index 00000000000..2cc845eb9f0 --- /dev/null +++ b/webpack-lib/lib/optimize/MinMaxSizeWarning.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const SizeFormatHelpers = require("../SizeFormatHelpers"); +const WebpackError = require("../WebpackError"); + +class MinMaxSizeWarning extends WebpackError { + /** + * @param {string[] | undefined} keys keys + * @param {number} minSize minimum size + * @param {number} maxSize maximum size + */ + constructor(keys, minSize, maxSize) { + let keysMessage = "Fallback cache group"; + if (keys) { + keysMessage = + keys.length > 1 + ? `Cache groups ${keys.sort().join(", ")}` + : `Cache group ${keys[0]}`; + } + super( + "SplitChunksPlugin\n" + + `${keysMessage}\n` + + `Configured minSize (${SizeFormatHelpers.formatSize(minSize)}) is ` + + `bigger than maxSize (${SizeFormatHelpers.formatSize(maxSize)}).\n` + + "This seem to be a invalid optimization.splitChunks configuration." + ); + } +} + +module.exports = MinMaxSizeWarning; diff --git a/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js b/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js new file mode 100644 index 00000000000..1dc33af9dd6 --- /dev/null +++ b/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js @@ -0,0 +1,932 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const asyncLib = require("neo-async"); +const ChunkGraph = require("../ChunkGraph"); +const ModuleGraph = require("../ModuleGraph"); +const { STAGE_DEFAULT } = require("../OptimizationStages"); +const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const { + intersectRuntime, + mergeRuntimeOwned, + filterRuntime, + runtimeToString, + mergeRuntime +} = require("../util/runtime"); +const ConcatenatedModule = require("./ConcatenatedModule"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +/** + * @typedef {object} Statistics + * @property {number} cached + * @property {number} alreadyInConfig + * @property {number} invalidModule + * @property {number} incorrectChunks + * @property {number} incorrectDependency + * @property {number} incorrectModuleDependency + * @property {number} incorrectChunksOfImporter + * @property {number} incorrectRuntimeCondition + * @property {number} importerFailed + * @property {number} added + */ + +/** + * @param {string} msg message + * @returns {string} formatted message + */ +const formatBailoutReason = msg => `ModuleConcatenation bailout: ${msg}`; + +class ModuleConcatenationPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { _backCompat: backCompat } = compiler; + compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => { + if (compilation.moduleMemCaches) { + throw new Error( + "optimization.concatenateModules can't be used with cacheUnaffected as module concatenation is a global effect" + ); + } + const moduleGraph = compilation.moduleGraph; + /** @type {Map string)>} */ + const bailoutReasonMap = new Map(); + + /** + * @param {Module} module the module + * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason + */ + const setBailoutReason = (module, reason) => { + setInnerBailoutReason(module, reason); + moduleGraph + .getOptimizationBailout(module) + .push( + typeof reason === "function" + ? rs => formatBailoutReason(reason(rs)) + : formatBailoutReason(reason) + ); + }; + + /** + * @param {Module} module the module + * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason + */ + const setInnerBailoutReason = (module, reason) => { + bailoutReasonMap.set(module, reason); + }; + + /** + * @param {Module} module the module + * @param {RequestShortener} requestShortener the request shortener + * @returns {string | ((requestShortener: RequestShortener) => string) | undefined} the reason + */ + const getInnerBailoutReason = (module, requestShortener) => { + const reason = bailoutReasonMap.get(module); + if (typeof reason === "function") return reason(requestShortener); + return reason; + }; + + /** + * @param {Module} module the module + * @param {Module | function(RequestShortener): string} problem the problem + * @returns {(requestShortener: RequestShortener) => string} the reason + */ + const formatBailoutWarning = (module, problem) => requestShortener => { + if (typeof problem === "function") { + return formatBailoutReason( + `Cannot concat with ${module.readableIdentifier( + requestShortener + )}: ${problem(requestShortener)}` + ); + } + const reason = getInnerBailoutReason(module, requestShortener); + const reasonWithPrefix = reason ? `: ${reason}` : ""; + if (module === problem) { + return formatBailoutReason( + `Cannot concat with ${module.readableIdentifier( + requestShortener + )}${reasonWithPrefix}` + ); + } + return formatBailoutReason( + `Cannot concat with ${module.readableIdentifier( + requestShortener + )} because of ${problem.readableIdentifier( + requestShortener + )}${reasonWithPrefix}` + ); + }; + + compilation.hooks.optimizeChunkModules.tapAsync( + { + name: "ModuleConcatenationPlugin", + stage: STAGE_DEFAULT + }, + (allChunks, modules, callback) => { + const logger = compilation.getLogger( + "webpack.ModuleConcatenationPlugin" + ); + const { chunkGraph, moduleGraph } = compilation; + const relevantModules = []; + const possibleInners = new Set(); + const context = { + chunkGraph, + moduleGraph + }; + logger.time("select relevant modules"); + for (const module of modules) { + let canBeRoot = true; + let canBeInner = true; + + const bailoutReason = module.getConcatenationBailoutReason(context); + if (bailoutReason) { + setBailoutReason(module, bailoutReason); + continue; + } + + // Must not be an async module + if (moduleGraph.isAsync(module)) { + setBailoutReason(module, "Module is async"); + continue; + } + + // Must be in strict mode + if (!(/** @type {BuildInfo} */ (module.buildInfo).strict)) { + setBailoutReason(module, "Module is not in strict mode"); + continue; + } + + // Module must be in any chunk (we don't want to do useless work) + if (chunkGraph.getNumberOfModuleChunks(module) === 0) { + setBailoutReason(module, "Module is not in any chunk"); + continue; + } + + // Exports must be known (and not dynamic) + const exportsInfo = moduleGraph.getExportsInfo(module); + const relevantExports = exportsInfo.getRelevantExports(undefined); + const unknownReexports = relevantExports.filter( + exportInfo => + exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph) + ); + if (unknownReexports.length > 0) { + setBailoutReason( + module, + `Reexports in this module do not have a static target (${Array.from( + unknownReexports, + exportInfo => + `${ + exportInfo.name || "other exports" + }: ${exportInfo.getUsedInfo()}` + ).join(", ")})` + ); + continue; + } + + // Root modules must have a static list of exports + const unknownProvidedExports = relevantExports.filter( + exportInfo => exportInfo.provided !== true + ); + if (unknownProvidedExports.length > 0) { + setBailoutReason( + module, + `List of module exports is dynamic (${Array.from( + unknownProvidedExports, + exportInfo => + `${ + exportInfo.name || "other exports" + }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}` + ).join(", ")})` + ); + canBeRoot = false; + } + + // Module must not be an entry point + if (chunkGraph.isEntryModule(module)) { + setInnerBailoutReason(module, "Module is an entry point"); + canBeInner = false; + } + + if (canBeRoot) relevantModules.push(module); + if (canBeInner) possibleInners.add(module); + } + logger.timeEnd("select relevant modules"); + logger.debug( + `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules` + ); + // sort by depth + // modules with lower depth are more likely suited as roots + // this improves performance, because modules already selected as inner are skipped + logger.time("sort relevant modules"); + relevantModules.sort( + (a, b) => + /** @type {number} */ (moduleGraph.getDepth(a)) - + /** @type {number} */ (moduleGraph.getDepth(b)) + ); + logger.timeEnd("sort relevant modules"); + + /** @type {Statistics} */ + const stats = { + cached: 0, + alreadyInConfig: 0, + invalidModule: 0, + incorrectChunks: 0, + incorrectDependency: 0, + incorrectModuleDependency: 0, + incorrectChunksOfImporter: 0, + incorrectRuntimeCondition: 0, + importerFailed: 0, + added: 0 + }; + let statsCandidates = 0; + let statsSizeSum = 0; + let statsEmptyConfigurations = 0; + + logger.time("find modules to concatenate"); + const concatConfigurations = []; + const usedAsInner = new Set(); + for (const currentRoot of relevantModules) { + // when used by another configuration as inner: + // the other configuration is better and we can skip this one + // TODO reconsider that when it's only used in a different runtime + if (usedAsInner.has(currentRoot)) continue; + + let chunkRuntime; + for (const r of chunkGraph.getModuleRuntimes(currentRoot)) { + chunkRuntime = mergeRuntimeOwned(chunkRuntime, r); + } + const exportsInfo = moduleGraph.getExportsInfo(currentRoot); + const filteredRuntime = filterRuntime(chunkRuntime, r => + exportsInfo.isModuleUsed(r) + ); + const activeRuntime = + filteredRuntime === true + ? chunkRuntime + : filteredRuntime === false + ? undefined + : filteredRuntime; + + // create a configuration with the root + const currentConfiguration = new ConcatConfiguration( + currentRoot, + activeRuntime + ); + + // cache failures to add modules + const failureCache = new Map(); + + // potential optional import candidates + /** @type {Set} */ + const candidates = new Set(); + + // try to add all imports + for (const imp of this._getImports( + compilation, + currentRoot, + activeRuntime + )) { + candidates.add(imp); + } + + for (const imp of candidates) { + const impCandidates = new Set(); + const problem = this._tryToAdd( + compilation, + currentConfiguration, + imp, + chunkRuntime, + activeRuntime, + possibleInners, + impCandidates, + failureCache, + chunkGraph, + true, + stats + ); + if (problem) { + failureCache.set(imp, problem); + currentConfiguration.addWarning(imp, problem); + } else { + for (const c of impCandidates) { + candidates.add(c); + } + } + } + statsCandidates += candidates.size; + if (!currentConfiguration.isEmpty()) { + const modules = currentConfiguration.getModules(); + statsSizeSum += modules.size; + concatConfigurations.push(currentConfiguration); + for (const module of modules) { + if (module !== currentConfiguration.rootModule) { + usedAsInner.add(module); + } + } + } else { + statsEmptyConfigurations++; + const optimizationBailouts = + moduleGraph.getOptimizationBailout(currentRoot); + for (const warning of currentConfiguration.getWarningsSorted()) { + optimizationBailouts.push( + formatBailoutWarning(warning[0], warning[1]) + ); + } + } + } + logger.timeEnd("find modules to concatenate"); + logger.debug( + `${ + concatConfigurations.length + } successful concat configurations (avg size: ${ + statsSizeSum / concatConfigurations.length + }), ${statsEmptyConfigurations} bailed out completely` + ); + logger.debug( + `${statsCandidates} candidates were considered for adding (${stats.cached} cached failure, ${stats.alreadyInConfig} already in config, ${stats.invalidModule} invalid module, ${stats.incorrectChunks} incorrect chunks, ${stats.incorrectDependency} incorrect dependency, ${stats.incorrectChunksOfImporter} incorrect chunks of importer, ${stats.incorrectModuleDependency} incorrect module dependency, ${stats.incorrectRuntimeCondition} incorrect runtime condition, ${stats.importerFailed} importer failed, ${stats.added} added)` + ); + // HACK: Sort configurations by length and start with the longest one + // to get the biggest groups possible. Used modules are marked with usedModules + // TODO: Allow to reuse existing configuration while trying to add dependencies. + // This would improve performance. O(n^2) -> O(n) + logger.time("sort concat configurations"); + concatConfigurations.sort((a, b) => b.modules.size - a.modules.size); + logger.timeEnd("sort concat configurations"); + const usedModules = new Set(); + + logger.time("create concatenated modules"); + asyncLib.each( + concatConfigurations, + (concatConfiguration, callback) => { + const rootModule = concatConfiguration.rootModule; + + // Avoid overlapping configurations + // TODO: remove this when todo above is fixed + if (usedModules.has(rootModule)) return callback(); + const modules = concatConfiguration.getModules(); + for (const m of modules) { + usedModules.add(m); + } + + // Create a new ConcatenatedModule + ConcatenatedModule.getCompilationHooks(compilation); + const newModule = ConcatenatedModule.create( + rootModule, + modules, + concatConfiguration.runtime, + compilation, + compiler.root, + compilation.outputOptions.hashFunction + ); + + const build = () => { + newModule.build( + compiler.options, + compilation, + /** @type {TODO} */ + (null), + /** @type {TODO} */ + (null), + err => { + if (err) { + if (!err.module) { + err.module = newModule; + } + return callback(err); + } + integrate(); + } + ); + }; + + const integrate = () => { + if (backCompat) { + ChunkGraph.setChunkGraphForModule(newModule, chunkGraph); + ModuleGraph.setModuleGraphForModule(newModule, moduleGraph); + } + + for (const warning of concatConfiguration.getWarningsSorted()) { + moduleGraph + .getOptimizationBailout(newModule) + .push(formatBailoutWarning(warning[0], warning[1])); + } + moduleGraph.cloneModuleAttributes(rootModule, newModule); + for (const m of modules) { + // add to builtModules when one of the included modules was built + if (compilation.builtModules.has(m)) { + compilation.builtModules.add(newModule); + } + if (m !== rootModule) { + // attach external references to the concatenated module too + moduleGraph.copyOutgoingModuleConnections( + m, + newModule, + c => + c.originModule === m && + !( + c.dependency instanceof HarmonyImportDependency && + modules.has(c.module) + ) + ); + // remove module from chunk + for (const chunk of chunkGraph.getModuleChunksIterable( + rootModule + )) { + const sourceTypes = chunkGraph.getChunkModuleSourceTypes( + chunk, + m + ); + if (sourceTypes.size === 1) { + chunkGraph.disconnectChunkAndModule(chunk, m); + } else { + const newSourceTypes = new Set(sourceTypes); + newSourceTypes.delete("javascript"); + chunkGraph.setChunkModuleSourceTypes( + chunk, + m, + newSourceTypes + ); + } + } + } + } + compilation.modules.delete(rootModule); + ChunkGraph.clearChunkGraphForModule(rootModule); + ModuleGraph.clearModuleGraphForModule(rootModule); + + // remove module from chunk + chunkGraph.replaceModule(rootModule, newModule); + // replace module references with the concatenated module + moduleGraph.moveModuleConnections(rootModule, newModule, c => { + const otherModule = + c.module === rootModule ? c.originModule : c.module; + const innerConnection = + c.dependency instanceof HarmonyImportDependency && + modules.has(/** @type {Module} */ (otherModule)); + return !innerConnection; + }); + // add concatenated module to the compilation + compilation.modules.add(newModule); + + callback(); + }; + + build(); + }, + err => { + logger.timeEnd("create concatenated modules"); + process.nextTick(callback.bind(null, err)); + } + ); + } + ); + }); + } + + /** + * @param {Compilation} compilation the compilation + * @param {Module} module the module to be added + * @param {RuntimeSpec} runtime the runtime scope + * @returns {Set} the imported modules + */ + _getImports(compilation, module, runtime) { + const moduleGraph = compilation.moduleGraph; + const set = new Set(); + for (const dep of module.dependencies) { + // Get reference info only for harmony Dependencies + if (!(dep instanceof HarmonyImportDependency)) continue; + + const connection = moduleGraph.getConnection(dep); + // Reference is valid and has a module + if ( + !connection || + !connection.module || + !connection.isTargetActive(runtime) + ) { + continue; + } + + const importedNames = compilation.getDependencyReferencedExports( + dep, + undefined + ); + + if ( + importedNames.every(i => + Array.isArray(i) ? i.length > 0 : i.name.length > 0 + ) || + Array.isArray(moduleGraph.getProvidedExports(module)) + ) { + set.add(connection.module); + } + } + return set; + } + + /** + * @param {Compilation} compilation webpack compilation + * @param {ConcatConfiguration} config concat configuration (will be modified when added) + * @param {Module} module the module to be added + * @param {RuntimeSpec} runtime the runtime scope of the generated code + * @param {RuntimeSpec} activeRuntime the runtime scope of the root module + * @param {Set} possibleModules modules that are candidates + * @param {Set} candidates list of potential candidates (will be added to) + * @param {Map} failureCache cache for problematic modules to be more performant + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails + * @param {Statistics} statistics gathering metrics + * @returns {null | Module | function(RequestShortener): string} the problematic module + */ + _tryToAdd( + compilation, + config, + module, + runtime, + activeRuntime, + possibleModules, + candidates, + failureCache, + chunkGraph, + avoidMutateOnFailure, + statistics + ) { + const cacheEntry = failureCache.get(module); + if (cacheEntry) { + statistics.cached++; + return cacheEntry; + } + + // Already added? + if (config.has(module)) { + statistics.alreadyInConfig++; + return null; + } + + // Not possible to add? + if (!possibleModules.has(module)) { + statistics.invalidModule++; + failureCache.set(module, module); // cache failures for performance + return module; + } + + // Module must be in the correct chunks + const missingChunks = Array.from( + chunkGraph.getModuleChunksIterable(config.rootModule) + ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk)); + if (missingChunks.length > 0) { + /** + * @param {RequestShortener} requestShortener request shortener + * @returns {string} problem description + */ + const problem = requestShortener => { + const missingChunksList = Array.from( + new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)")) + ).sort(); + const chunks = Array.from( + new Set( + Array.from(chunkGraph.getModuleChunksIterable(module)).map( + chunk => chunk.name || "unnamed chunk(s)" + ) + ) + ).sort(); + return `Module ${module.readableIdentifier( + requestShortener + )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join( + ", " + )}, module is in chunk(s) ${chunks.join(", ")})`; + }; + statistics.incorrectChunks++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + + const moduleGraph = compilation.moduleGraph; + + const incomingConnections = + moduleGraph.getIncomingConnectionsByOriginModule(module); + + const incomingConnectionsFromNonModules = + incomingConnections.get(null) || incomingConnections.get(undefined); + if (incomingConnectionsFromNonModules) { + const activeNonModulesConnections = + incomingConnectionsFromNonModules.filter(connection => + // We are not interested in inactive connections + // or connections without dependency + connection.isActive(runtime) + ); + if (activeNonModulesConnections.length > 0) { + /** + * @param {RequestShortener} requestShortener request shortener + * @returns {string} problem description + */ + const problem = requestShortener => { + const importingExplanations = new Set( + activeNonModulesConnections.map(c => c.explanation).filter(Boolean) + ); + const explanations = Array.from(importingExplanations).sort(); + return `Module ${module.readableIdentifier( + requestShortener + )} is referenced ${ + explanations.length > 0 + ? `by: ${explanations.join(", ")}` + : "in an unsupported way" + }`; + }; + statistics.incorrectDependency++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + } + + /** @type {Map} */ + const incomingConnectionsFromModules = new Map(); + for (const [originModule, connections] of incomingConnections) { + if (originModule) { + // Ignore connection from orphan modules + if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue; + + // We don't care for connections from other runtimes + let originRuntime; + for (const r of chunkGraph.getModuleRuntimes(originModule)) { + originRuntime = mergeRuntimeOwned(originRuntime, r); + } + + if (!intersectRuntime(runtime, originRuntime)) continue; + + // We are not interested in inactive connections + const activeConnections = connections.filter(connection => + connection.isActive(runtime) + ); + if (activeConnections.length > 0) + incomingConnectionsFromModules.set(originModule, activeConnections); + } + } + + const incomingModules = Array.from(incomingConnectionsFromModules.keys()); + + // Module must be in the same chunks like the referencing module + const otherChunkModules = incomingModules.filter(originModule => { + for (const chunk of chunkGraph.getModuleChunksIterable( + config.rootModule + )) { + if (!chunkGraph.isModuleInChunk(originModule, chunk)) { + return true; + } + } + return false; + }); + if (otherChunkModules.length > 0) { + /** + * @param {RequestShortener} requestShortener request shortener + * @returns {string} problem description + */ + const problem = requestShortener => { + const names = otherChunkModules + .map(m => m.readableIdentifier(requestShortener)) + .sort(); + return `Module ${module.readableIdentifier( + requestShortener + )} is referenced from different chunks by these modules: ${names.join( + ", " + )}`; + }; + statistics.incorrectChunksOfImporter++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + + /** @type {Map} */ + const nonHarmonyConnections = new Map(); + for (const [originModule, connections] of incomingConnectionsFromModules) { + const selected = connections.filter( + connection => + !connection.dependency || + !(connection.dependency instanceof HarmonyImportDependency) + ); + if (selected.length > 0) + nonHarmonyConnections.set(originModule, connections); + } + if (nonHarmonyConnections.size > 0) { + /** + * @param {RequestShortener} requestShortener request shortener + * @returns {string} problem description + */ + const problem = requestShortener => { + const names = Array.from(nonHarmonyConnections) + .map( + ([originModule, connections]) => + `${originModule.readableIdentifier( + requestShortener + )} (referenced with ${Array.from( + new Set( + connections + .map(c => c.dependency && c.dependency.type) + .filter(Boolean) + ) + ) + .sort() + .join(", ")})` + ) + .sort(); + return `Module ${module.readableIdentifier( + requestShortener + )} is referenced from these modules with unsupported syntax: ${names.join( + ", " + )}`; + }; + statistics.incorrectModuleDependency++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + + if (runtime !== undefined && typeof runtime !== "string") { + // Module must be consistently referenced in the same runtimes + /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */ + const otherRuntimeConnections = []; + outer: for (const [ + originModule, + connections + ] of incomingConnectionsFromModules) { + /** @type {false | RuntimeSpec} */ + let currentRuntimeCondition = false; + for (const connection of connections) { + const runtimeCondition = filterRuntime(runtime, runtime => + connection.isTargetActive(runtime) + ); + if (runtimeCondition === false) continue; + if (runtimeCondition === true) continue outer; + currentRuntimeCondition = + currentRuntimeCondition !== false + ? mergeRuntime(currentRuntimeCondition, runtimeCondition) + : runtimeCondition; + } + if (currentRuntimeCondition !== false) { + otherRuntimeConnections.push({ + originModule, + runtimeCondition: currentRuntimeCondition + }); + } + } + if (otherRuntimeConnections.length > 0) { + /** + * @param {RequestShortener} requestShortener request shortener + * @returns {string} problem description + */ + const problem = requestShortener => + `Module ${module.readableIdentifier( + requestShortener + )} is runtime-dependent referenced by these modules: ${Array.from( + otherRuntimeConnections, + ({ originModule, runtimeCondition }) => + `${originModule.readableIdentifier( + requestShortener + )} (expected runtime ${runtimeToString( + runtime + )}, module is only referenced in ${runtimeToString( + /** @type {RuntimeSpec} */ (runtimeCondition) + )})` + ).join(", ")}`; + statistics.incorrectRuntimeCondition++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + } + + let backup; + if (avoidMutateOnFailure) { + backup = config.snapshot(); + } + + // Add the module + config.add(module); + + incomingModules.sort(compareModulesByIdentifier); + + // Every module which depends on the added module must be in the configuration too. + for (const originModule of incomingModules) { + const problem = this._tryToAdd( + compilation, + config, + originModule, + runtime, + activeRuntime, + possibleModules, + candidates, + failureCache, + chunkGraph, + false, + statistics + ); + if (problem) { + if (backup !== undefined) config.rollback(backup); + statistics.importerFailed++; + failureCache.set(module, problem); // cache failures for performance + return problem; + } + } + + // Add imports to possible candidates list + for (const imp of this._getImports(compilation, module, runtime)) { + candidates.add(imp); + } + statistics.added++; + return null; + } +} + +class ConcatConfiguration { + /** + * @param {Module} rootModule the root module + * @param {RuntimeSpec} runtime the runtime + */ + constructor(rootModule, runtime) { + this.rootModule = rootModule; + this.runtime = runtime; + /** @type {Set} */ + this.modules = new Set(); + this.modules.add(rootModule); + /** @type {Map} */ + this.warnings = new Map(); + } + + /** + * @param {Module} module the module + */ + add(module) { + this.modules.add(module); + } + + /** + * @param {Module} module the module + * @returns {boolean} true, when the module is in the module set + */ + has(module) { + return this.modules.has(module); + } + + isEmpty() { + return this.modules.size === 1; + } + + /** + * @param {Module} module the module + * @param {Module | function(RequestShortener): string} problem the problem + */ + addWarning(module, problem) { + this.warnings.set(module, problem); + } + + /** + * @returns {Map} warnings + */ + getWarningsSorted() { + return new Map( + Array.from(this.warnings).sort((a, b) => { + const ai = a[0].identifier(); + const bi = b[0].identifier(); + if (ai < bi) return -1; + if (ai > bi) return 1; + return 0; + }) + ); + } + + /** + * @returns {Set} modules as set + */ + getModules() { + return this.modules; + } + + snapshot() { + return this.modules.size; + } + + /** + * @param {number} snapshot snapshot + */ + rollback(snapshot) { + const modules = this.modules; + for (const m of modules) { + if (snapshot === 0) { + modules.delete(m); + } else { + snapshot--; + } + } + } +} + +module.exports = ModuleConcatenationPlugin; diff --git a/webpack-lib/lib/optimize/RealContentHashPlugin.js b/webpack-lib/lib/optimize/RealContentHashPlugin.js new file mode 100644 index 00000000000..8b0ab056525 --- /dev/null +++ b/webpack-lib/lib/optimize/RealContentHashPlugin.js @@ -0,0 +1,464 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncBailHook } = require("tapable"); +const { RawSource, CachedSource, CompatSource } = require("webpack-sources"); +const Compilation = require("../Compilation"); +const WebpackError = require("../WebpackError"); +const { compareSelect, compareStrings } = require("../util/comparators"); +const createHash = require("../util/createHash"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Cache").Etag} Etag */ +/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {typeof import("../util/Hash")} Hash */ + +const EMPTY_SET = new Set(); + +/** + * @template T + * @param {T | T[]} itemOrItems item or items + * @param {Set} list list + */ +const addToList = (itemOrItems, list) => { + if (Array.isArray(itemOrItems)) { + for (const item of itemOrItems) { + list.add(item); + } + } else if (itemOrItems) { + list.add(itemOrItems); + } +}; + +/** + * @template T + * @param {T[]} input list + * @param {function(T): Buffer} fn map function + * @returns {Buffer[]} buffers without duplicates + */ +const mapAndDeduplicateBuffers = (input, fn) => { + // Buffer.equals compares size first so this should be efficient enough + // If it becomes a performance problem we can use a map and group by size + // instead of looping over all assets. + const result = []; + outer: for (const value of input) { + const buf = fn(value); + for (const other of result) { + if (buf.equals(other)) continue outer; + } + result.push(buf); + } + return result; +}; + +/** + * Escapes regular expression metacharacters + * @param {string} str String to quote + * @returns {string} Escaped string + */ +const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); + +const cachedSourceMap = new WeakMap(); + +/** + * @param {Source} source source + * @returns {CachedSource} cached source + */ +const toCachedSource = source => { + if (source instanceof CachedSource) { + return source; + } + const entry = cachedSourceMap.get(source); + if (entry !== undefined) return entry; + const newSource = new CachedSource(CompatSource.from(source)); + cachedSourceMap.set(source, newSource); + return newSource; +}; + +/** @typedef {Set} OwnHashes */ +/** @typedef {Set} ReferencedHashes */ +/** @typedef {Set} Hashes */ + +/** + * @typedef {object} AssetInfoForRealContentHash + * @property {string} name + * @property {AssetInfo} info + * @property {Source} source + * @property {RawSource | undefined} newSource + * @property {RawSource | undefined} newSourceWithoutOwn + * @property {string} content + * @property {OwnHashes | undefined} ownHashes + * @property {Promise | undefined} contentComputePromise + * @property {Promise | undefined} contentComputeWithoutOwnPromise + * @property {ReferencedHashes | undefined} referencedHashes + * @property {Hashes} hashes + */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncBailHook<[Buffer[], string], string | void>} updateHash + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class RealContentHashPlugin { + /** + * @param {Compilation} compilation the compilation + * @returns {CompilationHooks} 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 = { + updateHash: new SyncBailHook(["content", "oldHash"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {object} options options object + * @param {string | Hash} options.hashFunction the hash function to use + * @param {string} options.hashDigest the hash digest to use + */ + constructor({ hashFunction, hashDigest }) { + this._hashFunction = hashFunction; + this._hashDigest = hashDigest; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("RealContentHashPlugin", compilation => { + const cacheAnalyse = compilation.getCache( + "RealContentHashPlugin|analyse" + ); + const cacheGenerate = compilation.getCache( + "RealContentHashPlugin|generate" + ); + const hooks = RealContentHashPlugin.getCompilationHooks(compilation); + compilation.hooks.processAssets.tapPromise( + { + name: "RealContentHashPlugin", + stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH + }, + async () => { + const assets = compilation.getAssets(); + /** @type {AssetInfoForRealContentHash[]} */ + const assetsWithInfo = []; + /** @type {Map} */ + const hashToAssets = new Map(); + for (const { source, info, name } of assets) { + const cachedSource = toCachedSource(source); + const content = /** @type {string} */ (cachedSource.source()); + /** @type {Hashes} */ + const hashes = new Set(); + addToList(info.contenthash, hashes); + /** @type {AssetInfoForRealContentHash} */ + const data = { + name, + info, + source: cachedSource, + newSource: undefined, + newSourceWithoutOwn: undefined, + content, + ownHashes: undefined, + contentComputePromise: undefined, + contentComputeWithoutOwnPromise: undefined, + referencedHashes: undefined, + hashes + }; + assetsWithInfo.push(data); + for (const hash of hashes) { + const list = hashToAssets.get(hash); + if (list === undefined) { + hashToAssets.set(hash, [data]); + } else { + list.push(data); + } + } + } + if (hashToAssets.size === 0) return; + const hashRegExp = new RegExp( + Array.from(hashToAssets.keys(), quoteMeta).join("|"), + "g" + ); + await Promise.all( + assetsWithInfo.map(async asset => { + const { name, source, content, hashes } = asset; + if (Buffer.isBuffer(content)) { + asset.referencedHashes = EMPTY_SET; + asset.ownHashes = EMPTY_SET; + return; + } + const etag = cacheAnalyse.mergeEtags( + cacheAnalyse.getLazyHashedEtag(source), + Array.from(hashes).join("|") + ); + [asset.referencedHashes, asset.ownHashes] = + await cacheAnalyse.providePromise(name, etag, () => { + const referencedHashes = new Set(); + const ownHashes = new Set(); + const inContent = content.match(hashRegExp); + if (inContent) { + for (const hash of inContent) { + if (hashes.has(hash)) { + ownHashes.add(hash); + continue; + } + referencedHashes.add(hash); + } + } + return [referencedHashes, ownHashes]; + }); + }) + ); + /** + * @param {string} hash the hash + * @returns {undefined | ReferencedHashes} the referenced hashes + */ + const getDependencies = hash => { + const assets = hashToAssets.get(hash); + if (!assets) { + const referencingAssets = assetsWithInfo.filter(asset => + /** @type {ReferencedHashes} */ (asset.referencedHashes).has( + hash + ) + ); + const err = new WebpackError(`RealContentHashPlugin +Some kind of unexpected caching problem occurred. +An asset was cached with a reference to another asset (${hash}) that's not in the compilation anymore. +Either the asset was incorrectly cached, or the referenced asset should also be restored from cache. +Referenced by: +${referencingAssets + .map(a => { + const match = new RegExp(`.{0,20}${quoteMeta(hash)}.{0,20}`).exec( + a.content + ); + return ` - ${a.name}: ...${match ? match[0] : "???"}...`; + }) + .join("\n")}`); + compilation.errors.push(err); + return; + } + const hashes = new Set(); + for (const { referencedHashes, ownHashes } of assets) { + if (!(/** @type {OwnHashes} */ (ownHashes).has(hash))) { + for (const hash of /** @type {OwnHashes} */ (ownHashes)) { + hashes.add(hash); + } + } + for (const hash of /** @type {ReferencedHashes} */ ( + referencedHashes + )) { + hashes.add(hash); + } + } + return hashes; + }; + /** + * @param {string} hash the hash + * @returns {string} the hash info + */ + const hashInfo = hash => { + const assets = hashToAssets.get(hash); + return `${hash} (${Array.from( + /** @type {AssetInfoForRealContentHash[]} */ (assets), + a => a.name + )})`; + }; + const hashesInOrder = new Set(); + for (const hash of hashToAssets.keys()) { + /** + * @param {string} hash the hash + * @param {Set} stack stack of hashes + */ + const add = (hash, stack) => { + const deps = getDependencies(hash); + if (!deps) return; + stack.add(hash); + for (const dep of deps) { + if (hashesInOrder.has(dep)) continue; + if (stack.has(dep)) { + throw new Error( + `Circular hash dependency ${Array.from( + stack, + hashInfo + ).join(" -> ")} -> ${hashInfo(dep)}` + ); + } + add(dep, stack); + } + hashesInOrder.add(hash); + stack.delete(hash); + }; + if (hashesInOrder.has(hash)) continue; + add(hash, new Set()); + } + const hashToNewHash = new Map(); + /** + * @param {AssetInfoForRealContentHash} asset asset info + * @returns {Etag} etag + */ + const getEtag = asset => + cacheGenerate.mergeEtags( + cacheGenerate.getLazyHashedEtag(asset.source), + Array.from( + /** @type {ReferencedHashes} */ (asset.referencedHashes), + hash => hashToNewHash.get(hash) + ).join("|") + ); + /** + * @param {AssetInfoForRealContentHash} asset asset info + * @returns {Promise} + */ + const computeNewContent = asset => { + if (asset.contentComputePromise) return asset.contentComputePromise; + return (asset.contentComputePromise = (async () => { + if ( + /** @type {OwnHashes} */ (asset.ownHashes).size > 0 || + Array.from( + /** @type {ReferencedHashes} */ + (asset.referencedHashes) + ).some(hash => hashToNewHash.get(hash) !== hash) + ) { + const identifier = asset.name; + const etag = getEtag(asset); + asset.newSource = await cacheGenerate.providePromise( + identifier, + etag, + () => { + const newContent = asset.content.replace(hashRegExp, hash => + hashToNewHash.get(hash) + ); + return new RawSource(newContent); + } + ); + } + })()); + }; + /** + * @param {AssetInfoForRealContentHash} asset asset info + * @returns {Promise} + */ + const computeNewContentWithoutOwn = asset => { + if (asset.contentComputeWithoutOwnPromise) + return asset.contentComputeWithoutOwnPromise; + return (asset.contentComputeWithoutOwnPromise = (async () => { + if ( + /** @type {OwnHashes} */ (asset.ownHashes).size > 0 || + Array.from( + /** @type {ReferencedHashes} */ + (asset.referencedHashes) + ).some(hash => hashToNewHash.get(hash) !== hash) + ) { + const identifier = `${asset.name}|without-own`; + const etag = getEtag(asset); + asset.newSourceWithoutOwn = await cacheGenerate.providePromise( + identifier, + etag, + () => { + const newContent = asset.content.replace( + hashRegExp, + hash => { + if ( + /** @type {OwnHashes} */ (asset.ownHashes).has(hash) + ) { + return ""; + } + return hashToNewHash.get(hash); + } + ); + return new RawSource(newContent); + } + ); + } + })()); + }; + const comparator = compareSelect(a => a.name, compareStrings); + for (const oldHash of hashesInOrder) { + const assets = + /** @type {AssetInfoForRealContentHash[]} */ + (hashToAssets.get(oldHash)); + assets.sort(comparator); + await Promise.all( + assets.map(asset => + /** @type {OwnHashes} */ (asset.ownHashes).has(oldHash) + ? computeNewContentWithoutOwn(asset) + : computeNewContent(asset) + ) + ); + const assetsContent = mapAndDeduplicateBuffers(assets, asset => { + if (/** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)) { + return asset.newSourceWithoutOwn + ? asset.newSourceWithoutOwn.buffer() + : asset.source.buffer(); + } + return asset.newSource + ? asset.newSource.buffer() + : asset.source.buffer(); + }); + let newHash = hooks.updateHash.call(assetsContent, oldHash); + if (!newHash) { + const hash = createHash(this._hashFunction); + if (compilation.outputOptions.hashSalt) { + hash.update(compilation.outputOptions.hashSalt); + } + for (const content of assetsContent) { + hash.update(content); + } + const digest = hash.digest(this._hashDigest); + newHash = /** @type {string} */ (digest.slice(0, oldHash.length)); + } + hashToNewHash.set(oldHash, newHash); + } + await Promise.all( + assetsWithInfo.map(async asset => { + await computeNewContent(asset); + const newName = asset.name.replace(hashRegExp, hash => + hashToNewHash.get(hash) + ); + + const infoUpdate = {}; + const hash = asset.info.contenthash; + infoUpdate.contenthash = Array.isArray(hash) + ? hash.map(hash => hashToNewHash.get(hash)) + : hashToNewHash.get(hash); + + if (asset.newSource !== undefined) { + compilation.updateAsset( + asset.name, + asset.newSource, + infoUpdate + ); + } else { + compilation.updateAsset(asset.name, asset.source, infoUpdate); + } + + if (asset.name !== newName) { + compilation.renameAsset(asset.name, newName); + } + }) + ); + } + ); + }); + } +} + +module.exports = RealContentHashPlugin; diff --git a/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js b/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js new file mode 100644 index 00000000000..6dbc2ae6aa0 --- /dev/null +++ b/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js @@ -0,0 +1,57 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_BASIC, STAGE_ADVANCED } = require("../OptimizationStages"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +class RemoveEmptyChunksPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("RemoveEmptyChunksPlugin", compilation => { + /** + * @param {Iterable} chunks the chunks array + * @returns {void} + */ + const handler = chunks => { + const chunkGraph = compilation.chunkGraph; + for (const chunk of chunks) { + if ( + chunkGraph.getNumberOfChunkModules(chunk) === 0 && + !chunk.hasRuntime() && + chunkGraph.getNumberOfEntryModules(chunk) === 0 + ) { + compilation.chunkGraph.disconnectChunk(chunk); + compilation.chunks.delete(chunk); + } + } + }; + + // TODO do it once + compilation.hooks.optimizeChunks.tap( + { + name: "RemoveEmptyChunksPlugin", + stage: STAGE_BASIC + }, + handler + ); + compilation.hooks.optimizeChunks.tap( + { + name: "RemoveEmptyChunksPlugin", + stage: STAGE_ADVANCED + }, + handler + ); + }); + } +} +module.exports = RemoveEmptyChunksPlugin; diff --git a/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js b/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js new file mode 100644 index 00000000000..8c244ec5077 --- /dev/null +++ b/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js @@ -0,0 +1,204 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { STAGE_BASIC } = require("../OptimizationStages"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ + +/** + * Intersects multiple masks represented as bigints + * @param {bigint[]} masks The module masks to intersect + * @returns {bigint} The intersection of all masks + */ +function intersectMasks(masks) { + let result = masks[0]; + for (let i = masks.length - 1; i >= 1; i--) { + result &= masks[i]; + } + return result; +} + +const ZERO_BIGINT = BigInt(0); +const ONE_BIGINT = BigInt(1); +const THIRTY_TWO_BIGINT = BigInt(32); + +/** + * Parses the module mask and returns the modules represented by it + * @param {bigint} mask the module mask + * @param {Module[]} ordinalModules the modules in the order they were added to the mask (LSB is index 0) + * @returns {Generator} the modules represented by the mask + */ +function* getModulesFromMask(mask, ordinalModules) { + let offset = 31; + while (mask !== ZERO_BIGINT) { + // Consider the last 32 bits, since that's what Math.clz32 can handle + let last32 = Number(BigInt.asUintN(32, mask)); + while (last32 > 0) { + const last = Math.clz32(last32); + // The number of trailing zeros is the number trimmed off the input mask + 31 - the number of leading zeros + // The 32 is baked into the initial value of offset + const moduleIndex = offset - last; + // The number of trailing zeros is the index into the array generated by getOrCreateModuleMask + const module = ordinalModules[moduleIndex]; + yield module; + // Remove the matched module from the mask + // Since we can only count leading zeros, not trailing, we can't just downshift the mask + last32 &= ~(1 << (31 - last)); + } + + // Remove the processed chunk from the mask + mask >>= THIRTY_TWO_BIGINT; + offset += 32; + } +} + +class RemoveParentModulesPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("RemoveParentModulesPlugin", compilation => { + /** + * @param {Iterable} chunks the chunks + * @param {ChunkGroup[]} chunkGroups the chunk groups + */ + const handler = (chunks, chunkGroups) => { + const chunkGraph = compilation.chunkGraph; + const queue = new Set(); + const availableModulesMap = new WeakMap(); + + let nextModuleMask = ONE_BIGINT; + const maskByModule = new WeakMap(); + /** @type {Module[]} */ + const ordinalModules = []; + + /** + * Gets or creates a unique mask for a module + * @param {Module} mod the module to get the mask for + * @returns {bigint} the module mask to uniquely identify the module + */ + const getOrCreateModuleMask = mod => { + let id = maskByModule.get(mod); + if (id === undefined) { + id = nextModuleMask; + ordinalModules.push(mod); + maskByModule.set(mod, id); + nextModuleMask <<= ONE_BIGINT; + } + return id; + }; + + // Initialize masks by chunk and by chunk group for quicker comparisons + const chunkMasks = new WeakMap(); + for (const chunk of chunks) { + let mask = ZERO_BIGINT; + for (const m of chunkGraph.getChunkModulesIterable(chunk)) { + const id = getOrCreateModuleMask(m); + mask |= id; + } + chunkMasks.set(chunk, mask); + } + + const chunkGroupMasks = new WeakMap(); + for (const chunkGroup of chunkGroups) { + let mask = ZERO_BIGINT; + for (const chunk of chunkGroup.chunks) { + const chunkMask = chunkMasks.get(chunk); + if (chunkMask !== undefined) { + mask |= chunkMask; + } + } + chunkGroupMasks.set(chunkGroup, mask); + } + + for (const chunkGroup of compilation.entrypoints.values()) { + // initialize available modules for chunks without parents + availableModulesMap.set(chunkGroup, ZERO_BIGINT); + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + for (const chunkGroup of compilation.asyncEntrypoints) { + // initialize available modules for chunks without parents + availableModulesMap.set(chunkGroup, ZERO_BIGINT); + for (const child of chunkGroup.childrenIterable) { + queue.add(child); + } + } + + for (const chunkGroup of queue) { + let availableModulesMask = availableModulesMap.get(chunkGroup); + let changed = false; + for (const parent of chunkGroup.parentsIterable) { + const availableModulesInParent = availableModulesMap.get(parent); + if (availableModulesInParent !== undefined) { + const parentMask = + availableModulesInParent | chunkGroupMasks.get(parent); + // If we know the available modules in parent: process these + if (availableModulesMask === undefined) { + // if we have not own info yet: create new entry + availableModulesMask = parentMask; + changed = true; + } else { + const newMask = availableModulesMask & parentMask; + if (newMask !== availableModulesMask) { + changed = true; + availableModulesMask = newMask; + } + } + } + } + + if (changed) { + availableModulesMap.set(chunkGroup, availableModulesMask); + // if something changed: enqueue our children + for (const child of chunkGroup.childrenIterable) { + // Push the child to the end of the queue + queue.delete(child); + queue.add(child); + } + } + } + + // now we have available modules for every chunk + for (const chunk of chunks) { + const chunkMask = chunkMasks.get(chunk); + if (chunkMask === undefined) continue; // No info about this chunk + + const availableModulesSets = Array.from( + chunk.groupsIterable, + chunkGroup => availableModulesMap.get(chunkGroup) + ); + if (availableModulesSets.includes(undefined)) continue; // No info about this chunk group + + const availableModulesMask = intersectMasks(availableModulesSets); + const toRemoveMask = chunkMask & availableModulesMask; + if (toRemoveMask !== ZERO_BIGINT) { + for (const module of getModulesFromMask( + toRemoveMask, + ordinalModules + )) { + chunkGraph.disconnectChunkAndModule(chunk, module); + } + } + } + }; + compilation.hooks.optimizeChunks.tap( + { + name: "RemoveParentModulesPlugin", + stage: STAGE_BASIC + }, + handler + ); + }); + } +} +module.exports = RemoveParentModulesPlugin; diff --git a/webpack-lib/lib/optimize/RuntimeChunkPlugin.js b/webpack-lib/lib/optimize/RuntimeChunkPlugin.js new file mode 100644 index 00000000000..1923e468303 --- /dev/null +++ b/webpack-lib/lib/optimize/RuntimeChunkPlugin.js @@ -0,0 +1,57 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Compilation").EntryData} EntryData */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Entrypoint")} Entrypoint */ + +class RuntimeChunkPlugin { + /** + * @param {{ name?: (entrypoint: { name: string }) => string }} options options + */ + constructor(options) { + this.options = { + /** + * @param {Entrypoint} entrypoint entrypoint name + * @returns {string} runtime chunk name + */ + name: entrypoint => `runtime~${entrypoint.name}`, + ...options + }; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap("RuntimeChunkPlugin", compilation => { + compilation.hooks.addEntry.tap( + "RuntimeChunkPlugin", + (_, { name: entryName }) => { + if (entryName === undefined) return; + const data = + /** @type {EntryData} */ + (compilation.entries.get(entryName)); + if (data.options.runtime === undefined && !data.options.dependOn) { + // Determine runtime chunk name + let name = + /** @type {string | ((entrypoint: { name: string }) => string)} */ + (this.options.name); + if (typeof name === "function") { + name = name({ name: entryName }); + } + data.options.runtime = name; + } + } + ); + }); + } +} + +module.exports = RuntimeChunkPlugin; diff --git a/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js b/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js new file mode 100644 index 00000000000..0edb048db26 --- /dev/null +++ b/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js @@ -0,0 +1,396 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const glob2regexp = require("glob-to-regexp"); +const { + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM, + JAVASCRIPT_MODULE_TYPE_DYNAMIC +} = require("../ModuleTypeConstants"); +const { STAGE_DEFAULT } = require("../OptimizationStages"); +const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency"); +const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency"); +const formatLocation = require("../formatLocation"); + +/** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */ +/** @typedef {import("estree").Statement} Statement */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../NormalModuleFactory").ModuleSettings} ModuleSettings */ +/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +/** + * @typedef {object} ExportInModule + * @property {Module} module the module + * @property {string} exportName the name of the export + * @property {boolean} checked if the export is conditional + */ + +/** + * @typedef {object} ReexportInfo + * @property {Map} static + * @property {Map>} dynamic + */ + +/** @typedef {Map} CacheItem */ + +/** @type {WeakMap} */ +const globToRegexpCache = new WeakMap(); + +/** + * @param {string} glob the pattern + * @param {Map} cache the glob to RegExp cache + * @returns {RegExp} a regular expression + */ +const globToRegexp = (glob, cache) => { + const cacheEntry = cache.get(glob); + if (cacheEntry !== undefined) return cacheEntry; + if (!glob.includes("/")) { + glob = `**/${glob}`; + } + const baseRegexp = glob2regexp(glob, { globstar: true, extended: true }); + const regexpSource = baseRegexp.source; + const regexp = new RegExp(`^(\\./)?${regexpSource.slice(1)}`); + cache.set(glob, regexp); + return regexp; +}; + +const PLUGIN_NAME = "SideEffectsFlagPlugin"; + +class SideEffectsFlagPlugin { + /** + * @param {boolean} analyseSource analyse source code for side effects + */ + constructor(analyseSource = true) { + this._analyseSource = analyseSource; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + let cache = globToRegexpCache.get(compiler.root); + if (cache === undefined) { + cache = new Map(); + globToRegexpCache.set(compiler.root, cache); + } + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const moduleGraph = compilation.moduleGraph; + normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => { + const resolveData = data.resourceResolveData; + if ( + resolveData && + resolveData.descriptionFileData && + resolveData.relativePath + ) { + const sideEffects = resolveData.descriptionFileData.sideEffects; + if (sideEffects !== undefined) { + if (module.factoryMeta === undefined) { + module.factoryMeta = {}; + } + const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( + resolveData.relativePath, + sideEffects, + /** @type {CacheItem} */ (cache) + ); + module.factoryMeta.sideEffectFree = !hasSideEffects; + } + } + + return module; + }); + normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => { + const settings = /** @type {ModuleSettings} */ (data.settings); + if (typeof settings.sideEffects === "boolean") { + if (module.factoryMeta === undefined) { + module.factoryMeta = {}; + } + module.factoryMeta.sideEffectFree = !settings.sideEffects; + } + return module; + }); + if (this._analyseSource) { + /** + * @param {JavascriptParser} parser the parser + * @returns {void} + */ + const parserHandler = parser => { + /** @type {undefined | Statement | ModuleDeclaration} */ + let sideEffectsStatement; + parser.hooks.program.tap(PLUGIN_NAME, () => { + sideEffectsStatement = undefined; + }); + parser.hooks.statement.tap( + { name: PLUGIN_NAME, stage: -100 }, + statement => { + if (sideEffectsStatement) return; + if (parser.scope.topLevelScope !== true) return; + switch (statement.type) { + case "ExpressionStatement": + if ( + !parser.isPure( + statement.expression, + /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + break; + case "IfStatement": + case "WhileStatement": + case "DoWhileStatement": + if ( + !parser.isPure( + statement.test, + /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + // statement hook will be called for child statements too + break; + case "ForStatement": + if ( + !parser.isPure( + statement.init, + /** @type {Range} */ (statement.range)[0] + ) || + !parser.isPure( + statement.test, + statement.init + ? /** @type {Range} */ (statement.init.range)[1] + : /** @type {Range} */ (statement.range)[0] + ) || + !parser.isPure( + statement.update, + statement.test + ? /** @type {Range} */ (statement.test.range)[1] + : statement.init + ? /** @type {Range} */ (statement.init.range)[1] + : /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + // statement hook will be called for child statements too + break; + case "SwitchStatement": + if ( + !parser.isPure( + statement.discriminant, + /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + // statement hook will be called for child statements too + break; + case "VariableDeclaration": + case "ClassDeclaration": + case "FunctionDeclaration": + if ( + !parser.isPure( + statement, + /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + break; + case "ExportNamedDeclaration": + case "ExportDefaultDeclaration": + if ( + !parser.isPure( + /** @type {TODO} */ + (statement.declaration), + /** @type {Range} */ (statement.range)[0] + ) + ) { + sideEffectsStatement = statement; + } + break; + case "LabeledStatement": + case "BlockStatement": + // statement hook will be called for child statements too + break; + case "EmptyStatement": + break; + case "ExportAllDeclaration": + case "ImportDeclaration": + // imports will be handled by the dependencies + break; + default: + sideEffectsStatement = statement; + break; + } + } + ); + parser.hooks.finish.tap(PLUGIN_NAME, () => { + if (sideEffectsStatement === undefined) { + /** @type {BuildMeta} */ + (parser.state.module.buildMeta).sideEffectFree = true; + } else { + const { loc, type } = sideEffectsStatement; + moduleGraph + .getOptimizationBailout(parser.state.module) + .push( + () => + `Statement (${type}) with side effects in source code at ${formatLocation( + /** @type {DependencyLocation} */ (loc) + )}` + ); + } + }); + }; + for (const key of [ + JAVASCRIPT_MODULE_TYPE_AUTO, + JAVASCRIPT_MODULE_TYPE_ESM, + JAVASCRIPT_MODULE_TYPE_DYNAMIC + ]) { + normalModuleFactory.hooks.parser + .for(key) + .tap(PLUGIN_NAME, parserHandler); + } + } + compilation.hooks.optimizeDependencies.tap( + { + name: PLUGIN_NAME, + stage: STAGE_DEFAULT + }, + modules => { + const logger = compilation.getLogger( + "webpack.SideEffectsFlagPlugin" + ); + + logger.time("update dependencies"); + + const optimizedModules = new Set(); + + /** + * @param {Module} module module + */ + const optimizeIncomingConnections = module => { + if (optimizedModules.has(module)) return; + optimizedModules.add(module); + if (module.getSideEffectsConnectionState(moduleGraph) === false) { + const exportsInfo = moduleGraph.getExportsInfo(module); + for (const connection of moduleGraph.getIncomingConnections( + module + )) { + const dep = connection.dependency; + let isReexport; + if ( + (isReexport = + dep instanceof + HarmonyExportImportedSpecifierDependency) || + (dep instanceof HarmonyImportSpecifierDependency && + !dep.namespaceObjectAsContext) + ) { + if (connection.originModule !== null) { + optimizeIncomingConnections(connection.originModule); + } + // TODO improve for export * + if (isReexport && dep.name) { + const exportInfo = moduleGraph.getExportInfo( + /** @type {Module} */ (connection.originModule), + dep.name + ); + exportInfo.moveTarget( + moduleGraph, + ({ module }) => + module.getSideEffectsConnectionState(moduleGraph) === + false, + ({ module: newModule, export: exportName }) => { + moduleGraph.updateModule(dep, newModule); + moduleGraph.addExplanation( + dep, + "(skipped side-effect-free modules)" + ); + const ids = dep.getIds(moduleGraph); + dep.setIds( + moduleGraph, + exportName + ? [...exportName, ...ids.slice(1)] + : ids.slice(1) + ); + return /** @type {ModuleGraphConnection} */ ( + moduleGraph.getConnection(dep) + ); + } + ); + continue; + } + // TODO improve for nested imports + const ids = dep.getIds(moduleGraph); + if (ids.length > 0) { + const exportInfo = exportsInfo.getExportInfo(ids[0]); + const target = exportInfo.getTarget( + moduleGraph, + ({ module }) => + module.getSideEffectsConnectionState(moduleGraph) === + false + ); + if (!target) continue; + + moduleGraph.updateModule(dep, target.module); + moduleGraph.addExplanation( + dep, + "(skipped side-effect-free modules)" + ); + dep.setIds( + moduleGraph, + target.export + ? [...target.export, ...ids.slice(1)] + : ids.slice(1) + ); + } + } + } + } + }; + + for (const module of modules) { + optimizeIncomingConnections(module); + } + logger.timeEnd("update dependencies"); + } + ); + } + ); + } + + /** + * @param {string} moduleName the module name + * @param {undefined | boolean | string | string[]} flagValue the flag value + * @param {Map} cache cache for glob to regexp + * @returns {boolean | undefined} true, when the module has side effects, undefined or false when not + */ + static moduleHasSideEffects(moduleName, flagValue, cache) { + switch (typeof flagValue) { + case "undefined": + return true; + case "boolean": + return flagValue; + case "string": + return globToRegexp(flagValue, cache).test(moduleName); + case "object": + return flagValue.some(glob => + SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob, cache) + ); + } + } +} +module.exports = SideEffectsFlagPlugin; diff --git a/webpack-lib/lib/optimize/SplitChunksPlugin.js b/webpack-lib/lib/optimize/SplitChunksPlugin.js new file mode 100644 index 00000000000..c37d200e5d4 --- /dev/null +++ b/webpack-lib/lib/optimize/SplitChunksPlugin.js @@ -0,0 +1,1767 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Chunk = require("../Chunk"); +const { STAGE_ADVANCED } = require("../OptimizationStages"); +const WebpackError = require("../WebpackError"); +const { requestToId } = require("../ids/IdHelpers"); +const { isSubset } = require("../util/SetHelpers"); +const SortableSet = require("../util/SortableSet"); +const { + compareModulesByIdentifier, + compareIterables +} = require("../util/comparators"); +const createHash = require("../util/createHash"); +const deterministicGrouping = require("../util/deterministicGrouping"); +const { makePathsRelative } = require("../util/identifier"); +const memoize = require("../util/memoize"); +const MinMaxSizeWarning = require("./MinMaxSizeWarning"); + +/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksCacheGroup} OptimizationSplitChunksCacheGroup */ +/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksGetCacheGroups} OptimizationSplitChunksGetCacheGroups */ +/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */ +/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */ +/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** @typedef {import("../util/deterministicGrouping").GroupedItems} DeterministicGroupingGroupedItemsForModule */ +/** @typedef {import("../util/deterministicGrouping").Options} DeterministicGroupingOptionsForModule */ + +/** @typedef {Record} SplitChunksSizes */ + +/** + * @callback ChunkFilterFunction + * @param {Chunk} chunk + * @returns {boolean | undefined} + */ + +/** + * @callback CombineSizeFunction + * @param {number} a + * @param {number} b + * @returns {number} + */ + +/** + * @typedef {object} CacheGroupSource + * @property {string=} key + * @property {number=} priority + * @property {GetName=} getName + * @property {ChunkFilterFunction=} chunksFilter + * @property {boolean=} enforce + * @property {SplitChunksSizes} minSize + * @property {SplitChunksSizes} minSizeReduction + * @property {SplitChunksSizes} minRemainingSize + * @property {SplitChunksSizes} enforceSizeThreshold + * @property {SplitChunksSizes} maxAsyncSize + * @property {SplitChunksSizes} maxInitialSize + * @property {number=} minChunks + * @property {number=} maxAsyncRequests + * @property {number=} maxInitialRequests + * @property {TemplatePath=} filename + * @property {string=} idHint + * @property {string=} automaticNameDelimiter + * @property {boolean=} reuseExistingChunk + * @property {boolean=} usedExports + */ + +/** + * @typedef {object} CacheGroup + * @property {string} key + * @property {number=} priority + * @property {GetName=} getName + * @property {ChunkFilterFunction=} chunksFilter + * @property {SplitChunksSizes} minSize + * @property {SplitChunksSizes} minSizeReduction + * @property {SplitChunksSizes} minRemainingSize + * @property {SplitChunksSizes} enforceSizeThreshold + * @property {SplitChunksSizes} maxAsyncSize + * @property {SplitChunksSizes} maxInitialSize + * @property {number=} minChunks + * @property {number=} maxAsyncRequests + * @property {number=} maxInitialRequests + * @property {TemplatePath=} filename + * @property {string=} idHint + * @property {string} automaticNameDelimiter + * @property {boolean} reuseExistingChunk + * @property {boolean} usedExports + * @property {boolean} _validateSize + * @property {boolean} _validateRemainingSize + * @property {SplitChunksSizes} _minSizeForMaxSize + * @property {boolean} _conditionalEnforce + */ + +/** + * @typedef {object} FallbackCacheGroup + * @property {ChunkFilterFunction} chunksFilter + * @property {SplitChunksSizes} minSize + * @property {SplitChunksSizes} maxAsyncSize + * @property {SplitChunksSizes} maxInitialSize + * @property {string} automaticNameDelimiter + */ + +/** + * @typedef {object} CacheGroupsContext + * @property {ModuleGraph} moduleGraph + * @property {ChunkGraph} chunkGraph + */ + +/** + * @callback GetCacheGroups + * @param {Module} module + * @param {CacheGroupsContext} context + * @returns {CacheGroupSource[]} + */ + +/** + * @callback GetName + * @param {Module=} module + * @param {Chunk[]=} chunks + * @param {string=} key + * @returns {string=} + */ + +/** + * @typedef {object} SplitChunksOptions + * @property {ChunkFilterFunction} chunksFilter + * @property {string[]} defaultSizeTypes + * @property {SplitChunksSizes} minSize + * @property {SplitChunksSizes} minSizeReduction + * @property {SplitChunksSizes} minRemainingSize + * @property {SplitChunksSizes} enforceSizeThreshold + * @property {SplitChunksSizes} maxInitialSize + * @property {SplitChunksSizes} maxAsyncSize + * @property {number} minChunks + * @property {number} maxAsyncRequests + * @property {number} maxInitialRequests + * @property {boolean} hidePathInfo + * @property {TemplatePath} filename + * @property {string} automaticNameDelimiter + * @property {GetCacheGroups} getCacheGroups + * @property {GetName} getName + * @property {boolean} usedExports + * @property {FallbackCacheGroup} fallbackCacheGroup + */ + +/** + * @typedef {object} ChunksInfoItem + * @property {SortableSet} modules + * @property {CacheGroup} cacheGroup + * @property {number} cacheGroupIndex + * @property {string} name + * @property {Record} sizes + * @property {Set} chunks + * @property {Set} reusableChunks + * @property {Set} chunksKeys + */ + +const defaultGetName = /** @type {GetName} */ (() => {}); + +const deterministicGroupingForModules = + /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ + (deterministicGrouping); + +/** @type {WeakMap} */ +const getKeyCache = new WeakMap(); + +/** + * @param {string} name a filename to hash + * @param {OutputOptions} outputOptions hash function used + * @returns {string} hashed filename + */ +const hashFilename = (name, outputOptions) => { + const digest = + /** @type {string} */ + ( + createHash(outputOptions.hashFunction) + .update(name) + .digest(outputOptions.hashDigest) + ); + return digest.slice(0, 8); +}; + +/** + * @param {Chunk} chunk the chunk + * @returns {number} the number of requests + */ +const getRequests = chunk => { + let requests = 0; + for (const chunkGroup of chunk.groupsIterable) { + requests = Math.max(requests, chunkGroup.chunks.length); + } + return requests; +}; + +/** + * @template {object} T + * @template {object} R + * @param {T} obj obj an object + * @param {function(T[keyof T], keyof T): T[keyof T]} fn fn + * @returns {T} result + */ +const mapObject = (obj, fn) => { + const newObj = Object.create(null); + for (const key of Object.keys(obj)) { + newObj[key] = fn( + obj[/** @type {keyof T} */ (key)], + /** @type {keyof T} */ + (key) + ); + } + return newObj; +}; + +/** + * @template T + * @param {Set} a set + * @param {Set} b other set + * @returns {boolean} true if at least one item of a is in b + */ +const isOverlap = (a, b) => { + for (const item of a) { + if (b.has(item)) return true; + } + return false; +}; + +const compareModuleIterables = compareIterables(compareModulesByIdentifier); + +/** + * @param {ChunksInfoItem} a item + * @param {ChunksInfoItem} b item + * @returns {number} compare result + */ +const compareEntries = (a, b) => { + // 1. by priority + const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority; + if (diffPriority) return diffPriority; + // 2. by number of chunks + const diffCount = a.chunks.size - b.chunks.size; + if (diffCount) return diffCount; + // 3. by size reduction + const aSizeReduce = totalSize(a.sizes) * (a.chunks.size - 1); + const bSizeReduce = totalSize(b.sizes) * (b.chunks.size - 1); + const diffSizeReduce = aSizeReduce - bSizeReduce; + if (diffSizeReduce) return diffSizeReduce; + // 4. by cache group index + const indexDiff = b.cacheGroupIndex - a.cacheGroupIndex; + if (indexDiff) return indexDiff; + // 5. by number of modules (to be able to compare by identifier) + const modulesA = a.modules; + const modulesB = b.modules; + const diff = modulesA.size - modulesB.size; + if (diff) return diff; + // 6. by module identifiers + modulesA.sort(); + modulesB.sort(); + return compareModuleIterables(modulesA, modulesB); +}; + +/** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, if the chunk is an entry chunk + */ +const INITIAL_CHUNK_FILTER = chunk => chunk.canBeInitial(); +/** + * @param {Chunk} chunk the chunk + * @returns {boolean} true, if the chunk is an async chunk + */ +const ASYNC_CHUNK_FILTER = chunk => !chunk.canBeInitial(); +/** + * @param {Chunk} chunk the chunk + * @returns {boolean} always true + */ +const ALL_CHUNK_FILTER = chunk => true; + +/** + * @param {OptimizationSplitChunksSizes | undefined} value the sizes + * @param {string[]} defaultSizeTypes the default size types + * @returns {SplitChunksSizes} normalized representation + */ +const normalizeSizes = (value, defaultSizeTypes) => { + if (typeof value === "number") { + /** @type {Record} */ + const o = {}; + for (const sizeType of defaultSizeTypes) o[sizeType] = value; + return o; + } else if (typeof value === "object" && value !== null) { + return { ...value }; + } + return {}; +}; + +/** + * @param {...(SplitChunksSizes | undefined)} sizes the sizes + * @returns {SplitChunksSizes} the merged sizes + */ +const mergeSizes = (...sizes) => { + /** @type {SplitChunksSizes} */ + let merged = {}; + for (let i = sizes.length - 1; i >= 0; i--) { + merged = Object.assign(merged, sizes[i]); + } + return merged; +}; + +/** + * @param {SplitChunksSizes} sizes the sizes + * @returns {boolean} true, if there are sizes > 0 + */ +const hasNonZeroSizes = sizes => { + for (const key of Object.keys(sizes)) { + if (sizes[key] > 0) return true; + } + return false; +}; + +/** + * @param {SplitChunksSizes} a first sizes + * @param {SplitChunksSizes} b second sizes + * @param {CombineSizeFunction} combine a function to combine sizes + * @returns {SplitChunksSizes} the combine sizes + */ +const combineSizes = (a, b, combine) => { + const aKeys = new Set(Object.keys(a)); + const bKeys = new Set(Object.keys(b)); + /** @type {SplitChunksSizes} */ + const result = {}; + for (const key of aKeys) { + result[key] = bKeys.has(key) ? combine(a[key], b[key]) : a[key]; + } + for (const key of bKeys) { + if (!aKeys.has(key)) { + result[key] = b[key]; + } + } + return result; +}; + +/** + * @param {SplitChunksSizes} sizes the sizes + * @param {SplitChunksSizes} minSize the min sizes + * @returns {boolean} true if there are sizes and all existing sizes are at least `minSize` + */ +const checkMinSize = (sizes, minSize) => { + for (const key of Object.keys(minSize)) { + const size = sizes[key]; + if (size === undefined || size === 0) continue; + if (size < minSize[key]) return false; + } + return true; +}; + +/** + * @param {SplitChunksSizes} sizes the sizes + * @param {SplitChunksSizes} minSizeReduction the min sizes + * @param {number} chunkCount number of chunks + * @returns {boolean} true if there are sizes and all existing sizes are at least `minSizeReduction` + */ +const checkMinSizeReduction = (sizes, minSizeReduction, chunkCount) => { + for (const key of Object.keys(minSizeReduction)) { + const size = sizes[key]; + if (size === undefined || size === 0) continue; + if (size * chunkCount < minSizeReduction[key]) return false; + } + return true; +}; + +/** + * @param {SplitChunksSizes} sizes the sizes + * @param {SplitChunksSizes} minSize the min sizes + * @returns {undefined | string[]} list of size types that are below min size + */ +const getViolatingMinSizes = (sizes, minSize) => { + let list; + for (const key of Object.keys(minSize)) { + const size = sizes[key]; + if (size === undefined || size === 0) continue; + if (size < minSize[key]) { + if (list === undefined) list = [key]; + else list.push(key); + } + } + return list; +}; + +/** + * @param {SplitChunksSizes} sizes the sizes + * @returns {number} the total size + */ +const totalSize = sizes => { + let size = 0; + for (const key of Object.keys(sizes)) { + size += sizes[key]; + } + return size; +}; + +/** + * @param {false|string|Function|undefined} name the chunk name + * @returns {GetName | undefined} a function to get the name of the chunk + */ +const normalizeName = name => { + if (typeof name === "string") { + return () => name; + } + if (typeof name === "function") { + return /** @type {GetName} */ (name); + } +}; + +/** + * @param {OptimizationSplitChunksCacheGroup["chunks"]} chunks the chunk filter option + * @returns {ChunkFilterFunction} the chunk filter function + */ +const normalizeChunksFilter = chunks => { + if (chunks === "initial") { + return INITIAL_CHUNK_FILTER; + } + if (chunks === "async") { + return ASYNC_CHUNK_FILTER; + } + if (chunks === "all") { + return ALL_CHUNK_FILTER; + } + if (chunks instanceof RegExp) { + return chunk => (chunk.name ? chunks.test(chunk.name) : false); + } + if (typeof chunks === "function") { + return chunks; + } +}; + +/** + * @param {GetCacheGroups | Record} cacheGroups the cache group options + * @param {string[]} defaultSizeTypes the default size types + * @returns {GetCacheGroups} a function to get the cache groups + */ +const normalizeCacheGroups = (cacheGroups, defaultSizeTypes) => { + if (typeof cacheGroups === "function") { + return cacheGroups; + } + if (typeof cacheGroups === "object" && cacheGroups !== null) { + /** @type {(function(Module, CacheGroupsContext, CacheGroupSource[]): void)[]} */ + const handlers = []; + for (const key of Object.keys(cacheGroups)) { + const option = cacheGroups[key]; + if (option === false) { + continue; + } + if (typeof option === "string" || option instanceof RegExp) { + const source = createCacheGroupSource({}, key, defaultSizeTypes); + handlers.push((module, context, results) => { + if (checkTest(option, module, context)) { + results.push(source); + } + }); + } else if (typeof option === "function") { + const cache = new WeakMap(); + handlers.push((module, context, results) => { + const result = option(module); + if (result) { + const groups = Array.isArray(result) ? result : [result]; + for (const group of groups) { + const cachedSource = cache.get(group); + if (cachedSource !== undefined) { + results.push(cachedSource); + } else { + const source = createCacheGroupSource( + group, + key, + defaultSizeTypes + ); + cache.set(group, source); + results.push(source); + } + } + } + }); + } else { + const source = createCacheGroupSource(option, key, defaultSizeTypes); + handlers.push((module, context, results) => { + if ( + checkTest(option.test, module, context) && + checkModuleType(option.type, module) && + checkModuleLayer(option.layer, module) + ) { + results.push(source); + } + }); + } + } + /** + * @param {Module} module the current module + * @param {CacheGroupsContext} context the current context + * @returns {CacheGroupSource[]} the matching cache groups + */ + const fn = (module, context) => { + /** @type {CacheGroupSource[]} */ + const results = []; + for (const fn of handlers) { + fn(module, context, results); + } + return results; + }; + return fn; + } + return () => null; +}; + +/** + * @param {undefined|boolean|string|RegExp|Function} test test option + * @param {Module} module the module + * @param {CacheGroupsContext} context context object + * @returns {boolean} true, if the module should be selected + */ +const checkTest = (test, module, context) => { + if (test === undefined) return true; + if (typeof test === "function") { + return test(module, context); + } + if (typeof test === "boolean") return test; + if (typeof test === "string") { + const name = module.nameForCondition(); + return name && name.startsWith(test); + } + if (test instanceof RegExp) { + const name = module.nameForCondition(); + return name && test.test(name); + } + return false; +}; + +/** + * @param {undefined|string|RegExp|Function} test type option + * @param {Module} module the module + * @returns {boolean} true, if the module should be selected + */ +const checkModuleType = (test, module) => { + if (test === undefined) return true; + if (typeof test === "function") { + return test(module.type); + } + if (typeof test === "string") { + const type = module.type; + return test === type; + } + if (test instanceof RegExp) { + const type = module.type; + return test.test(type); + } + return false; +}; + +/** + * @param {undefined|string|RegExp|Function} test type option + * @param {Module} module the module + * @returns {boolean} true, if the module should be selected + */ +const checkModuleLayer = (test, module) => { + if (test === undefined) return true; + if (typeof test === "function") { + return test(module.layer); + } + if (typeof test === "string") { + const layer = module.layer; + return test === "" ? !layer : layer && layer.startsWith(test); + } + if (test instanceof RegExp) { + const layer = module.layer; + return test.test(layer); + } + return false; +}; + +/** + * @param {OptimizationSplitChunksCacheGroup} options the group options + * @param {string} key key of cache group + * @param {string[]} defaultSizeTypes the default size types + * @returns {CacheGroupSource} the normalized cached group + */ +const createCacheGroupSource = (options, key, defaultSizeTypes) => { + const minSize = normalizeSizes(options.minSize, defaultSizeTypes); + const minSizeReduction = normalizeSizes( + options.minSizeReduction, + defaultSizeTypes + ); + const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes); + return { + key, + priority: options.priority, + getName: normalizeName(options.name), + chunksFilter: normalizeChunksFilter(options.chunks), + enforce: options.enforce, + minSize, + minSizeReduction, + minRemainingSize: mergeSizes( + normalizeSizes(options.minRemainingSize, defaultSizeTypes), + minSize + ), + enforceSizeThreshold: normalizeSizes( + options.enforceSizeThreshold, + defaultSizeTypes + ), + maxAsyncSize: mergeSizes( + normalizeSizes(options.maxAsyncSize, defaultSizeTypes), + maxSize + ), + maxInitialSize: mergeSizes( + normalizeSizes(options.maxInitialSize, defaultSizeTypes), + maxSize + ), + minChunks: options.minChunks, + maxAsyncRequests: options.maxAsyncRequests, + maxInitialRequests: options.maxInitialRequests, + filename: options.filename, + idHint: options.idHint, + automaticNameDelimiter: options.automaticNameDelimiter, + reuseExistingChunk: options.reuseExistingChunk, + usedExports: options.usedExports + }; +}; + +module.exports = class SplitChunksPlugin { + /** + * @param {OptimizationSplitChunksOptions=} options plugin options + */ + constructor(options = {}) { + const defaultSizeTypes = options.defaultSizeTypes || [ + "javascript", + "unknown" + ]; + const fallbackCacheGroup = options.fallbackCacheGroup || {}; + const minSize = normalizeSizes(options.minSize, defaultSizeTypes); + const minSizeReduction = normalizeSizes( + options.minSizeReduction, + defaultSizeTypes + ); + const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes); + + /** @type {SplitChunksOptions} */ + this.options = { + chunksFilter: normalizeChunksFilter(options.chunks || "all"), + defaultSizeTypes, + minSize, + minSizeReduction, + minRemainingSize: mergeSizes( + normalizeSizes(options.minRemainingSize, defaultSizeTypes), + minSize + ), + enforceSizeThreshold: normalizeSizes( + options.enforceSizeThreshold, + defaultSizeTypes + ), + maxAsyncSize: mergeSizes( + normalizeSizes(options.maxAsyncSize, defaultSizeTypes), + maxSize + ), + maxInitialSize: mergeSizes( + normalizeSizes(options.maxInitialSize, defaultSizeTypes), + maxSize + ), + minChunks: options.minChunks || 1, + maxAsyncRequests: options.maxAsyncRequests || 1, + maxInitialRequests: options.maxInitialRequests || 1, + hidePathInfo: options.hidePathInfo || false, + filename: options.filename || undefined, + getCacheGroups: normalizeCacheGroups( + options.cacheGroups, + defaultSizeTypes + ), + getName: options.name ? normalizeName(options.name) : defaultGetName, + automaticNameDelimiter: options.automaticNameDelimiter, + usedExports: options.usedExports, + fallbackCacheGroup: { + chunksFilter: normalizeChunksFilter( + fallbackCacheGroup.chunks || options.chunks || "all" + ), + minSize: mergeSizes( + normalizeSizes(fallbackCacheGroup.minSize, defaultSizeTypes), + minSize + ), + maxAsyncSize: mergeSizes( + normalizeSizes(fallbackCacheGroup.maxAsyncSize, defaultSizeTypes), + normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes), + normalizeSizes(options.maxAsyncSize, defaultSizeTypes), + normalizeSizes(options.maxSize, defaultSizeTypes) + ), + maxInitialSize: mergeSizes( + normalizeSizes(fallbackCacheGroup.maxInitialSize, defaultSizeTypes), + normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes), + normalizeSizes(options.maxInitialSize, defaultSizeTypes), + normalizeSizes(options.maxSize, defaultSizeTypes) + ), + automaticNameDelimiter: + fallbackCacheGroup.automaticNameDelimiter || + options.automaticNameDelimiter || + "~" + } + }; + + /** @type {WeakMap} */ + this._cacheGroupCache = new WeakMap(); + } + + /** + * @param {CacheGroupSource} cacheGroupSource source + * @returns {CacheGroup} the cache group (cached) + */ + _getCacheGroup(cacheGroupSource) { + const cacheEntry = this._cacheGroupCache.get(cacheGroupSource); + if (cacheEntry !== undefined) return cacheEntry; + const minSize = mergeSizes( + cacheGroupSource.minSize, + cacheGroupSource.enforce ? undefined : this.options.minSize + ); + const minSizeReduction = mergeSizes( + cacheGroupSource.minSizeReduction, + cacheGroupSource.enforce ? undefined : this.options.minSizeReduction + ); + const minRemainingSize = mergeSizes( + cacheGroupSource.minRemainingSize, + cacheGroupSource.enforce ? undefined : this.options.minRemainingSize + ); + const enforceSizeThreshold = mergeSizes( + cacheGroupSource.enforceSizeThreshold, + cacheGroupSource.enforce ? undefined : this.options.enforceSizeThreshold + ); + const cacheGroup = { + key: cacheGroupSource.key, + priority: cacheGroupSource.priority || 0, + chunksFilter: cacheGroupSource.chunksFilter || this.options.chunksFilter, + minSize, + minSizeReduction, + minRemainingSize, + enforceSizeThreshold, + maxAsyncSize: mergeSizes( + cacheGroupSource.maxAsyncSize, + cacheGroupSource.enforce ? undefined : this.options.maxAsyncSize + ), + maxInitialSize: mergeSizes( + cacheGroupSource.maxInitialSize, + cacheGroupSource.enforce ? undefined : this.options.maxInitialSize + ), + minChunks: + cacheGroupSource.minChunks !== undefined + ? cacheGroupSource.minChunks + : cacheGroupSource.enforce + ? 1 + : this.options.minChunks, + maxAsyncRequests: + cacheGroupSource.maxAsyncRequests !== undefined + ? cacheGroupSource.maxAsyncRequests + : cacheGroupSource.enforce + ? Infinity + : this.options.maxAsyncRequests, + maxInitialRequests: + cacheGroupSource.maxInitialRequests !== undefined + ? cacheGroupSource.maxInitialRequests + : cacheGroupSource.enforce + ? Infinity + : this.options.maxInitialRequests, + getName: + cacheGroupSource.getName !== undefined + ? cacheGroupSource.getName + : this.options.getName, + usedExports: + cacheGroupSource.usedExports !== undefined + ? cacheGroupSource.usedExports + : this.options.usedExports, + filename: + cacheGroupSource.filename !== undefined + ? cacheGroupSource.filename + : this.options.filename, + automaticNameDelimiter: + cacheGroupSource.automaticNameDelimiter !== undefined + ? cacheGroupSource.automaticNameDelimiter + : this.options.automaticNameDelimiter, + idHint: + cacheGroupSource.idHint !== undefined + ? cacheGroupSource.idHint + : cacheGroupSource.key, + reuseExistingChunk: cacheGroupSource.reuseExistingChunk || false, + _validateSize: hasNonZeroSizes(minSize), + _validateRemainingSize: hasNonZeroSizes(minRemainingSize), + _minSizeForMaxSize: mergeSizes( + cacheGroupSource.minSize, + this.options.minSize + ), + _conditionalEnforce: hasNonZeroSizes(enforceSizeThreshold) + }; + this._cacheGroupCache.set(cacheGroupSource, cacheGroup); + return cacheGroup; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const cachedMakePathsRelative = makePathsRelative.bindContextCache( + compiler.context, + compiler.root + ); + compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => { + const logger = compilation.getLogger("webpack.SplitChunksPlugin"); + let alreadyOptimized = false; + compilation.hooks.unseal.tap("SplitChunksPlugin", () => { + alreadyOptimized = false; + }); + compilation.hooks.optimizeChunks.tap( + { + name: "SplitChunksPlugin", + stage: STAGE_ADVANCED + }, + chunks => { + if (alreadyOptimized) return; + alreadyOptimized = true; + logger.time("prepare"); + const chunkGraph = compilation.chunkGraph; + const moduleGraph = compilation.moduleGraph; + // Give each selected chunk an index (to create strings from chunks) + /** @type {Map} */ + const chunkIndexMap = new Map(); + const ZERO = BigInt("0"); + const ONE = BigInt("1"); + const START = ONE << BigInt("31"); + let index = START; + for (const chunk of chunks) { + chunkIndexMap.set( + chunk, + index | BigInt((Math.random() * 0x7fffffff) | 0) + ); + index = index << ONE; + } + /** + * @param {Iterable} chunks list of chunks + * @returns {bigint | Chunk} key of the chunks + */ + const getKey = chunks => { + const iterator = chunks[Symbol.iterator](); + let result = iterator.next(); + if (result.done) return ZERO; + const first = result.value; + result = iterator.next(); + if (result.done) return first; + let key = + chunkIndexMap.get(first) | chunkIndexMap.get(result.value); + while (!(result = iterator.next()).done) { + const raw = chunkIndexMap.get(result.value); + key = key ^ raw; + } + return key; + }; + /** + * @param {bigint | Chunk} key key of the chunks + * @returns {string} stringified key + */ + const keyToString = key => { + if (typeof key === "bigint") return key.toString(16); + return chunkIndexMap.get(key).toString(16); + }; + + const getChunkSetsInGraph = memoize(() => { + /** @type {Map>} */ + const chunkSetsInGraph = new Map(); + /** @type {Set} */ + const singleChunkSets = new Set(); + for (const module of compilation.modules) { + const chunks = chunkGraph.getModuleChunksIterable(module); + const chunksKey = getKey(chunks); + if (typeof chunksKey === "bigint") { + if (!chunkSetsInGraph.has(chunksKey)) { + chunkSetsInGraph.set(chunksKey, new Set(chunks)); + } + } else { + singleChunkSets.add(chunksKey); + } + } + return { chunkSetsInGraph, singleChunkSets }; + }); + + /** + * @param {Module} module the module + * @returns {Iterable} groups of chunks with equal exports + */ + const groupChunksByExports = module => { + const exportsInfo = moduleGraph.getExportsInfo(module); + const groupedByUsedExports = new Map(); + for (const chunk of chunkGraph.getModuleChunksIterable(module)) { + const key = exportsInfo.getUsageKey(chunk.runtime); + const list = groupedByUsedExports.get(key); + if (list !== undefined) { + list.push(chunk); + } else { + groupedByUsedExports.set(key, [chunk]); + } + } + return groupedByUsedExports.values(); + }; + + /** @type {Map>} */ + const groupedByExportsMap = new Map(); + + const getExportsChunkSetsInGraph = memoize(() => { + /** @type {Map>} */ + const chunkSetsInGraph = new Map(); + /** @type {Set} */ + const singleChunkSets = new Set(); + for (const module of compilation.modules) { + const groupedChunks = Array.from(groupChunksByExports(module)); + groupedByExportsMap.set(module, groupedChunks); + for (const chunks of groupedChunks) { + if (chunks.length === 1) { + singleChunkSets.add(chunks[0]); + } else { + const chunksKey = /** @type {bigint} */ (getKey(chunks)); + if (!chunkSetsInGraph.has(chunksKey)) { + chunkSetsInGraph.set(chunksKey, new Set(chunks)); + } + } + } + } + return { chunkSetsInGraph, singleChunkSets }; + }); + + // group these set of chunks by count + // to allow to check less sets via isSubset + // (only smaller sets can be subset) + /** + * @param {IterableIterator>} chunkSets set of sets of chunks + * @returns {Map>>} map of sets of chunks by count + */ + const groupChunkSetsByCount = chunkSets => { + /** @type {Map>>} */ + const chunkSetsByCount = new Map(); + for (const chunksSet of chunkSets) { + const count = chunksSet.size; + let array = chunkSetsByCount.get(count); + if (array === undefined) { + array = []; + chunkSetsByCount.set(count, array); + } + array.push(chunksSet); + } + return chunkSetsByCount; + }; + const getChunkSetsByCount = memoize(() => + groupChunkSetsByCount( + getChunkSetsInGraph().chunkSetsInGraph.values() + ) + ); + const getExportsChunkSetsByCount = memoize(() => + groupChunkSetsByCount( + getExportsChunkSetsInGraph().chunkSetsInGraph.values() + ) + ); + + // Create a list of possible combinations + const createGetCombinations = ( + chunkSets, + singleChunkSets, + chunkSetsByCount + ) => { + /** @type {Map | Chunk)[]>} */ + const combinationsCache = new Map(); + + return key => { + const cacheEntry = combinationsCache.get(key); + if (cacheEntry !== undefined) return cacheEntry; + if (key instanceof Chunk) { + const result = [key]; + combinationsCache.set(key, result); + return result; + } + const chunksSet = chunkSets.get(key); + /** @type {(Set | Chunk)[]} */ + const array = [chunksSet]; + for (const [count, setArray] of chunkSetsByCount) { + // "equal" is not needed because they would have been merge in the first step + if (count < chunksSet.size) { + for (const set of setArray) { + if (isSubset(chunksSet, set)) { + array.push(set); + } + } + } + } + for (const chunk of singleChunkSets) { + if (chunksSet.has(chunk)) { + array.push(chunk); + } + } + combinationsCache.set(key, array); + return array; + }; + }; + + const getCombinationsFactory = memoize(() => { + const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph(); + return createGetCombinations( + chunkSetsInGraph, + singleChunkSets, + getChunkSetsByCount() + ); + }); + const getCombinations = key => getCombinationsFactory()(key); + + const getExportsCombinationsFactory = memoize(() => { + const { chunkSetsInGraph, singleChunkSets } = + getExportsChunkSetsInGraph(); + return createGetCombinations( + chunkSetsInGraph, + singleChunkSets, + getExportsChunkSetsByCount() + ); + }); + const getExportsCombinations = key => + getExportsCombinationsFactory()(key); + + /** + * @typedef {object} SelectedChunksResult + * @property {Chunk[]} chunks the list of chunks + * @property {bigint | Chunk} key a key of the list + */ + + /** @type {WeakMap | Chunk, WeakMap>} */ + const selectedChunksCacheByChunksSet = new WeakMap(); + + /** + * get list and key by applying the filter function to the list + * It is cached for performance reasons + * @param {Set | Chunk} chunks list of chunks + * @param {ChunkFilterFunction} chunkFilter filter function for chunks + * @returns {SelectedChunksResult} list and key + */ + const getSelectedChunks = (chunks, chunkFilter) => { + let entry = selectedChunksCacheByChunksSet.get(chunks); + if (entry === undefined) { + entry = new WeakMap(); + selectedChunksCacheByChunksSet.set(chunks, entry); + } + let entry2 = + /** @type {SelectedChunksResult} */ + (entry.get(chunkFilter)); + if (entry2 === undefined) { + /** @type {Chunk[]} */ + const selectedChunks = []; + if (chunks instanceof Chunk) { + if (chunkFilter(chunks)) selectedChunks.push(chunks); + } else { + for (const chunk of chunks) { + if (chunkFilter(chunk)) selectedChunks.push(chunk); + } + } + entry2 = { + chunks: selectedChunks, + key: getKey(selectedChunks) + }; + entry.set(chunkFilter, entry2); + } + return entry2; + }; + + /** @type {Map} */ + const alreadyValidatedParents = new Map(); + /** @type {Set} */ + const alreadyReportedErrors = new Set(); + + // Map a list of chunks to a list of modules + // For the key the chunk "index" is used, the value is a SortableSet of modules + /** @type {Map} */ + const chunksInfoMap = new Map(); + + /** + * @param {CacheGroup} cacheGroup the current cache group + * @param {number} cacheGroupIndex the index of the cache group of ordering + * @param {Chunk[]} selectedChunks chunks selected for this module + * @param {bigint | Chunk} selectedChunksKey a key of selectedChunks + * @param {Module} module the current module + * @returns {void} + */ + const addModuleToChunksInfoMap = ( + cacheGroup, + cacheGroupIndex, + selectedChunks, + selectedChunksKey, + module + ) => { + // Break if minimum number of chunks is not reached + if (selectedChunks.length < cacheGroup.minChunks) return; + // Determine name for split chunk + const name = + /** @type {string} */ + (cacheGroup.getName(module, selectedChunks, cacheGroup.key)); + // Check if the name is ok + const existingChunk = compilation.namedChunks.get(name); + if (existingChunk) { + const parentValidationKey = `${name}|${ + typeof selectedChunksKey === "bigint" + ? selectedChunksKey + : selectedChunksKey.debugId + }`; + const valid = alreadyValidatedParents.get(parentValidationKey); + if (valid === false) return; + if (valid === undefined) { + // Module can only be moved into the existing chunk if the existing chunk + // is a parent of all selected chunks + let isInAllParents = true; + /** @type {Set} */ + const queue = new Set(); + for (const chunk of selectedChunks) { + for (const group of chunk.groupsIterable) { + queue.add(group); + } + } + for (const group of queue) { + if (existingChunk.isInGroup(group)) continue; + let hasParent = false; + for (const parent of group.parentsIterable) { + hasParent = true; + queue.add(parent); + } + if (!hasParent) { + isInAllParents = false; + } + } + const valid = isInAllParents; + alreadyValidatedParents.set(parentValidationKey, valid); + if (!valid) { + if (!alreadyReportedErrors.has(name)) { + alreadyReportedErrors.add(name); + compilation.errors.push( + new WebpackError( + "SplitChunksPlugin\n" + + `Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` + + `Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` + + "Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependOn).\n" + + 'HINT: You can omit "name" to automatically create a name.\n' + + "BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " + + "This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" + + "Remove this entrypoint and add modules to cache group's 'test' instead. " + + "If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " + + "See migration guide of more info." + ) + ); + } + return; + } + } + } + // Create key for maps + // When it has a name we use the name as key + // Otherwise we create the key from chunks and cache group key + // This automatically merges equal names + const key = + cacheGroup.key + + (name + ? ` name:${name}` + : ` chunks:${keyToString(selectedChunksKey)}`); + // Add module to maps + let info = /** @type {ChunksInfoItem} */ (chunksInfoMap.get(key)); + if (info === undefined) { + chunksInfoMap.set( + key, + (info = { + modules: new SortableSet( + undefined, + compareModulesByIdentifier + ), + cacheGroup, + cacheGroupIndex, + name, + sizes: {}, + chunks: new Set(), + reusableChunks: new Set(), + chunksKeys: new Set() + }) + ); + } + const oldSize = info.modules.size; + info.modules.add(module); + if (info.modules.size !== oldSize) { + for (const type of module.getSourceTypes()) { + info.sizes[type] = (info.sizes[type] || 0) + module.size(type); + } + } + const oldChunksKeysSize = info.chunksKeys.size; + info.chunksKeys.add(selectedChunksKey); + if (oldChunksKeysSize !== info.chunksKeys.size) { + for (const chunk of selectedChunks) { + info.chunks.add(chunk); + } + } + }; + + const context = { + moduleGraph, + chunkGraph + }; + + logger.timeEnd("prepare"); + + logger.time("modules"); + + // Walk through all modules + for (const module of compilation.modules) { + // Get cache group + const cacheGroups = this.options.getCacheGroups(module, context); + if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) { + continue; + } + + // Prepare some values (usedExports = false) + const getCombs = memoize(() => { + const chunks = chunkGraph.getModuleChunksIterable(module); + const chunksKey = getKey(chunks); + return getCombinations(chunksKey); + }); + + // Prepare some values (usedExports = true) + const getCombsByUsedExports = memoize(() => { + // fill the groupedByExportsMap + getExportsChunkSetsInGraph(); + /** @type {Set | Chunk>} */ + const set = new Set(); + const groupedByUsedExports = + /** @type {Iterable} */ + (groupedByExportsMap.get(module)); + for (const chunks of groupedByUsedExports) { + const chunksKey = getKey(chunks); + for (const comb of getExportsCombinations(chunksKey)) + set.add(comb); + } + return set; + }); + + let cacheGroupIndex = 0; + for (const cacheGroupSource of cacheGroups) { + const cacheGroup = this._getCacheGroup(cacheGroupSource); + + const combs = cacheGroup.usedExports + ? getCombsByUsedExports() + : getCombs(); + // For all combination of chunk selection + for (const chunkCombination of combs) { + // Break if minimum number of chunks is not reached + const count = + chunkCombination instanceof Chunk ? 1 : chunkCombination.size; + if (count < cacheGroup.minChunks) continue; + // Select chunks by configuration + const { chunks: selectedChunks, key: selectedChunksKey } = + getSelectedChunks( + chunkCombination, + /** @type {ChunkFilterFunction} */ (cacheGroup.chunksFilter) + ); + + addModuleToChunksInfoMap( + cacheGroup, + cacheGroupIndex, + selectedChunks, + selectedChunksKey, + module + ); + } + cacheGroupIndex++; + } + } + + logger.timeEnd("modules"); + + logger.time("queue"); + + /** + * @param {ChunksInfoItem} info entry + * @param {string[]} sourceTypes source types to be removed + */ + const removeModulesWithSourceType = (info, sourceTypes) => { + for (const module of info.modules) { + const types = module.getSourceTypes(); + if (sourceTypes.some(type => types.has(type))) { + info.modules.delete(module); + for (const type of types) { + info.sizes[type] -= module.size(type); + } + } + } + }; + + /** + * @param {ChunksInfoItem} info entry + * @returns {boolean} true, if entry become empty + */ + const removeMinSizeViolatingModules = info => { + if (!info.cacheGroup._validateSize) return false; + const violatingSizes = getViolatingMinSizes( + info.sizes, + info.cacheGroup.minSize + ); + if (violatingSizes === undefined) return false; + removeModulesWithSourceType(info, violatingSizes); + return info.modules.size === 0; + }; + + // Filter items were size < minSize + for (const [key, info] of chunksInfoMap) { + if (removeMinSizeViolatingModules(info)) { + chunksInfoMap.delete(key); + } else if ( + !checkMinSizeReduction( + info.sizes, + info.cacheGroup.minSizeReduction, + info.chunks.size + ) + ) { + chunksInfoMap.delete(key); + } + } + + /** + * @typedef {object} MaxSizeQueueItem + * @property {SplitChunksSizes} minSize + * @property {SplitChunksSizes} maxAsyncSize + * @property {SplitChunksSizes} maxInitialSize + * @property {string} automaticNameDelimiter + * @property {string[]} keys + */ + + /** @type {Map} */ + const maxSizeQueueMap = new Map(); + + while (chunksInfoMap.size > 0) { + // Find best matching entry + let bestEntryKey; + let bestEntry; + for (const pair of chunksInfoMap) { + const key = pair[0]; + const info = pair[1]; + if ( + bestEntry === undefined || + compareEntries(bestEntry, info) < 0 + ) { + bestEntry = info; + bestEntryKey = key; + } + } + + const item = /** @type {ChunksInfoItem} */ (bestEntry); + chunksInfoMap.delete(/** @type {string} */ (bestEntryKey)); + + /** @type {Chunk["name"] | undefined} */ + let chunkName = item.name; + // Variable for the new chunk (lazy created) + /** @type {Chunk | undefined} */ + let newChunk; + // When no chunk name, check if we can reuse a chunk instead of creating a new one + let isExistingChunk = false; + let isReusedWithAllModules = false; + if (chunkName) { + const chunkByName = compilation.namedChunks.get(chunkName); + if (chunkByName !== undefined) { + newChunk = chunkByName; + const oldSize = item.chunks.size; + item.chunks.delete(newChunk); + isExistingChunk = item.chunks.size !== oldSize; + } + } else if (item.cacheGroup.reuseExistingChunk) { + outer: for (const chunk of item.chunks) { + if ( + chunkGraph.getNumberOfChunkModules(chunk) !== + item.modules.size + ) { + continue; + } + if ( + item.chunks.size > 1 && + chunkGraph.getNumberOfEntryModules(chunk) > 0 + ) { + continue; + } + for (const module of item.modules) { + if (!chunkGraph.isModuleInChunk(module, chunk)) { + continue outer; + } + } + if (!newChunk || !newChunk.name) { + newChunk = chunk; + } else if ( + chunk.name && + chunk.name.length < newChunk.name.length + ) { + newChunk = chunk; + } else if ( + chunk.name && + chunk.name.length === newChunk.name.length && + chunk.name < newChunk.name + ) { + newChunk = chunk; + } + } + if (newChunk) { + item.chunks.delete(newChunk); + chunkName = undefined; + isExistingChunk = true; + isReusedWithAllModules = true; + } + } + + const enforced = + item.cacheGroup._conditionalEnforce && + checkMinSize(item.sizes, item.cacheGroup.enforceSizeThreshold); + + const usedChunks = new Set(item.chunks); + + // Check if maxRequests condition can be fulfilled + if ( + !enforced && + (Number.isFinite(item.cacheGroup.maxInitialRequests) || + Number.isFinite(item.cacheGroup.maxAsyncRequests)) + ) { + for (const chunk of usedChunks) { + // respect max requests + const maxRequests = /** @type {number} */ ( + chunk.isOnlyInitial() + ? item.cacheGroup.maxInitialRequests + : chunk.canBeInitial() + ? Math.min( + /** @type {number} */ + (item.cacheGroup.maxInitialRequests), + /** @type {number} */ + (item.cacheGroup.maxAsyncRequests) + ) + : item.cacheGroup.maxAsyncRequests + ); + if ( + Number.isFinite(maxRequests) && + getRequests(chunk) >= maxRequests + ) { + usedChunks.delete(chunk); + } + } + } + + outer: for (const chunk of usedChunks) { + for (const module of item.modules) { + if (chunkGraph.isModuleInChunk(module, chunk)) continue outer; + } + usedChunks.delete(chunk); + } + + // Were some (invalid) chunks removed from usedChunks? + // => readd all modules to the queue, as things could have been changed + if (usedChunks.size < item.chunks.size) { + if (isExistingChunk) + usedChunks.add(/** @type {Chunk} */ (newChunk)); + if ( + /** @type {number} */ (usedChunks.size) >= + /** @type {number} */ (item.cacheGroup.minChunks) + ) { + const chunksArr = Array.from(usedChunks); + for (const module of item.modules) { + addModuleToChunksInfoMap( + item.cacheGroup, + item.cacheGroupIndex, + chunksArr, + getKey(usedChunks), + module + ); + } + } + continue; + } + + // Validate minRemainingSize constraint when a single chunk is left over + if ( + !enforced && + item.cacheGroup._validateRemainingSize && + usedChunks.size === 1 + ) { + const [chunk] = usedChunks; + const chunkSizes = Object.create(null); + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { + if (!item.modules.has(module)) { + for (const type of module.getSourceTypes()) { + chunkSizes[type] = + (chunkSizes[type] || 0) + module.size(type); + } + } + } + const violatingSizes = getViolatingMinSizes( + chunkSizes, + item.cacheGroup.minRemainingSize + ); + if (violatingSizes !== undefined) { + const oldModulesSize = item.modules.size; + removeModulesWithSourceType(item, violatingSizes); + if ( + item.modules.size > 0 && + item.modules.size !== oldModulesSize + ) { + // queue this item again to be processed again + // without violating modules + chunksInfoMap.set(/** @type {string} */ (bestEntryKey), item); + } + continue; + } + } + + // Create the new chunk if not reusing one + if (newChunk === undefined) { + newChunk = compilation.addChunk(chunkName); + } + // Walk through all chunks + for (const chunk of usedChunks) { + // Add graph connections for splitted chunk + chunk.split(newChunk); + } + + // Add a note to the chunk + newChunk.chunkReason = + (newChunk.chunkReason ? `${newChunk.chunkReason}, ` : "") + + (isReusedWithAllModules + ? "reused as split chunk" + : "split chunk"); + if (item.cacheGroup.key) { + newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`; + } + if (chunkName) { + newChunk.chunkReason += ` (name: ${chunkName})`; + } + if (item.cacheGroup.filename) { + newChunk.filenameTemplate = item.cacheGroup.filename; + } + if (item.cacheGroup.idHint) { + newChunk.idNameHints.add(item.cacheGroup.idHint); + } + if (!isReusedWithAllModules) { + // Add all modules to the new chunk + for (const module of item.modules) { + if (!module.chunkCondition(newChunk, compilation)) continue; + // Add module to new chunk + chunkGraph.connectChunkAndModule(newChunk, module); + // Remove module from used chunks + for (const chunk of usedChunks) { + chunkGraph.disconnectChunkAndModule(chunk, module); + } + } + } else { + // Remove all modules from used chunks + for (const module of item.modules) { + for (const chunk of usedChunks) { + chunkGraph.disconnectChunkAndModule(chunk, module); + } + } + } + + if ( + Object.keys(item.cacheGroup.maxAsyncSize).length > 0 || + Object.keys(item.cacheGroup.maxInitialSize).length > 0 + ) { + const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk); + maxSizeQueueMap.set(newChunk, { + minSize: oldMaxSizeSettings + ? combineSizes( + oldMaxSizeSettings.minSize, + item.cacheGroup._minSizeForMaxSize, + Math.max + ) + : item.cacheGroup.minSize, + maxAsyncSize: oldMaxSizeSettings + ? combineSizes( + oldMaxSizeSettings.maxAsyncSize, + item.cacheGroup.maxAsyncSize, + Math.min + ) + : item.cacheGroup.maxAsyncSize, + maxInitialSize: oldMaxSizeSettings + ? combineSizes( + oldMaxSizeSettings.maxInitialSize, + item.cacheGroup.maxInitialSize, + Math.min + ) + : item.cacheGroup.maxInitialSize, + automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter, + keys: oldMaxSizeSettings + ? oldMaxSizeSettings.keys.concat(item.cacheGroup.key) + : [item.cacheGroup.key] + }); + } + + // remove all modules from other entries and update size + for (const [key, info] of chunksInfoMap) { + if (isOverlap(info.chunks, usedChunks)) { + // update modules and total size + // may remove it from the map when < minSize + let updated = false; + for (const module of item.modules) { + if (info.modules.has(module)) { + // remove module + info.modules.delete(module); + // update size + for (const key of module.getSourceTypes()) { + info.sizes[key] -= module.size(key); + } + updated = true; + } + } + if (updated) { + if (info.modules.size === 0) { + chunksInfoMap.delete(key); + continue; + } + if ( + removeMinSizeViolatingModules(info) || + !checkMinSizeReduction( + info.sizes, + info.cacheGroup.minSizeReduction, + info.chunks.size + ) + ) { + chunksInfoMap.delete(key); + continue; + } + } + } + } + } + + logger.timeEnd("queue"); + + logger.time("maxSize"); + + /** @type {Set} */ + const incorrectMinMaxSizeSet = new Set(); + + const { outputOptions } = compilation; + + // Make sure that maxSize is fulfilled + const { fallbackCacheGroup } = this.options; + for (const chunk of Array.from(compilation.chunks)) { + const chunkConfig = maxSizeQueueMap.get(chunk); + const { + minSize, + maxAsyncSize, + maxInitialSize, + automaticNameDelimiter + } = chunkConfig || fallbackCacheGroup; + if (!chunkConfig && !fallbackCacheGroup.chunksFilter(chunk)) + continue; + /** @type {SplitChunksSizes} */ + let maxSize; + if (chunk.isOnlyInitial()) { + maxSize = maxInitialSize; + } else if (chunk.canBeInitial()) { + maxSize = combineSizes(maxAsyncSize, maxInitialSize, Math.min); + } else { + maxSize = maxAsyncSize; + } + if (Object.keys(maxSize).length === 0) { + continue; + } + for (const key of Object.keys(maxSize)) { + const maxSizeValue = maxSize[key]; + const minSizeValue = minSize[key]; + if ( + typeof minSizeValue === "number" && + minSizeValue > maxSizeValue + ) { + const keys = chunkConfig && chunkConfig.keys; + const warningKey = `${ + keys && keys.join() + } ${minSizeValue} ${maxSizeValue}`; + if (!incorrectMinMaxSizeSet.has(warningKey)) { + incorrectMinMaxSizeSet.add(warningKey); + compilation.warnings.push( + new MinMaxSizeWarning(keys, minSizeValue, maxSizeValue) + ); + } + } + } + const results = deterministicGroupingForModules({ + minSize, + maxSize: mapObject(maxSize, (value, key) => { + const minSizeValue = minSize[key]; + return typeof minSizeValue === "number" + ? Math.max(value, minSizeValue) + : value; + }), + items: chunkGraph.getChunkModulesIterable(chunk), + getKey(module) { + const cache = getKeyCache.get(module); + if (cache !== undefined) return cache; + const ident = cachedMakePathsRelative(module.identifier()); + const nameForCondition = + module.nameForCondition && module.nameForCondition(); + const name = nameForCondition + ? cachedMakePathsRelative(nameForCondition) + : ident.replace(/^.*!|\?[^?!]*$/g, ""); + const fullKey = + name + + automaticNameDelimiter + + hashFilename(ident, outputOptions); + const key = requestToId(fullKey); + getKeyCache.set(module, key); + return key; + }, + getSize(module) { + const size = Object.create(null); + for (const key of module.getSourceTypes()) { + size[key] = module.size(key); + } + return size; + } + }); + if (results.length <= 1) { + continue; + } + for (let i = 0; i < results.length; i++) { + const group = results[i]; + const key = this.options.hidePathInfo + ? hashFilename(group.key, outputOptions) + : group.key; + let name = chunk.name + ? chunk.name + automaticNameDelimiter + key + : null; + if (name && name.length > 100) { + name = + name.slice(0, 100) + + automaticNameDelimiter + + hashFilename(name, outputOptions); + } + if (i !== results.length - 1) { + const newPart = compilation.addChunk( + /** @type {Chunk["name"]} */ (name) + ); + chunk.split(newPart); + newPart.chunkReason = chunk.chunkReason; + // Add all modules to the new chunk + for (const module of group.items) { + if (!module.chunkCondition(newPart, compilation)) { + continue; + } + // Add module to new chunk + chunkGraph.connectChunkAndModule(newPart, module); + // Remove module from used chunks + chunkGraph.disconnectChunkAndModule(chunk, module); + } + } else { + // change the chunk to be a part + chunk.name = /** @type {Chunk["name"]} */ (name); + } + } + } + logger.timeEnd("maxSize"); + } + ); + }); + } +}; diff --git a/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js b/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js new file mode 100644 index 00000000000..5b414fc0dfd --- /dev/null +++ b/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js @@ -0,0 +1,32 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const { formatSize } = require("../SizeFormatHelpers"); +const WebpackError = require("../WebpackError"); + +/** @typedef {import("./SizeLimitsPlugin").AssetDetails} AssetDetails */ + +module.exports = class AssetsOverSizeLimitWarning extends WebpackError { + /** + * @param {AssetDetails[]} assetsOverSizeLimit the assets + * @param {number} assetLimit the size limit + */ + constructor(assetsOverSizeLimit, assetLimit) { + const assetLists = assetsOverSizeLimit + .map(asset => `\n ${asset.name} (${formatSize(asset.size)})`) + .join(""); + + super(`asset size limit: The following asset(s) exceed the recommended size limit (${formatSize( + assetLimit + )}). +This can impact web performance. +Assets: ${assetLists}`); + + this.name = "AssetsOverSizeLimitWarning"; + this.assets = assetsOverSizeLimit; + } +}; diff --git a/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js b/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js new file mode 100644 index 00000000000..270e8aaa708 --- /dev/null +++ b/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const { formatSize } = require("../SizeFormatHelpers"); +const WebpackError = require("../WebpackError"); + +/** @typedef {import("./SizeLimitsPlugin").EntrypointDetails} EntrypointDetails */ + +module.exports = class EntrypointsOverSizeLimitWarning extends WebpackError { + /** + * @param {EntrypointDetails[]} entrypoints the entrypoints + * @param {number} entrypointLimit the size limit + */ + constructor(entrypoints, entrypointLimit) { + const entrypointList = entrypoints + .map( + entrypoint => + `\n ${entrypoint.name} (${formatSize( + entrypoint.size + )})\n${entrypoint.files.map(asset => ` ${asset}`).join("\n")}` + ) + .join(""); + super(`entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (${formatSize( + entrypointLimit + )}). This can impact web performance. +Entrypoints:${entrypointList}\n`); + + this.name = "EntrypointsOverSizeLimitWarning"; + this.entrypoints = entrypoints; + } +}; diff --git a/webpack-lib/lib/performance/NoAsyncChunksWarning.js b/webpack-lib/lib/performance/NoAsyncChunksWarning.js new file mode 100644 index 00000000000..a7319d5950b --- /dev/null +++ b/webpack-lib/lib/performance/NoAsyncChunksWarning.js @@ -0,0 +1,20 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); + +module.exports = class NoAsyncChunksWarning extends WebpackError { + constructor() { + super( + "webpack performance recommendations: \n" + + "You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.\n" + + "For more info visit https://webpack.js.org/guides/code-splitting/" + ); + + this.name = "NoAsyncChunksWarning"; + } +}; diff --git a/webpack-lib/lib/performance/SizeLimitsPlugin.js b/webpack-lib/lib/performance/SizeLimitsPlugin.js new file mode 100644 index 00000000000..b1371a231fc --- /dev/null +++ b/webpack-lib/lib/performance/SizeLimitsPlugin.js @@ -0,0 +1,179 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sean Larkin @thelarkinn +*/ + +"use strict"; + +const { find } = require("../util/SetHelpers"); +const AssetsOverSizeLimitWarning = require("./AssetsOverSizeLimitWarning"); +const EntrypointsOverSizeLimitWarning = require("./EntrypointsOverSizeLimitWarning"); +const NoAsyncChunksWarning = require("./NoAsyncChunksWarning"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation").Asset} Asset */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Entrypoint")} Entrypoint */ +/** @typedef {import("../WebpackError")} WebpackError */ + +/** + * @typedef {object} AssetDetails + * @property {string} name + * @property {number} size + */ + +/** + * @typedef {object} EntrypointDetails + * @property {string} name + * @property {number} size + * @property {string[]} files + */ + +const isOverSizeLimitSet = new WeakSet(); + +/** + * @param {Asset["name"]} name the name + * @param {Asset["source"]} source the source + * @param {Asset["info"]} info the info + * @returns {boolean} result + */ +const excludeSourceMap = (name, source, info) => !info.development; + +module.exports = class SizeLimitsPlugin { + /** + * @param {PerformanceOptions} options the plugin options + */ + constructor(options) { + this.hints = options.hints; + this.maxAssetSize = options.maxAssetSize; + this.maxEntrypointSize = options.maxEntrypointSize; + this.assetFilter = options.assetFilter; + } + + /** + * @param {ChunkGroup | Source} thing the resource to test + * @returns {boolean} true if over the limit + */ + static isOverSizeLimit(thing) { + return isOverSizeLimitSet.has(thing); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const entrypointSizeLimit = this.maxEntrypointSize; + const assetSizeLimit = this.maxAssetSize; + const hints = this.hints; + const assetFilter = this.assetFilter || excludeSourceMap; + + compiler.hooks.afterEmit.tap("SizeLimitsPlugin", compilation => { + /** @type {WebpackError[]} */ + const warnings = []; + + /** + * @param {Entrypoint} entrypoint an entrypoint + * @returns {number} the size of the entrypoint + */ + const getEntrypointSize = entrypoint => { + let size = 0; + for (const file of entrypoint.getFiles()) { + const asset = compilation.getAsset(file); + if ( + asset && + assetFilter(asset.name, asset.source, asset.info) && + asset.source + ) { + size += asset.info.size || asset.source.size(); + } + } + return size; + }; + + /** @type {AssetDetails[]} */ + const assetsOverSizeLimit = []; + for (const { name, source, info } of compilation.getAssets()) { + if (!assetFilter(name, source, info) || !source) { + continue; + } + + const size = info.size || source.size(); + if (size > /** @type {number} */ (assetSizeLimit)) { + assetsOverSizeLimit.push({ + name, + size + }); + isOverSizeLimitSet.add(source); + } + } + + /** + * @param {Asset["name"]} name the name + * @returns {boolean | undefined} result + */ + const fileFilter = name => { + const asset = compilation.getAsset(name); + return asset && assetFilter(asset.name, asset.source, asset.info); + }; + + /** @type {EntrypointDetails[]} */ + const entrypointsOverLimit = []; + for (const [name, entry] of compilation.entrypoints) { + const size = getEntrypointSize(entry); + + if (size > /** @type {number} */ (entrypointSizeLimit)) { + entrypointsOverLimit.push({ + name, + size, + files: entry.getFiles().filter(fileFilter) + }); + isOverSizeLimitSet.add(entry); + } + } + + if (hints) { + // 1. Individual Chunk: Size < 250kb + // 2. Collective Initial Chunks [entrypoint] (Each Set?): Size < 250kb + // 3. No Async Chunks + // if !1, then 2, if !2 return + if (assetsOverSizeLimit.length > 0) { + warnings.push( + new AssetsOverSizeLimitWarning( + assetsOverSizeLimit, + /** @type {number} */ (assetSizeLimit) + ) + ); + } + if (entrypointsOverLimit.length > 0) { + warnings.push( + new EntrypointsOverSizeLimitWarning( + entrypointsOverLimit, + /** @type {number} */ (entrypointSizeLimit) + ) + ); + } + + if (warnings.length > 0) { + const someAsyncChunk = find( + compilation.chunks, + chunk => !chunk.canBeInitial() + ); + + if (!someAsyncChunk) { + warnings.push(new NoAsyncChunksWarning()); + } + + if (hints === "error") { + compilation.errors.push(...warnings); + } else { + compilation.warnings.push(...warnings); + } + } + } + }); + } +}; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js new file mode 100644 index 00000000000..a330b4a4d73 --- /dev/null +++ b/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js @@ -0,0 +1,46 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +class ChunkPrefetchFunctionRuntimeModule extends RuntimeModule { + /** + * @param {string} childType TODO + * @param {string} runtimeFunction TODO + * @param {string} runtimeHandlers TODO + */ + constructor(childType, runtimeFunction, runtimeHandlers) { + super(`chunk ${childType} function`); + this.childType = childType; + this.runtimeFunction = runtimeFunction; + this.runtimeHandlers = runtimeHandlers; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { runtimeFunction, runtimeHandlers } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return Template.asString([ + `${runtimeHandlers} = {};`, + `${runtimeFunction} = ${runtimeTemplate.basicFunction("chunkId", [ + // map is shorter than forEach + `Object.keys(${runtimeHandlers}).map(${runtimeTemplate.basicFunction( + "key", + `${runtimeHandlers}[key](chunkId);` + )});` + ])}` + ]); + } +} + +module.exports = ChunkPrefetchFunctionRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js b/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js new file mode 100644 index 00000000000..08e78ef6b9f --- /dev/null +++ b/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js @@ -0,0 +1,98 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const ChunkPrefetchFunctionRuntimeModule = require("./ChunkPrefetchFunctionRuntimeModule"); +const ChunkPrefetchStartupRuntimeModule = require("./ChunkPrefetchStartupRuntimeModule"); +const ChunkPrefetchTriggerRuntimeModule = require("./ChunkPrefetchTriggerRuntimeModule"); +const ChunkPreloadTriggerRuntimeModule = require("./ChunkPreloadTriggerRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ +/** @typedef {import("../Compiler")} Compiler */ + +class ChunkPrefetchPreloadPlugin { + /** + * @param {Compiler} compiler the compiler + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "ChunkPrefetchPreloadPlugin", + compilation => { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + "ChunkPrefetchPreloadPlugin", + (chunk, set, { chunkGraph }) => { + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; + const startupChildChunks = chunk.getChildrenOfTypeInOrder( + chunkGraph, + "prefetchOrder" + ); + if (startupChildChunks) { + set.add(RuntimeGlobals.prefetchChunk); + set.add(RuntimeGlobals.onChunksLoaded); + set.add(RuntimeGlobals.exports); + compilation.addRuntimeModule( + chunk, + new ChunkPrefetchStartupRuntimeModule(startupChildChunks) + ); + } + } + ); + compilation.hooks.additionalTreeRuntimeRequirements.tap( + "ChunkPrefetchPreloadPlugin", + (chunk, set, { chunkGraph }) => { + const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph); + + if (chunkMap.prefetch) { + set.add(RuntimeGlobals.prefetchChunk); + compilation.addRuntimeModule( + chunk, + new ChunkPrefetchTriggerRuntimeModule(chunkMap.prefetch) + ); + } + if (chunkMap.preload) { + set.add(RuntimeGlobals.preloadChunk); + compilation.addRuntimeModule( + chunk, + new ChunkPreloadTriggerRuntimeModule(chunkMap.preload) + ); + } + } + ); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.prefetchChunk) + .tap("ChunkPrefetchPreloadPlugin", (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new ChunkPrefetchFunctionRuntimeModule( + "prefetch", + RuntimeGlobals.prefetchChunk, + RuntimeGlobals.prefetchChunkHandlers + ) + ); + set.add(RuntimeGlobals.prefetchChunkHandlers); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.preloadChunk) + .tap("ChunkPrefetchPreloadPlugin", (chunk, set) => { + compilation.addRuntimeModule( + chunk, + new ChunkPrefetchFunctionRuntimeModule( + "preload", + RuntimeGlobals.preloadChunk, + RuntimeGlobals.preloadChunkHandlers + ) + ); + set.add(RuntimeGlobals.preloadChunkHandlers); + }); + } + ); + } +} + +module.exports = ChunkPrefetchPreloadPlugin; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js new file mode 100644 index 00000000000..740bbe8c3c1 --- /dev/null +++ b/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js @@ -0,0 +1,55 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +class ChunkPrefetchStartupRuntimeModule extends RuntimeModule { + /** + * @param {{ onChunks: Chunk[], chunks: Set }[]} startupChunks chunk ids to trigger when chunks are loaded + */ + constructor(startupChunks) { + super("startup prefetch", RuntimeModule.STAGE_TRIGGER); + this.startupChunks = startupChunks; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { startupChunks } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const chunk = /** @type {Chunk} */ (this.chunk); + const { runtimeTemplate } = compilation; + return Template.asString( + startupChunks.map( + ({ onChunks, chunks }) => + `${RuntimeGlobals.onChunksLoaded}(0, ${JSON.stringify( + // This need to include itself to delay execution after this chunk has been fully loaded + onChunks.filter(c => c === chunk).map(c => c.id) + )}, ${runtimeTemplate.basicFunction( + "", + chunks.size < 3 + ? Array.from( + chunks, + c => + `${RuntimeGlobals.prefetchChunk}(${JSON.stringify(c.id)});` + ) + : `${JSON.stringify(Array.from(chunks, c => c.id))}.map(${ + RuntimeGlobals.prefetchChunk + });` + )}, 5);` + ) + ); + } +} + +module.exports = ChunkPrefetchStartupRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js new file mode 100644 index 00000000000..74eb2bc613f --- /dev/null +++ b/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js @@ -0,0 +1,51 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +class ChunkPrefetchTriggerRuntimeModule extends RuntimeModule { + /** + * @param {Record} chunkMap map from chunk to + */ + constructor(chunkMap) { + super("chunk prefetch trigger", RuntimeModule.STAGE_TRIGGER); + this.chunkMap = chunkMap; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { chunkMap } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const body = [ + "var chunks = chunkToChildrenMap[chunkId];", + `Array.isArray(chunks) && chunks.map(${RuntimeGlobals.prefetchChunk});` + ]; + return Template.asString([ + Template.asString([ + `var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`, + `${ + RuntimeGlobals.ensureChunkHandlers + }.prefetch = ${runtimeTemplate.expressionFunction( + `Promise.all(promises).then(${runtimeTemplate.basicFunction( + "", + body + )})`, + "chunkId, promises" + )};` + ]) + ]); + } +} + +module.exports = ChunkPrefetchTriggerRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js new file mode 100644 index 00000000000..8509def176d --- /dev/null +++ b/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js @@ -0,0 +1,45 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +class ChunkPreloadTriggerRuntimeModule extends RuntimeModule { + /** + * @param {Record} chunkMap map from chunk to chunks + */ + constructor(chunkMap) { + super("chunk preload trigger", RuntimeModule.STAGE_TRIGGER); + this.chunkMap = chunkMap; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { chunkMap } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const body = [ + "var chunks = chunkToChildrenMap[chunkId];", + `Array.isArray(chunks) && chunks.map(${RuntimeGlobals.preloadChunk});` + ]; + return Template.asString([ + Template.asString([ + `var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`, + `${ + RuntimeGlobals.ensureChunkHandlers + }.preload = ${runtimeTemplate.basicFunction("chunkId", body)};` + ]) + ]); + } +} + +module.exports = ChunkPreloadTriggerRuntimeModule; diff --git a/webpack-lib/lib/rules/BasicEffectRulePlugin.js b/webpack-lib/lib/rules/BasicEffectRulePlugin.js new file mode 100644 index 00000000000..935716baad5 --- /dev/null +++ b/webpack-lib/lib/rules/BasicEffectRulePlugin.js @@ -0,0 +1,45 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ + +class BasicEffectRulePlugin { + /** + * @param {string} ruleProperty the rule property + * @param {string=} effectType the effect type + */ + constructor(ruleProperty, effectType) { + this.ruleProperty = ruleProperty; + this.effectType = effectType || ruleProperty; + } + + /** + * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler + * @returns {void} + */ + apply(ruleSetCompiler) { + ruleSetCompiler.hooks.rule.tap( + "BasicEffectRulePlugin", + (path, rule, unhandledProperties, result, references) => { + if (unhandledProperties.has(this.ruleProperty)) { + unhandledProperties.delete(this.ruleProperty); + + const value = + rule[/** @type {keyof RuleSetRule} */ (this.ruleProperty)]; + + result.effects.push({ + type: this.effectType, + value + }); + } + } + ); + } +} + +module.exports = BasicEffectRulePlugin; diff --git a/webpack-lib/lib/rules/BasicMatcherRulePlugin.js b/webpack-lib/lib/rules/BasicMatcherRulePlugin.js new file mode 100644 index 00000000000..47ac214f624 --- /dev/null +++ b/webpack-lib/lib/rules/BasicMatcherRulePlugin.js @@ -0,0 +1,54 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ +/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */ + +class BasicMatcherRulePlugin { + /** + * @param {string} ruleProperty the rule property + * @param {string=} dataProperty the data property + * @param {boolean=} invert if true, inverts the condition + */ + constructor(ruleProperty, dataProperty, invert) { + this.ruleProperty = ruleProperty; + this.dataProperty = dataProperty || ruleProperty; + this.invert = invert || false; + } + + /** + * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler + * @returns {void} + */ + apply(ruleSetCompiler) { + ruleSetCompiler.hooks.rule.tap( + "BasicMatcherRulePlugin", + (path, rule, unhandledProperties, result) => { + if (unhandledProperties.has(this.ruleProperty)) { + unhandledProperties.delete(this.ruleProperty); + const value = + rule[/** @type {keyof RuleSetRule} */ (this.ruleProperty)]; + const condition = ruleSetCompiler.compileCondition( + `${path}.${this.ruleProperty}`, + value + ); + const fn = condition.fn; + result.conditions.push({ + property: this.dataProperty, + matchWhenEmpty: this.invert + ? !condition.matchWhenEmpty + : condition.matchWhenEmpty, + fn: this.invert ? v => !fn(v) : fn + }); + } + } + ); + } +} + +module.exports = BasicMatcherRulePlugin; diff --git a/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js b/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js new file mode 100644 index 00000000000..984e86f83fa --- /dev/null +++ b/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js @@ -0,0 +1,64 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ +/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */ +/** @typedef {import("./RuleSetCompiler").RuleConditionFunction} RuleConditionFunction */ + +class ObjectMatcherRulePlugin { + /** + * @param {string} ruleProperty the rule property + * @param {string=} dataProperty the data property + * @param {RuleConditionFunction=} additionalConditionFunction need to check + */ + constructor(ruleProperty, dataProperty, additionalConditionFunction) { + this.ruleProperty = ruleProperty; + this.dataProperty = dataProperty || ruleProperty; + this.additionalConditionFunction = additionalConditionFunction; + } + + /** + * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler + * @returns {void} + */ + apply(ruleSetCompiler) { + const { ruleProperty, dataProperty } = this; + ruleSetCompiler.hooks.rule.tap( + "ObjectMatcherRulePlugin", + (path, rule, unhandledProperties, result) => { + if (unhandledProperties.has(ruleProperty)) { + unhandledProperties.delete(ruleProperty); + const value = + /** @type {Record} */ + (rule[/** @type {keyof RuleSetRule} */ (ruleProperty)]); + for (const property of Object.keys(value)) { + const nestedDataProperties = property.split("."); + const condition = ruleSetCompiler.compileCondition( + `${path}.${ruleProperty}.${property}`, + value[property] + ); + if (this.additionalConditionFunction) { + result.conditions.push({ + property: [dataProperty], + matchWhenEmpty: condition.matchWhenEmpty, + fn: this.additionalConditionFunction + }); + } + result.conditions.push({ + property: [dataProperty, ...nestedDataProperties], + matchWhenEmpty: condition.matchWhenEmpty, + fn: condition.fn + }); + } + } + } + ); + } +} + +module.exports = ObjectMatcherRulePlugin; diff --git a/webpack-lib/lib/rules/RuleSetCompiler.js b/webpack-lib/lib/rules/RuleSetCompiler.js new file mode 100644 index 00000000000..5e32e133987 --- /dev/null +++ b/webpack-lib/lib/rules/RuleSetCompiler.js @@ -0,0 +1,400 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncHook } = require("tapable"); + +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRules} RuleSetRules */ + +/** @typedef {function(string | EffectData): boolean} RuleConditionFunction */ + +/** + * @typedef {object} RuleCondition + * @property {string | string[]} property + * @property {boolean} matchWhenEmpty + * @property {RuleConditionFunction} fn + */ + +/** + * @typedef {object} Condition + * @property {boolean} matchWhenEmpty + * @property {RuleConditionFunction} fn + */ + +/** + * @typedef {Record} EffectData + */ + +/** + * @typedef {object} CompiledRule + * @property {RuleCondition[]} conditions + * @property {(Effect|function(EffectData): Effect[])[]} effects + * @property {CompiledRule[]=} rules + * @property {CompiledRule[]=} oneOf + */ + +/** + * @typedef {object} Effect + * @property {string} type + * @property {any} value + */ + +/** + * @typedef {object} RuleSet + * @property {Map} references map of references in the rule set (may grow over time) + * @property {function(EffectData): Effect[]} exec execute the rule set + */ + +/** @typedef {{ apply: (function(RuleSetCompiler): void) }} RuleSetPlugin */ + +class RuleSetCompiler { + /** + * @param {RuleSetPlugin[]} plugins plugins + */ + constructor(plugins) { + this.hooks = Object.freeze({ + /** @type {SyncHook<[string, RuleSetRule, Set, CompiledRule, Map]>} */ + rule: new SyncHook([ + "path", + "rule", + "unhandledProperties", + "compiledRule", + "references" + ]) + }); + if (plugins) { + for (const plugin of plugins) { + plugin.apply(this); + } + } + } + + /** + * @param {TODO[]} ruleSet raw user provided rules + * @returns {RuleSet} compiled RuleSet + */ + compile(ruleSet) { + const refs = new Map(); + const rules = this.compileRules("ruleSet", ruleSet, refs); + + /** + * @param {EffectData} data data passed in + * @param {CompiledRule} rule the compiled rule + * @param {Effect[]} effects an array where effects are pushed to + * @returns {boolean} true, if the rule has matched + */ + const execRule = (data, rule, effects) => { + for (const condition of rule.conditions) { + const p = condition.property; + if (Array.isArray(p)) { + /** @type {EffectData | string | undefined} */ + let current = data; + for (const subProperty of p) { + if ( + current && + typeof current === "object" && + Object.prototype.hasOwnProperty.call(current, subProperty) + ) { + current = current[subProperty]; + } else { + current = undefined; + break; + } + } + if (current !== undefined) { + if (!condition.fn(current)) return false; + continue; + } + } else if (p in data) { + const value = data[p]; + if (value !== undefined) { + if (!condition.fn(value)) return false; + continue; + } + } + if (!condition.matchWhenEmpty) { + return false; + } + } + for (const effect of rule.effects) { + if (typeof effect === "function") { + const returnedEffects = effect(data); + for (const effect of returnedEffects) { + effects.push(effect); + } + } else { + effects.push(effect); + } + } + if (rule.rules) { + for (const childRule of rule.rules) { + execRule(data, childRule, effects); + } + } + if (rule.oneOf) { + for (const childRule of rule.oneOf) { + if (execRule(data, childRule, effects)) { + break; + } + } + } + return true; + }; + + return { + references: refs, + exec: data => { + /** @type {Effect[]} */ + const effects = []; + for (const rule of rules) { + execRule(data, rule, effects); + } + return effects; + } + }; + } + + /** + * @param {string} path current path + * @param {RuleSetRules} rules the raw rules provided by user + * @param {Map} refs references + * @returns {CompiledRule[]} rules + */ + compileRules(path, rules, refs) { + return rules + .filter(Boolean) + .map((rule, i) => + this.compileRule( + `${path}[${i}]`, + /** @type {RuleSetRule} */ (rule), + refs + ) + ); + } + + /** + * @param {string} path current path + * @param {RuleSetRule} rule the raw rule provided by user + * @param {Map} refs references + * @returns {CompiledRule} normalized and compiled rule for processing + */ + compileRule(path, rule, refs) { + const unhandledProperties = new Set( + Object.keys(rule).filter( + key => rule[/** @type {keyof RuleSetRule} */ (key)] !== undefined + ) + ); + + /** @type {CompiledRule} */ + const compiledRule = { + conditions: [], + effects: [], + rules: undefined, + oneOf: undefined + }; + + this.hooks.rule.call(path, rule, unhandledProperties, compiledRule, refs); + + if (unhandledProperties.has("rules")) { + unhandledProperties.delete("rules"); + const rules = rule.rules; + if (!Array.isArray(rules)) + throw this.error(path, rules, "Rule.rules must be an array of rules"); + compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs); + } + + if (unhandledProperties.has("oneOf")) { + unhandledProperties.delete("oneOf"); + const oneOf = rule.oneOf; + if (!Array.isArray(oneOf)) + throw this.error(path, oneOf, "Rule.oneOf must be an array of rules"); + compiledRule.oneOf = this.compileRules(`${path}.oneOf`, oneOf, refs); + } + + if (unhandledProperties.size > 0) { + throw this.error( + path, + rule, + `Properties ${Array.from(unhandledProperties).join(", ")} are unknown` + ); + } + + return compiledRule; + } + + /** + * @param {string} path current path + * @param {any} condition user provided condition value + * @returns {Condition} compiled condition + */ + compileCondition(path, condition) { + if (condition === "") { + return { + matchWhenEmpty: true, + fn: str => str === "" + }; + } + if (!condition) { + throw this.error( + path, + condition, + "Expected condition but got falsy value" + ); + } + if (typeof condition === "string") { + return { + matchWhenEmpty: condition.length === 0, + fn: str => typeof str === "string" && str.startsWith(condition) + }; + } + if (typeof condition === "function") { + try { + return { + matchWhenEmpty: condition(""), + fn: condition + }; + } catch (_err) { + throw this.error( + path, + condition, + "Evaluation of condition function threw error" + ); + } + } + if (condition instanceof RegExp) { + return { + matchWhenEmpty: condition.test(""), + fn: v => typeof v === "string" && condition.test(v) + }; + } + if (Array.isArray(condition)) { + const items = condition.map((c, i) => + this.compileCondition(`${path}[${i}]`, c) + ); + return this.combineConditionsOr(items); + } + + if (typeof condition !== "object") { + throw this.error( + path, + condition, + `Unexpected ${typeof condition} when condition was expected` + ); + } + + const conditions = []; + for (const key of Object.keys(condition)) { + const value = condition[key]; + switch (key) { + case "or": + if (value) { + if (!Array.isArray(value)) { + throw this.error( + `${path}.or`, + condition.or, + "Expected array of conditions" + ); + } + conditions.push(this.compileCondition(`${path}.or`, value)); + } + break; + case "and": + if (value) { + if (!Array.isArray(value)) { + throw this.error( + `${path}.and`, + condition.and, + "Expected array of conditions" + ); + } + let i = 0; + for (const item of value) { + conditions.push(this.compileCondition(`${path}.and[${i}]`, item)); + i++; + } + } + break; + case "not": + if (value) { + const matcher = this.compileCondition(`${path}.not`, value); + const fn = matcher.fn; + conditions.push({ + matchWhenEmpty: !matcher.matchWhenEmpty, + fn: /** @type {RuleConditionFunction} */ (v => !fn(v)) + }); + } + break; + default: + throw this.error( + `${path}.${key}`, + condition[key], + `Unexpected property ${key} in condition` + ); + } + } + if (conditions.length === 0) { + throw this.error( + path, + condition, + "Expected condition, but got empty thing" + ); + } + return this.combineConditionsAnd(conditions); + } + + /** + * @param {Condition[]} conditions some conditions + * @returns {Condition} merged condition + */ + combineConditionsOr(conditions) { + if (conditions.length === 0) { + return { + matchWhenEmpty: false, + fn: () => false + }; + } else if (conditions.length === 1) { + return conditions[0]; + } + return { + matchWhenEmpty: conditions.some(c => c.matchWhenEmpty), + fn: v => conditions.some(c => c.fn(v)) + }; + } + + /** + * @param {Condition[]} conditions some conditions + * @returns {Condition} merged condition + */ + combineConditionsAnd(conditions) { + if (conditions.length === 0) { + return { + matchWhenEmpty: false, + fn: () => false + }; + } else if (conditions.length === 1) { + return conditions[0]; + } + return { + matchWhenEmpty: conditions.every(c => c.matchWhenEmpty), + fn: v => conditions.every(c => c.fn(v)) + }; + } + + /** + * @param {string} path current path + * @param {any} value value at the error location + * @param {string} message message explaining the problem + * @returns {Error} an error object + */ + error(path, value, message) { + return new Error( + `Compiling RuleSet failed: ${message} (at ${path}: ${value})` + ); + } +} + +module.exports = RuleSetCompiler; diff --git a/webpack-lib/lib/rules/UseEffectRulePlugin.js b/webpack-lib/lib/rules/UseEffectRulePlugin.js new file mode 100644 index 00000000000..56f2423de62 --- /dev/null +++ b/webpack-lib/lib/rules/UseEffectRulePlugin.js @@ -0,0 +1,200 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); + +/** @typedef {import("../../declarations/WebpackOptions").RuleSetLoader} RuleSetLoader */ +/** @typedef {import("../../declarations/WebpackOptions").RuleSetLoaderOptions} RuleSetLoaderOptions */ +/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ +/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ +/** @typedef {import("./RuleSetCompiler").Effect} Effect */ + +class UseEffectRulePlugin { + /** + * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler + * @returns {void} + */ + apply(ruleSetCompiler) { + ruleSetCompiler.hooks.rule.tap( + "UseEffectRulePlugin", + (path, rule, unhandledProperties, result, references) => { + /** + * @param {keyof RuleSetRule} property property + * @param {string} correctProperty correct property + */ + const conflictWith = (property, correctProperty) => { + if (unhandledProperties.has(property)) { + throw ruleSetCompiler.error( + `${path}.${property}`, + rule[property], + `A Rule must not have a '${property}' property when it has a '${correctProperty}' property` + ); + } + }; + + if (unhandledProperties.has("use")) { + unhandledProperties.delete("use"); + unhandledProperties.delete("enforce"); + + conflictWith("loader", "use"); + conflictWith("options", "use"); + + const use = rule.use; + const enforce = rule.enforce; + + const type = enforce ? `use-${enforce}` : "use"; + + /** + * @param {string} path options path + * @param {string} defaultIdent default ident when none is provided + * @param {object} item user provided use value + * @returns {Effect|function(any): Effect[]} effect + */ + const useToEffect = (path, defaultIdent, item) => { + if (typeof item === "function") { + return data => useToEffectsWithoutIdent(path, item(data)); + } + return useToEffectRaw(path, defaultIdent, item); + }; + + /** + * @param {string} path options path + * @param {string} defaultIdent default ident when none is provided + * @param {{ ident?: string, loader?: RuleSetLoader, options?: RuleSetLoaderOptions }} item user provided use value + * @returns {Effect} effect + */ + const useToEffectRaw = (path, defaultIdent, item) => { + if (typeof item === "string") { + return { + type, + value: { + loader: item, + options: undefined, + ident: undefined + } + }; + } + const loader = item.loader; + const options = item.options; + let ident = item.ident; + if (options && typeof options === "object") { + if (!ident) ident = defaultIdent; + references.set(ident, options); + } + if (typeof options === "string") { + util.deprecate( + () => {}, + `Using a string as loader options is deprecated (${path}.options)`, + "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" + )(); + } + return { + type: enforce ? `use-${enforce}` : "use", + value: { + loader, + options, + ident + } + }; + }; + + /** + * @param {string} path options path + * @param {any} items user provided use value + * @returns {Effect[]} effects + */ + const useToEffectsWithoutIdent = (path, items) => { + if (Array.isArray(items)) { + return items + .filter(Boolean) + .map((item, idx) => + useToEffectRaw(`${path}[${idx}]`, "[[missing ident]]", item) + ); + } + return [useToEffectRaw(path, "[[missing ident]]", items)]; + }; + + /** + * @param {string} path current path + * @param {any} items user provided use value + * @returns {(Effect|function(any): Effect[])[]} effects + */ + const useToEffects = (path, items) => { + if (Array.isArray(items)) { + return items.filter(Boolean).map((item, idx) => { + const subPath = `${path}[${idx}]`; + return useToEffect(subPath, subPath, item); + }); + } + return [useToEffect(path, path, items)]; + }; + + if (typeof use === "function") { + result.effects.push(data => + useToEffectsWithoutIdent( + `${path}.use`, + use(/** @type {TODO} */ (data)) + ) + ); + } else { + for (const effect of useToEffects(`${path}.use`, use)) { + result.effects.push(effect); + } + } + } + + if (unhandledProperties.has("loader")) { + unhandledProperties.delete("loader"); + unhandledProperties.delete("options"); + unhandledProperties.delete("enforce"); + + const loader = /** @type {RuleSetLoader} */ (rule.loader); + const options = rule.options; + const enforce = rule.enforce; + + if (loader.includes("!")) { + throw ruleSetCompiler.error( + `${path}.loader`, + loader, + "Exclamation mark separated loader lists has been removed in favor of the 'use' property with arrays" + ); + } + + if (loader.includes("?")) { + throw ruleSetCompiler.error( + `${path}.loader`, + loader, + "Query arguments on 'loader' has been removed in favor of the 'options' property" + ); + } + + if (typeof options === "string") { + util.deprecate( + () => {}, + `Using a string as loader options is deprecated (${path}.options)`, + "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" + )(); + } + + const ident = + options && typeof options === "object" ? path : undefined; + references.set(ident, options); + result.effects.push({ + type: enforce ? `use-${enforce}` : "use", + value: { + loader, + options, + ident + } + }); + } + } + ); + } +} + +module.exports = UseEffectRulePlugin; diff --git a/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js b/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js new file mode 100644 index 00000000000..79141c76f2e --- /dev/null +++ b/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js @@ -0,0 +1,133 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class AsyncModuleRuntimeModule extends HelperRuntimeModule { + constructor() { + super("async module"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.asyncModule; + return Template.asString([ + 'var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__";', + `var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "${RuntimeGlobals.exports}";`, + 'var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";', + `var resolveQueue = ${runtimeTemplate.basicFunction("queue", [ + "if(queue && queue.d < 1) {", + Template.indent([ + "queue.d = 1;", + `queue.forEach(${runtimeTemplate.expressionFunction( + "fn.r--", + "fn" + )});`, + `queue.forEach(${runtimeTemplate.expressionFunction( + "fn.r-- ? fn.r++ : fn()", + "fn" + )});` + ]), + "}" + ])}`, + `var wrapDeps = ${runtimeTemplate.returningFunction( + `deps.map(${runtimeTemplate.basicFunction("dep", [ + 'if(dep !== null && typeof dep === "object") {', + Template.indent([ + "if(dep[webpackQueues]) return dep;", + "if(dep.then) {", + Template.indent([ + "var queue = [];", + "queue.d = 0;", + `dep.then(${runtimeTemplate.basicFunction("r", [ + "obj[webpackExports] = r;", + "resolveQueue(queue);" + ])}, ${runtimeTemplate.basicFunction("e", [ + "obj[webpackError] = e;", + "resolveQueue(queue);" + ])});`, + "var obj = {};", + `obj[webpackQueues] = ${runtimeTemplate.expressionFunction( + "fn(queue)", + "fn" + )};`, + "return obj;" + ]), + "}" + ]), + "}", + "var ret = {};", + `ret[webpackQueues] = ${runtimeTemplate.emptyFunction()};`, + "ret[webpackExports] = dep;", + "return ret;" + ])})`, + "deps" + )};`, + `${fn} = ${runtimeTemplate.basicFunction("module, body, hasAwait", [ + "var queue;", + "hasAwait && ((queue = []).d = -1);", + "var depQueues = new Set();", + "var exports = module.exports;", + "var currentDeps;", + "var outerResolve;", + "var reject;", + `var promise = new Promise(${runtimeTemplate.basicFunction( + "resolve, rej", + ["reject = rej;", "outerResolve = resolve;"] + )});`, + "promise[webpackExports] = exports;", + `promise[webpackQueues] = ${runtimeTemplate.expressionFunction( + `queue && fn(queue), depQueues.forEach(fn), promise["catch"](${runtimeTemplate.emptyFunction()})`, + "fn" + )};`, + "module.exports = promise;", + `body(${runtimeTemplate.basicFunction("deps", [ + "currentDeps = wrapDeps(deps);", + "var fn;", + `var getResult = ${runtimeTemplate.returningFunction( + `currentDeps.map(${runtimeTemplate.basicFunction("d", [ + "if(d[webpackError]) throw d[webpackError];", + "return d[webpackExports];" + ])})` + )}`, + `var promise = new Promise(${runtimeTemplate.basicFunction( + "resolve", + [ + `fn = ${runtimeTemplate.expressionFunction( + "resolve(getResult)", + "" + )};`, + "fn.r = 0;", + `var fnQueue = ${runtimeTemplate.expressionFunction( + "q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))", + "q" + )};`, + `currentDeps.map(${runtimeTemplate.expressionFunction( + "dep[webpackQueues](fnQueue)", + "dep" + )});` + ] + )});`, + "return fn.r ? promise : getResult();" + ])}, ${runtimeTemplate.expressionFunction( + "(err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue)", + "err" + )});`, + "queue && queue.d < 0 && (queue.d = 0);" + ])};` + ]); + } +} + +module.exports = AsyncModuleRuntimeModule; diff --git a/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js b/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js new file mode 100644 index 00000000000..74b40a1e883 --- /dev/null +++ b/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js @@ -0,0 +1,85 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ + +class AutoPublicPathRuntimeModule extends RuntimeModule { + constructor() { + super("publicPath", RuntimeModule.STAGE_BASIC); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { scriptType, importMetaName, path } = compilation.outputOptions; + const chunkName = compilation.getPath( + JavascriptModulesPlugin.getChunkFilenameTemplate( + /** @type {Chunk} */ + (this.chunk), + compilation.outputOptions + ), + { + chunk: this.chunk, + contentHashType: "javascript" + } + ); + const undoPath = getUndoPath( + chunkName, + /** @type {string} */ (path), + false + ); + + return Template.asString([ + "var scriptUrl;", + scriptType === "module" + ? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url` + : Template.asString([ + `if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`, + `var document = ${RuntimeGlobals.global}.document;`, + "if (!scriptUrl && document) {", + Template.indent([ + // Technically we could use `document.currentScript instanceof window.HTMLScriptElement`, + // but an attacker could try to inject `` + // and use `` + "if (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')", + Template.indent("scriptUrl = document.currentScript.src;"), + "if (!scriptUrl) {", + Template.indent([ + 'var scripts = document.getElementsByTagName("script");', + "if(scripts.length) {", + Template.indent([ + "var i = scripts.length - 1;", + "while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;" + ]), + "}" + ]), + "}" + ]), + "}" + ]), + "// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration", + '// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.', + 'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");', + 'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");', + !undoPath + ? `${RuntimeGlobals.publicPath} = scriptUrl;` + : `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify( + undoPath + )};` + ]); + } +} + +module.exports = AutoPublicPathRuntimeModule; diff --git a/webpack-lib/lib/runtime/BaseUriRuntimeModule.js b/webpack-lib/lib/runtime/BaseUriRuntimeModule.js new file mode 100644 index 00000000000..99609b762bd --- /dev/null +++ b/webpack-lib/lib/runtime/BaseUriRuntimeModule.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ +/** @typedef {import("../Chunk")} Chunk */ + +class BaseUriRuntimeModule extends RuntimeModule { + constructor() { + super("base uri", RuntimeModule.STAGE_ATTACH); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const chunk = /** @type {Chunk} */ (this.chunk); + const options = + /** @type {EntryDescriptionNormalized} */ + (chunk.getEntryOptions()); + return `${RuntimeGlobals.baseURI} = ${ + options.baseUri === undefined + ? "undefined" + : JSON.stringify(options.baseUri) + };`; + } +} + +module.exports = BaseUriRuntimeModule; diff --git a/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js b/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js new file mode 100644 index 00000000000..22149767907 --- /dev/null +++ b/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js @@ -0,0 +1,27 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +class ChunkNameRuntimeModule extends RuntimeModule { + /** + * @param {string} chunkName the chunk's name + */ + constructor(chunkName) { + super("chunkName"); + this.chunkName = chunkName; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return `${RuntimeGlobals.chunkName} = ${JSON.stringify(this.chunkName)};`; + } +} + +module.exports = ChunkNameRuntimeModule; diff --git a/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js b/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js new file mode 100644 index 00000000000..1406e051fd9 --- /dev/null +++ b/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js @@ -0,0 +1,40 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class CompatGetDefaultExportRuntimeModule extends HelperRuntimeModule { + constructor() { + super("compat get default export"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.compatGetDefaultExport; + return Template.asString([ + "// getDefaultExport function for compatibility with non-harmony modules", + `${fn} = ${runtimeTemplate.basicFunction("module", [ + "var getter = module && module.__esModule ?", + Template.indent([ + `${runtimeTemplate.returningFunction("module['default']")} :`, + `${runtimeTemplate.returningFunction("module")};` + ]), + `${RuntimeGlobals.definePropertyGetters}(getter, { a: getter });`, + "return getter;" + ])};` + ]); + } +} + +module.exports = CompatGetDefaultExportRuntimeModule; diff --git a/webpack-lib/lib/runtime/CompatRuntimeModule.js b/webpack-lib/lib/runtime/CompatRuntimeModule.js new file mode 100644 index 00000000000..cf386c0886b --- /dev/null +++ b/webpack-lib/lib/runtime/CompatRuntimeModule.js @@ -0,0 +1,83 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../MainTemplate")} MainTemplate */ + +class CompatRuntimeModule extends RuntimeModule { + constructor() { + super("compat", RuntimeModule.STAGE_ATTACH); + this.fullHash = true; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const { + runtimeTemplate, + mainTemplate, + moduleTemplates, + dependencyTemplates + } = compilation; + const bootstrap = mainTemplate.hooks.bootstrap.call( + "", + chunk, + compilation.hash || "XXXX", + moduleTemplates.javascript, + dependencyTemplates + ); + const localVars = mainTemplate.hooks.localVars.call( + "", + chunk, + compilation.hash || "XXXX" + ); + const requireExtensions = mainTemplate.hooks.requireExtensions.call( + "", + chunk, + compilation.hash || "XXXX" + ); + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); + let requireEnsure = ""; + if (runtimeRequirements.has(RuntimeGlobals.ensureChunk)) { + const requireEnsureHandler = mainTemplate.hooks.requireEnsure.call( + "", + chunk, + compilation.hash || "XXXX", + "chunkId" + ); + if (requireEnsureHandler) { + requireEnsure = `${ + RuntimeGlobals.ensureChunkHandlers + }.compat = ${runtimeTemplate.basicFunction( + "chunkId, promises", + requireEnsureHandler + )};`; + } + } + return [bootstrap, localVars, requireEnsure, requireExtensions] + .filter(Boolean) + .join("\n"); + } + + /** + * @returns {boolean} true, if the runtime module should get it's own scope + */ + shouldIsolate() { + // We avoid isolating this to have better backward-compat + return false; + } +} + +module.exports = CompatRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js b/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js new file mode 100644 index 00000000000..05b811b19b0 --- /dev/null +++ b/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class CreateFakeNamespaceObjectRuntimeModule extends HelperRuntimeModule { + constructor() { + super("create fake namespace object"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.createFakeNamespaceObject; + return Template.asString([ + `var getProto = Object.getPrototypeOf ? ${runtimeTemplate.returningFunction( + "Object.getPrototypeOf(obj)", + "obj" + )} : ${runtimeTemplate.returningFunction("obj.__proto__", "obj")};`, + "var 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", + // Note: must be a function (not arrow), because this is used in body! + `${fn} = function(value, mode) {`, + Template.indent([ + "if(mode & 1) value = this(value);", + "if(mode & 8) return value;", + "if(typeof value === 'object' && value) {", + Template.indent([ + "if((mode & 4) && value.__esModule) return value;", + "if((mode & 16) && typeof value.then === 'function') return value;" + ]), + "}", + "var ns = Object.create(null);", + `${RuntimeGlobals.makeNamespaceObject}(ns);`, + "var def = {};", + "leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];", + "for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {", + Template.indent([ + `Object.getOwnPropertyNames(current).forEach(${runtimeTemplate.expressionFunction( + `def[key] = ${runtimeTemplate.returningFunction("value[key]", "")}`, + "key" + )});` + ]), + "}", + `def['default'] = ${runtimeTemplate.returningFunction("value", "")};`, + `${RuntimeGlobals.definePropertyGetters}(ns, def);`, + "return ns;" + ]), + "};" + ]); + } +} + +module.exports = CreateFakeNamespaceObjectRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js b/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js new file mode 100644 index 00000000000..7859e87d411 --- /dev/null +++ b/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class CreateScriptRuntimeModule extends HelperRuntimeModule { + constructor() { + super("trusted types script"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate, outputOptions } = compilation; + const { trustedTypes } = outputOptions; + const fn = RuntimeGlobals.createScript; + + return Template.asString( + `${fn} = ${runtimeTemplate.returningFunction( + trustedTypes + ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScript(script)` + : "script", + "script" + )};` + ); + } +} + +module.exports = CreateScriptRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js b/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js new file mode 100644 index 00000000000..4c8960024d9 --- /dev/null +++ b/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class CreateScriptUrlRuntimeModule extends HelperRuntimeModule { + constructor() { + super("trusted types script url"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate, outputOptions } = compilation; + const { trustedTypes } = outputOptions; + const fn = RuntimeGlobals.createScriptUrl; + + return Template.asString( + `${fn} = ${runtimeTemplate.returningFunction( + trustedTypes + ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScriptURL(url)` + : "url", + "url" + )};` + ); + } +} + +module.exports = CreateScriptUrlRuntimeModule; diff --git a/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js b/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js new file mode 100644 index 00000000000..4dad207a935 --- /dev/null +++ b/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js @@ -0,0 +1,42 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class DefinePropertyGettersRuntimeModule extends HelperRuntimeModule { + constructor() { + super("define property getters"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.definePropertyGetters; + return Template.asString([ + "// define getter functions for harmony exports", + `${fn} = ${runtimeTemplate.basicFunction("exports, definition", [ + "for(var key in definition) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(definition, key) && !${RuntimeGlobals.hasOwnProperty}(exports, key)) {`, + Template.indent([ + "Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });" + ]), + "}" + ]), + "}" + ])};` + ]); + } +} + +module.exports = DefinePropertyGettersRuntimeModule; diff --git a/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js b/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js new file mode 100644 index 00000000000..bc6c0ecbdf1 --- /dev/null +++ b/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js @@ -0,0 +1,68 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +class EnsureChunkRuntimeModule extends RuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("ensure chunk"); + this.runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + // Check if there are non initial chunks which need to be imported using require-ensure + if (this.runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers)) { + const withFetchPriority = this.runtimeRequirements.has( + RuntimeGlobals.hasFetchPriority + ); + const handlers = RuntimeGlobals.ensureChunkHandlers; + return Template.asString([ + `${handlers} = {};`, + "// This file contains only the entry chunk.", + "// The chunk loading function for additional chunks", + `${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.basicFunction( + `chunkId${withFetchPriority ? ", fetchPriority" : ""}`, + [ + `return Promise.all(Object.keys(${handlers}).reduce(${runtimeTemplate.basicFunction( + "promises, key", + [ + `${handlers}[key](chunkId, promises${ + withFetchPriority ? ", fetchPriority" : "" + });`, + "return promises;" + ] + )}, []));` + ] + )};` + ]); + } + // There ensureChunk is used somewhere in the tree, so we need an empty requireEnsure + // function. This can happen with multiple entrypoints. + return Template.asString([ + "// The chunk loading function for additional chunks", + "// Since all referenced chunks are already included", + "// in this file, this function is empty here.", + `${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.returningFunction( + "Promise.resolve()" + )};` + ]); + } +} + +module.exports = EnsureChunkRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js b/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js new file mode 100644 index 00000000000..3d9b2659950 --- /dev/null +++ b/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js @@ -0,0 +1,295 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { first } = require("../util/SetHelpers"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ + +class GetChunkFilenameRuntimeModule extends RuntimeModule { + /** + * @param {string} contentType the contentType to use the content hash for + * @param {string} name kind of filename + * @param {string} global function name to be assigned + * @param {function(Chunk): TemplatePath | false} getFilenameForChunk functor to get the filename or function + * @param {boolean} allChunks when false, only async chunks are included + */ + constructor(contentType, name, global, getFilenameForChunk, allChunks) { + super(`get ${name} chunk filename`); + this.contentType = contentType; + this.global = global; + this.getFilenameForChunk = getFilenameForChunk; + this.allChunks = allChunks; + this.dependentHash = true; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { global, contentType, getFilenameForChunk, allChunks } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const { runtimeTemplate } = compilation; + + /** @type {Map>} */ + const chunkFilenames = new Map(); + let maxChunks = 0; + /** @type {string | undefined} */ + let dynamicFilename; + + /** + * @param {Chunk} c the chunk + * @returns {void} + */ + const addChunk = c => { + const chunkFilename = getFilenameForChunk(c); + if (chunkFilename) { + let set = chunkFilenames.get(chunkFilename); + if (set === undefined) { + chunkFilenames.set(chunkFilename, (set = new Set())); + } + set.add(c); + if (typeof chunkFilename === "string") { + if (set.size < maxChunks) return; + if (set.size === maxChunks) { + if ( + chunkFilename.length < + /** @type {string} */ (dynamicFilename).length + ) { + return; + } + + if ( + chunkFilename.length === + /** @type {string} */ (dynamicFilename).length && + chunkFilename < /** @type {string} */ (dynamicFilename) + ) { + return; + } + } + maxChunks = set.size; + dynamicFilename = chunkFilename; + } + } + }; + + /** @type {string[]} */ + const includedChunksMessages = []; + if (allChunks) { + includedChunksMessages.push("all chunks"); + for (const c of chunk.getAllReferencedChunks()) { + addChunk(c); + } + } else { + includedChunksMessages.push("async chunks"); + for (const c of chunk.getAllAsyncChunks()) { + addChunk(c); + } + const includeEntries = chunkGraph + .getTreeRuntimeRequirements(chunk) + .has(RuntimeGlobals.ensureChunkIncludeEntries); + if (includeEntries) { + includedChunksMessages.push("sibling chunks for the entrypoint"); + for (const c of chunkGraph.getChunkEntryDependentChunksIterable( + chunk + )) { + addChunk(c); + } + } + } + for (const entrypoint of chunk.getAllReferencedAsyncEntrypoints()) { + addChunk(entrypoint.chunks[entrypoint.chunks.length - 1]); + } + + /** @type {Map>} */ + const staticUrls = new Map(); + /** @type {Set} */ + const dynamicUrlChunks = new Set(); + + /** + * @param {Chunk} c the chunk + * @param {string | TemplatePath} chunkFilename the filename template for the chunk + * @returns {void} + */ + const addStaticUrl = (c, chunkFilename) => { + /** + * @param {string | number} value a value + * @returns {string} string to put in quotes + */ + const unquotedStringify = value => { + const str = `${value}`; + if (str.length >= 5 && str === `${c.id}`) { + // This is shorter and generates the same result + return '" + chunkId + "'; + } + const s = JSON.stringify(str); + return s.slice(1, -1); + }; + /** + * @param {string} value string + * @returns {function(number): string} string to put in quotes with length + */ + const unquotedStringifyWithLength = value => length => + unquotedStringify(`${value}`.slice(0, length)); + const chunkFilenameValue = + typeof chunkFilename === "function" + ? JSON.stringify( + chunkFilename({ + chunk: c, + contentHashType: contentType + }) + ) + : JSON.stringify(chunkFilename); + const staticChunkFilename = compilation.getPath(chunkFilenameValue, { + hash: `" + ${RuntimeGlobals.getFullHash}() + "`, + hashWithLength: length => + `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, + chunk: { + id: unquotedStringify(/** @type {number | string} */ (c.id)), + hash: unquotedStringify(/** @type {string} */ (c.renderedHash)), + hashWithLength: unquotedStringifyWithLength( + /** @type {string} */ (c.renderedHash) + ), + name: unquotedStringify( + c.name || /** @type {number | string} */ (c.id) + ), + contentHash: { + [contentType]: unquotedStringify(c.contentHash[contentType]) + }, + contentHashWithLength: { + [contentType]: unquotedStringifyWithLength( + c.contentHash[contentType] + ) + } + }, + contentHashType: contentType + }); + let set = staticUrls.get(staticChunkFilename); + if (set === undefined) { + staticUrls.set(staticChunkFilename, (set = new Set())); + } + set.add(c.id); + }; + + for (const [filename, chunks] of chunkFilenames) { + if (filename !== dynamicFilename) { + for (const c of chunks) addStaticUrl(c, filename); + } else { + for (const c of chunks) dynamicUrlChunks.add(c); + } + } + + /** + * @param {function(Chunk): string | number} fn function from chunk to value + * @returns {string} code with static mapping of results of fn + */ + const createMap = fn => { + /** @type {Record} */ + const obj = {}; + let useId = false; + /** @type {number | string | undefined} */ + let lastKey; + let entries = 0; + for (const c of dynamicUrlChunks) { + const value = fn(c); + if (value === c.id) { + useId = true; + } else { + obj[/** @type {number | string} */ (c.id)] = value; + lastKey = /** @type {number | string} */ (c.id); + entries++; + } + } + if (entries === 0) return "chunkId"; + if (entries === 1) { + return useId + ? `(chunkId === ${JSON.stringify(lastKey)} ? ${JSON.stringify( + obj[/** @type {number | string} */ (lastKey)] + )} : chunkId)` + : JSON.stringify(obj[/** @type {number | string} */ (lastKey)]); + } + return useId + ? `(${JSON.stringify(obj)}[chunkId] || chunkId)` + : `${JSON.stringify(obj)}[chunkId]`; + }; + + /** + * @param {function(Chunk): string | number} fn function from chunk to value + * @returns {string} code with static mapping of results of fn for including in quoted string + */ + const mapExpr = fn => `" + ${createMap(fn)} + "`; + + /** + * @param {function(Chunk): string | number} fn function from chunk to value + * @returns {function(number): string} function which generates code with static mapping of results of fn for including in quoted string for specific length + */ + const mapExprWithLength = fn => length => + `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`; + + const url = + dynamicFilename && + compilation.getPath(JSON.stringify(dynamicFilename), { + hash: `" + ${RuntimeGlobals.getFullHash}() + "`, + hashWithLength: length => + `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, + chunk: { + id: '" + chunkId + "', + hash: mapExpr(c => /** @type {string} */ (c.renderedHash)), + hashWithLength: mapExprWithLength( + c => /** @type {string} */ (c.renderedHash) + ), + name: mapExpr(c => c.name || /** @type {number | string} */ (c.id)), + contentHash: { + [contentType]: mapExpr(c => c.contentHash[contentType]) + }, + contentHashWithLength: { + [contentType]: mapExprWithLength(c => c.contentHash[contentType]) + } + }, + contentHashType: contentType + }); + + return Template.asString([ + `// This function allow to reference ${includedChunksMessages.join( + " and " + )}`, + `${global} = ${runtimeTemplate.basicFunction( + "chunkId", + + staticUrls.size > 0 + ? [ + "// return url for filenames not based on template", + // it minimizes to `x===1?"...":x===2?"...":"..."` + Template.asString( + Array.from(staticUrls, ([url, ids]) => { + const condition = + ids.size === 1 + ? `chunkId === ${JSON.stringify(first(ids))}` + : `{${Array.from( + ids, + id => `${JSON.stringify(id)}:1` + ).join(",")}}[chunkId]`; + return `if (${condition}) return ${url};`; + }) + ), + "// return url for filenames based on template", + `return ${url};` + ] + : ["// return url for filenames based on template", `return ${url};`] + )};` + ]); + } +} + +module.exports = GetChunkFilenameRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js b/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js new file mode 100644 index 00000000000..cf9949394fb --- /dev/null +++ b/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js @@ -0,0 +1,30 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class GetFullHashRuntimeModule extends RuntimeModule { + constructor() { + super("getFullHash"); + this.fullHash = true; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return `${RuntimeGlobals.getFullHash} = ${runtimeTemplate.returningFunction( + JSON.stringify(compilation.hash || "XXXX") + )}`; + } +} + +module.exports = GetFullHashRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js b/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js new file mode 100644 index 00000000000..0a9fdf50bb8 --- /dev/null +++ b/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js @@ -0,0 +1,47 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ + +class GetMainFilenameRuntimeModule extends RuntimeModule { + /** + * @param {string} name readable name + * @param {string} global global object binding + * @param {string} filename main file name + */ + constructor(name, global, filename) { + super(`get ${name} filename`); + this.global = global; + this.filename = filename; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { global, filename } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + const chunk = /** @type {Chunk} */ (this.chunk); + const { runtimeTemplate } = compilation; + const url = compilation.getPath(JSON.stringify(filename), { + hash: `" + ${RuntimeGlobals.getFullHash}() + "`, + hashWithLength: length => + `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, + chunk, + runtime: chunk.runtime + }); + return Template.asString([ + `${global} = ${runtimeTemplate.returningFunction(url)};` + ]); + } +} + +module.exports = GetMainFilenameRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js b/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js new file mode 100644 index 00000000000..e8342b3431c --- /dev/null +++ b/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js @@ -0,0 +1,98 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +class GetTrustedTypesPolicyRuntimeModule extends HelperRuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("trusted types policy"); + this.runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate, outputOptions } = compilation; + const { trustedTypes } = outputOptions; + const fn = RuntimeGlobals.getTrustedTypesPolicy; + const wrapPolicyCreationInTryCatch = trustedTypes + ? trustedTypes.onPolicyCreationFailure === "continue" + : false; + + return Template.asString([ + "var policy;", + `${fn} = ${runtimeTemplate.basicFunction("", [ + "// Create Trusted Type policy if Trusted Types are available and the policy doesn't exist yet.", + "if (policy === undefined) {", + Template.indent([ + "policy = {", + Template.indent( + [ + ...(this.runtimeRequirements.has(RuntimeGlobals.createScript) + ? [ + `createScript: ${runtimeTemplate.returningFunction( + "script", + "script" + )}` + ] + : []), + ...(this.runtimeRequirements.has(RuntimeGlobals.createScriptUrl) + ? [ + `createScriptURL: ${runtimeTemplate.returningFunction( + "url", + "url" + )}` + ] + : []) + ].join(",\n") + ), + "};", + ...(trustedTypes + ? [ + 'if (typeof trustedTypes !== "undefined" && trustedTypes.createPolicy) {', + Template.indent([ + ...(wrapPolicyCreationInTryCatch ? ["try {"] : []), + ...[ + `policy = trustedTypes.createPolicy(${JSON.stringify( + trustedTypes.policyName + )}, policy);` + ].map(line => + wrapPolicyCreationInTryCatch ? Template.indent(line) : line + ), + ...(wrapPolicyCreationInTryCatch + ? [ + "} catch (e) {", + Template.indent([ + `console.warn('Could not create trusted-types policy ${JSON.stringify( + trustedTypes.policyName + )}');` + ]), + "}" + ] + : []) + ]), + "}" + ] + : []) + ]), + "}", + "return policy;" + ])};` + ]); + } +} + +module.exports = GetTrustedTypesPolicyRuntimeModule; diff --git a/webpack-lib/lib/runtime/GlobalRuntimeModule.js b/webpack-lib/lib/runtime/GlobalRuntimeModule.js new file mode 100644 index 00000000000..89e556c0858 --- /dev/null +++ b/webpack-lib/lib/runtime/GlobalRuntimeModule.js @@ -0,0 +1,47 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +class GlobalRuntimeModule extends RuntimeModule { + constructor() { + super("global"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return Template.asString([ + `${RuntimeGlobals.global} = (function() {`, + Template.indent([ + "if (typeof globalThis === 'object') return globalThis;", + "try {", + Template.indent( + // This works in non-strict mode + // or + // This works if eval is allowed (see CSP) + "return this || new Function('return this')();" + ), + "} catch (e) {", + Template.indent( + // This works if the window reference is available + "if (typeof window === 'object') return window;" + ), + "}" + // It can still be `undefined`, but nothing to do about it... + // We return `undefined`, instead of nothing here, so it's + // easier to handle this case: + // if (!global) { … } + ]), + "})();" + ]); + } +} + +module.exports = GlobalRuntimeModule; diff --git a/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js b/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js new file mode 100644 index 00000000000..78bf3afeb95 --- /dev/null +++ b/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sergey Melyukov @smelukov +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ + +class HasOwnPropertyRuntimeModule extends RuntimeModule { + constructor() { + super("hasOwnProperty shorthand"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + + return Template.asString([ + `${RuntimeGlobals.hasOwnProperty} = ${runtimeTemplate.returningFunction( + "Object.prototype.hasOwnProperty.call(obj, prop)", + "obj, prop" + )}` + ]); + } +} + +module.exports = HasOwnPropertyRuntimeModule; diff --git a/webpack-lib/lib/runtime/HelperRuntimeModule.js b/webpack-lib/lib/runtime/HelperRuntimeModule.js new file mode 100644 index 00000000000..012916c9228 --- /dev/null +++ b/webpack-lib/lib/runtime/HelperRuntimeModule.js @@ -0,0 +1,18 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeModule = require("../RuntimeModule"); + +class HelperRuntimeModule extends RuntimeModule { + /** + * @param {string} name a readable name + */ + constructor(name) { + super(name); + } +} + +module.exports = HelperRuntimeModule; diff --git a/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js b/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js new file mode 100644 index 00000000000..b6b2f3e381c --- /dev/null +++ b/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js @@ -0,0 +1,174 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} LoadScriptCompilationHooks + * @property {SyncWaterfallHook<[string, Chunk]>} createScript + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class LoadScriptRuntimeModule extends HelperRuntimeModule { + /** + * @param {Compilation} compilation the compilation + * @returns {LoadScriptCompilationHooks} 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 = { + createScript: new SyncWaterfallHook(["source", "chunk"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {boolean=} withCreateScriptUrl use create script url for trusted types + * @param {boolean=} withFetchPriority use `fetchPriority` attribute + */ + constructor(withCreateScriptUrl, withFetchPriority) { + super("load script"); + this._withCreateScriptUrl = withCreateScriptUrl; + this._withFetchPriority = withFetchPriority; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate, outputOptions } = compilation; + const { + scriptType, + chunkLoadTimeout: loadTimeout, + crossOriginLoading, + uniqueName, + charset + } = outputOptions; + const fn = RuntimeGlobals.loadScript; + + const { createScript } = + LoadScriptRuntimeModule.getCompilationHooks(compilation); + + const code = Template.asString([ + "script = document.createElement('script');", + scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "", + charset ? "script.charset = 'utf-8';" : "", + `script.timeout = ${/** @type {number} */ (loadTimeout) / 1000};`, + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + uniqueName + ? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);' + : "", + this._withFetchPriority + ? Template.asString([ + "if(fetchPriority) {", + Template.indent( + 'script.setAttribute("fetchpriority", fetchPriority);' + ), + "}" + ]) + : "", + `script.src = ${ + this._withCreateScriptUrl + ? `${RuntimeGlobals.createScriptUrl}(url)` + : "url" + };`, + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'script.crossOrigin = "use-credentials";' + : Template.asString([ + "if (script.src.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + ), + "}" + ]) + : "" + ]); + + return Template.asString([ + "var inProgress = {};", + uniqueName + ? `var dataWebpackPrefix = ${JSON.stringify(`${uniqueName}:`)};` + : "// data-webpack is not used as build has no uniqueName", + "// loadScript function to load a script via script tag", + `${fn} = ${runtimeTemplate.basicFunction( + `url, done, key, chunkId${ + this._withFetchPriority ? ", fetchPriority" : "" + }`, + [ + "if(inProgress[url]) { inProgress[url].push(done); return; }", + "var script, needAttach;", + "if(key !== undefined) {", + Template.indent([ + 'var scripts = document.getElementsByTagName("script");', + "for(var i = 0; i < scripts.length; i++) {", + Template.indent([ + "var s = scripts[i];", + `if(s.getAttribute("src") == url${ + uniqueName + ? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key' + : "" + }) { script = s; break; }` + ]), + "}" + ]), + "}", + "if(!script) {", + Template.indent([ + "needAttach = true;", + createScript.call(code, /** @type {Chunk} */ (this.chunk)) + ]), + "}", + "inProgress[url] = [done];", + `var onScriptComplete = ${runtimeTemplate.basicFunction( + "prev, event", + Template.asString([ + "// avoid mem leaks in IE.", + "script.onerror = script.onload = null;", + "clearTimeout(timeout);", + "var doneFns = inProgress[url];", + "delete inProgress[url];", + "script.parentNode && script.parentNode.removeChild(script);", + `doneFns && doneFns.forEach(${runtimeTemplate.returningFunction( + "fn(event)", + "fn" + )});`, + "if(prev) return prev(event);" + ]) + )}`, + `var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`, + "script.onerror = onScriptComplete.bind(null, script.onerror);", + "script.onload = onScriptComplete.bind(null, script.onload);", + "needAttach && document.head.appendChild(script);" + ] + )};` + ]); + } +} + +module.exports = LoadScriptRuntimeModule; diff --git a/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js b/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js new file mode 100644 index 00000000000..7b43080d020 --- /dev/null +++ b/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js @@ -0,0 +1,39 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class MakeNamespaceObjectRuntimeModule extends HelperRuntimeModule { + constructor() { + super("make namespace object"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + const fn = RuntimeGlobals.makeNamespaceObject; + return Template.asString([ + "// define __esModule on exports", + `${fn} = ${runtimeTemplate.basicFunction("exports", [ + "if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {", + Template.indent([ + "Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });" + ]), + "}", + "Object.defineProperty(exports, '__esModule', { value: true });" + ])};` + ]); + } +} + +module.exports = MakeNamespaceObjectRuntimeModule; diff --git a/webpack-lib/lib/runtime/NonceRuntimeModule.js b/webpack-lib/lib/runtime/NonceRuntimeModule.js new file mode 100644 index 00000000000..238407c1ba6 --- /dev/null +++ b/webpack-lib/lib/runtime/NonceRuntimeModule.js @@ -0,0 +1,24 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +class NonceRuntimeModule extends RuntimeModule { + constructor() { + super("nonce", RuntimeModule.STAGE_ATTACH); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return `${RuntimeGlobals.scriptNonce} = undefined;`; + } +} + +module.exports = NonceRuntimeModule; diff --git a/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js b/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js new file mode 100644 index 00000000000..2224d02bb4a --- /dev/null +++ b/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js @@ -0,0 +1,78 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Compilation")} Compilation */ + +class OnChunksLoadedRuntimeModule extends RuntimeModule { + constructor() { + super("chunk loaded"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return Template.asString([ + "var deferred = [];", + `${RuntimeGlobals.onChunksLoaded} = ${runtimeTemplate.basicFunction( + "result, chunkIds, fn, priority", + [ + "if(chunkIds) {", + Template.indent([ + "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;" + ]), + "}", + "var notFulfilled = Infinity;", + "for (var i = 0; i < deferred.length; i++) {", + Template.indent([ + runtimeTemplate.destructureArray( + ["chunkIds", "fn", "priority"], + "deferred[i]" + ), + "var fulfilled = true;", + "for (var j = 0; j < chunkIds.length; j++) {", + Template.indent([ + `if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(${ + RuntimeGlobals.onChunksLoaded + }).every(${runtimeTemplate.returningFunction( + `${RuntimeGlobals.onChunksLoaded}[key](chunkIds[j])`, + "key" + )})) {`, + Template.indent(["chunkIds.splice(j--, 1);"]), + "} else {", + Template.indent([ + "fulfilled = false;", + "if(priority < notFulfilled) notFulfilled = priority;" + ]), + "}" + ]), + "}", + "if(fulfilled) {", + Template.indent([ + "deferred.splice(i--, 1)", + "var r = fn();", + "if (r !== undefined) result = r;" + ]), + "}" + ]), + "}", + "return result;" + ] + )};` + ]); + } +} + +module.exports = OnChunksLoadedRuntimeModule; diff --git a/webpack-lib/lib/runtime/PublicPathRuntimeModule.js b/webpack-lib/lib/runtime/PublicPathRuntimeModule.js new file mode 100644 index 00000000000..7ea226161c9 --- /dev/null +++ b/webpack-lib/lib/runtime/PublicPathRuntimeModule.js @@ -0,0 +1,37 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../Compilation")} Compilation */ + +class PublicPathRuntimeModule extends RuntimeModule { + /** + * @param {OutputOptions["publicPath"]} publicPath public path + */ + constructor(publicPath) { + super("publicPath", RuntimeModule.STAGE_BASIC); + this.publicPath = publicPath; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const { publicPath } = this; + const compilation = /** @type {Compilation} */ (this.compilation); + + return `${RuntimeGlobals.publicPath} = ${JSON.stringify( + compilation.getPath(publicPath || "", { + hash: compilation.hash || "XXXX" + }) + )};`; + } +} + +module.exports = PublicPathRuntimeModule; diff --git a/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js b/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js new file mode 100644 index 00000000000..92e32daed98 --- /dev/null +++ b/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js @@ -0,0 +1,44 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const HelperRuntimeModule = require("./HelperRuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class RelativeUrlRuntimeModule extends HelperRuntimeModule { + constructor() { + super("relative url"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return Template.asString([ + `${RuntimeGlobals.relativeUrl} = function RelativeURL(url) {`, + Template.indent([ + 'var realUrl = new URL(url, "x:/");', + "var values = {};", + "for (var key in realUrl) values[key] = realUrl[key];", + "values.href = url;", + 'values.pathname = url.replace(/[?#].*/, "");', + 'values.origin = values.protocol = "";', + `values.toString = values.toJSON = ${runtimeTemplate.returningFunction( + "url" + )};`, + "for (var key in values) Object.defineProperty(this, key, { enumerable: true, configurable: true, value: values[key] });" + ]), + "};", + `${RuntimeGlobals.relativeUrl}.prototype = URL.prototype;` + ]); + } +} + +module.exports = RelativeUrlRuntimeModule; diff --git a/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js b/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js new file mode 100644 index 00000000000..1923bafca7e --- /dev/null +++ b/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js @@ -0,0 +1,32 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ + +class RuntimeIdRuntimeModule extends RuntimeModule { + constructor() { + super("runtimeId"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const runtime = chunk.runtime; + if (typeof runtime !== "string") + throw new Error("RuntimeIdRuntimeModule must be in a single runtime"); + const id = chunkGraph.getRuntimeId(runtime); + return `${RuntimeGlobals.runtimeId} = ${JSON.stringify(id)};`; + } +} + +module.exports = RuntimeIdRuntimeModule; diff --git a/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js b/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js new file mode 100644 index 00000000000..6fc74cde8ce --- /dev/null +++ b/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js @@ -0,0 +1,89 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const StartupChunkDependenciesRuntimeModule = require("./StartupChunkDependenciesRuntimeModule"); +const StartupEntrypointRuntimeModule = require("./StartupEntrypointRuntimeModule"); + +/** @typedef {import("../../declarations/WebpackOptions").ChunkLoadingType} ChunkLoadingType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} Options + * @property {ChunkLoadingType} chunkLoading + * @property {boolean=} asyncChunkLoading + */ + +class StartupChunkDependenciesPlugin { + /** + * @param {Options} options options + */ + constructor(options) { + this.chunkLoading = options.chunkLoading; + this.asyncChunkLoading = + typeof options.asyncChunkLoading === "boolean" + ? options.asyncChunkLoading + : true; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "StartupChunkDependenciesPlugin", + compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk chunk to check + * @returns {boolean} true, when the plugin is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === this.chunkLoading; + }; + compilation.hooks.additionalTreeRuntimeRequirements.tap( + "StartupChunkDependenciesPlugin", + (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if (chunkGraph.hasChunkEntryDependentChunks(chunk)) { + set.add(RuntimeGlobals.startup); + set.add(RuntimeGlobals.ensureChunk); + set.add(RuntimeGlobals.ensureChunkIncludeEntries); + compilation.addRuntimeModule( + chunk, + new StartupChunkDependenciesRuntimeModule( + this.asyncChunkLoading + ) + ); + } + } + ); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.startupEntrypoint) + .tap("StartupChunkDependenciesPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.require); + set.add(RuntimeGlobals.ensureChunk); + set.add(RuntimeGlobals.ensureChunkIncludeEntries); + compilation.addRuntimeModule( + chunk, + new StartupEntrypointRuntimeModule(this.asyncChunkLoading) + ); + }); + } + ); + } +} + +module.exports = StartupChunkDependenciesPlugin; diff --git a/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js b/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js new file mode 100644 index 00000000000..da2ec7548eb --- /dev/null +++ b/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js @@ -0,0 +1,75 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ + +class StartupChunkDependenciesRuntimeModule extends RuntimeModule { + /** + * @param {boolean} asyncChunkLoading use async chunk loading + */ + constructor(asyncChunkLoading) { + super("startup chunk dependencies", RuntimeModule.STAGE_TRIGGER); + this.asyncChunkLoading = asyncChunkLoading; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const chunkIds = Array.from( + chunkGraph.getChunkEntryDependentChunksIterable(chunk) + ).map(chunk => chunk.id); + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return Template.asString([ + `var next = ${RuntimeGlobals.startup};`, + `${RuntimeGlobals.startup} = ${runtimeTemplate.basicFunction( + "", + !this.asyncChunkLoading + ? chunkIds + .map( + id => `${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)});` + ) + .concat("return next();") + : chunkIds.length === 1 + ? `return ${RuntimeGlobals.ensureChunk}(${JSON.stringify( + chunkIds[0] + )}).then(next);` + : chunkIds.length > 2 + ? [ + // using map is shorter for 3 or more chunks + `return Promise.all(${JSON.stringify(chunkIds)}.map(${ + RuntimeGlobals.ensureChunk + }, ${RuntimeGlobals.require})).then(next);` + ] + : [ + // calling ensureChunk directly is shorter for 0 - 2 chunks + "return Promise.all([", + Template.indent( + chunkIds + .map( + id => + `${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)})` + ) + .join(",\n") + ), + "]).then(next);" + ] + )};` + ]); + } +} + +module.exports = StartupChunkDependenciesRuntimeModule; diff --git a/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js b/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js new file mode 100644 index 00000000000..5133767dab3 --- /dev/null +++ b/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js @@ -0,0 +1,54 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../MainTemplate")} MainTemplate */ + +class StartupEntrypointRuntimeModule extends RuntimeModule { + /** + * @param {boolean} asyncChunkLoading use async chunk loading + */ + constructor(asyncChunkLoading) { + super("startup entrypoint"); + this.asyncChunkLoading = asyncChunkLoading; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { runtimeTemplate } = compilation; + return `${ + RuntimeGlobals.startupEntrypoint + } = ${runtimeTemplate.basicFunction("result, chunkIds, fn", [ + "// arguments: chunkIds, moduleId are deprecated", + "var moduleId = chunkIds;", + `if(!fn) chunkIds = result, fn = ${runtimeTemplate.returningFunction( + `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)` + )};`, + ...(this.asyncChunkLoading + ? [ + `return Promise.all(chunkIds.map(${RuntimeGlobals.ensureChunk}, ${ + RuntimeGlobals.require + })).then(${runtimeTemplate.basicFunction("", [ + "var r = fn();", + "return r === undefined ? result : r;" + ])})` + ] + : [ + `chunkIds.map(${RuntimeGlobals.ensureChunk}, ${RuntimeGlobals.require})`, + "var r = fn();", + "return r === undefined ? result : r;" + ]) + ])}`; + } +} + +module.exports = StartupEntrypointRuntimeModule; diff --git a/webpack-lib/lib/runtime/SystemContextRuntimeModule.js b/webpack-lib/lib/runtime/SystemContextRuntimeModule.js new file mode 100644 index 00000000000..b7663ffde1c --- /dev/null +++ b/webpack-lib/lib/runtime/SystemContextRuntimeModule.js @@ -0,0 +1,25 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); + +/** @typedef {import("../Compilation")} Compilation */ + +class SystemContextRuntimeModule extends RuntimeModule { + constructor() { + super("__system_context__"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + return `${RuntimeGlobals.systemContext} = __system_context__;`; + } +} + +module.exports = SystemContextRuntimeModule; diff --git a/webpack-lib/lib/schemes/DataUriPlugin.js b/webpack-lib/lib/schemes/DataUriPlugin.js new file mode 100644 index 00000000000..06f0d0feca6 --- /dev/null +++ b/webpack-lib/lib/schemes/DataUriPlugin.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NormalModule = require("../NormalModule"); + +/** @typedef {import("../Compiler")} Compiler */ + +// data URL scheme: "data:text/javascript;charset=utf-8;base64,some-string" +// http://www.ietf.org/rfc/rfc2397.txt +const URIRegEx = /^data:([^;,]+)?((?:;[^;,]+)*?)(?:;(base64)?)?,(.*)$/i; + +/** + * @param {string} uri data URI + * @returns {Buffer | null} decoded data + */ +const decodeDataURI = uri => { + const match = URIRegEx.exec(uri); + if (!match) return null; + + const isBase64 = match[3]; + const body = match[4]; + + if (isBase64) { + return Buffer.from(body, "base64"); + } + + // CSS allows to use `data:image/svg+xml;utf8,` + // so we return original body if we can't `decodeURIComponent` + try { + return Buffer.from(decodeURIComponent(body), "ascii"); + } catch (_) { + return Buffer.from(body, "ascii"); + } +}; + +class DataUriPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "DataUriPlugin", + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.resolveForScheme + .for("data") + .tap("DataUriPlugin", resourceData => { + const match = URIRegEx.exec(resourceData.resource); + if (match) { + resourceData.data.mimetype = match[1] || ""; + resourceData.data.parameters = match[2] || ""; + resourceData.data.encoding = match[3] || false; + resourceData.data.encodedContent = match[4] || ""; + } + }); + NormalModule.getCompilationHooks(compilation) + .readResourceForScheme.for("data") + .tap("DataUriPlugin", resource => decodeDataURI(resource)); + } + ); + } +} + +module.exports = DataUriPlugin; diff --git a/webpack-lib/lib/schemes/FileUriPlugin.js b/webpack-lib/lib/schemes/FileUriPlugin.js new file mode 100644 index 00000000000..453abbd3b13 --- /dev/null +++ b/webpack-lib/lib/schemes/FileUriPlugin.js @@ -0,0 +1,49 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { URL, fileURLToPath } = require("url"); +const { NormalModule } = require(".."); + +/** @typedef {import("../Compiler")} Compiler */ + +class FileUriPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + "FileUriPlugin", + (compilation, { normalModuleFactory }) => { + normalModuleFactory.hooks.resolveForScheme + .for("file") + .tap("FileUriPlugin", resourceData => { + const url = new URL(resourceData.resource); + const path = fileURLToPath(url); + const query = url.search; + const fragment = url.hash; + resourceData.path = path; + resourceData.query = query; + resourceData.fragment = fragment; + resourceData.resource = path + query + fragment; + return true; + }); + const hooks = NormalModule.getCompilationHooks(compilation); + hooks.readResource + .for(undefined) + .tapAsync("FileUriPlugin", (loaderContext, callback) => { + const { resourcePath } = loaderContext; + loaderContext.addDependency(resourcePath); + loaderContext.fs.readFile(resourcePath, callback); + }); + } + ); + } +} + +module.exports = FileUriPlugin; diff --git a/webpack-lib/lib/schemes/HttpUriPlugin.js b/webpack-lib/lib/schemes/HttpUriPlugin.js new file mode 100644 index 00000000000..510b189f242 --- /dev/null +++ b/webpack-lib/lib/schemes/HttpUriPlugin.js @@ -0,0 +1,1271 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const EventEmitter = require("events"); +const { extname, basename } = require("path"); +const { URL } = require("url"); +const { createGunzip, createBrotliDecompress, createInflate } = require("zlib"); +const NormalModule = require("../NormalModule"); +const createSchemaValidation = require("../util/create-schema-validation"); +const createHash = require("../util/createHash"); +const { mkdirp, dirname, join } = require("../util/fs"); +const memoize = require("../util/memoize"); + +/** @typedef {import("http").IncomingMessage} IncomingMessage */ +/** @typedef {import("http").RequestOptions} RequestOptions */ +/** @typedef {import("net").Socket} Socket */ +/** @typedef {import("stream").Readable} Readable */ +/** @typedef {import("../../declarations/plugins/schemes/HttpUriPlugin").HttpUriPluginOptions} HttpUriPluginOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ +/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ + +const getHttp = memoize(() => require("http")); +const getHttps = memoize(() => require("https")); + +/** + * @param {typeof import("http") | typeof import("https")} request request + * @param {string | { toString: () => string } | undefined} proxy proxy + * @returns {function(URL, RequestOptions, function(IncomingMessage): void): EventEmitter} fn + */ +const proxyFetch = (request, proxy) => (url, options, callback) => { + const eventEmitter = new EventEmitter(); + + /** + * @param {Socket=} socket socket + * @returns {void} + */ + const doRequest = socket => { + request + .get(url, { ...options, ...(socket && { socket }) }, callback) + .on("error", eventEmitter.emit.bind(eventEmitter, "error")); + }; + + if (proxy) { + const { hostname: host, port } = new URL(proxy); + + getHttp() + .request({ + host, // IP address of proxy server + port, // port of proxy server + method: "CONNECT", + path: url.host + }) + .on("connect", (res, socket) => { + if (res.statusCode === 200) { + // connected to proxy server + doRequest(socket); + } + }) + .on("error", err => { + eventEmitter.emit( + "error", + new Error( + `Failed to connect to proxy server "${proxy}": ${err.message}` + ) + ); + }) + .end(); + } else { + doRequest(); + } + + return eventEmitter; +}; + +/** @typedef {() => void} InProgressWriteItem */ +/** @type {InProgressWriteItem[] | undefined} */ +let inProgressWrite; + +const validate = createSchemaValidation( + require("../../schemas/plugins/schemes/HttpUriPlugin.check.js"), + () => require("../../schemas/plugins/schemes/HttpUriPlugin.json"), + { + name: "Http Uri Plugin", + baseDataPath: "options" + } +); + +/** + * @param {string} str path + * @returns {string} safe path + */ +const toSafePath = str => + str + .replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "") + .replace(/[^a-zA-Z0-9._-]+/g, "_"); + +/** + * @param {Buffer} content content + * @returns {string} integrity + */ +const computeIntegrity = content => { + const hash = createHash("sha512"); + hash.update(content); + const integrity = `sha512-${hash.digest("base64")}`; + return integrity; +}; + +/** + * @param {Buffer} content content + * @param {string} integrity integrity + * @returns {boolean} true, if integrity matches + */ +const verifyIntegrity = (content, integrity) => { + if (integrity === "ignore") return true; + return computeIntegrity(content) === integrity; +}; + +/** + * @param {string} str input + * @returns {Record} parsed + */ +const parseKeyValuePairs = str => { + /** @type {Record} */ + const result = {}; + for (const item of str.split(",")) { + const i = item.indexOf("="); + if (i >= 0) { + const key = item.slice(0, i).trim(); + const value = item.slice(i + 1).trim(); + result[key] = value; + } else { + const key = item.trim(); + if (!key) continue; + result[key] = key; + } + } + return result; +}; + +/** + * @param {string | undefined} cacheControl Cache-Control header + * @param {number} requestTime timestamp of request + * @returns {{storeCache: boolean, storeLock: boolean, validUntil: number}} Logic for storing in cache and lockfile cache + */ +const parseCacheControl = (cacheControl, requestTime) => { + // When false resource is not stored in cache + let storeCache = true; + // When false resource is not stored in lockfile cache + let storeLock = true; + // Resource is only revalidated, after that timestamp and when upgrade is chosen + let validUntil = 0; + if (cacheControl) { + const parsed = parseKeyValuePairs(cacheControl); + if (parsed["no-cache"]) storeCache = storeLock = false; + if (parsed["max-age"] && !Number.isNaN(Number(parsed["max-age"]))) { + validUntil = requestTime + Number(parsed["max-age"]) * 1000; + } + if (parsed["must-revalidate"]) validUntil = 0; + } + return { + storeLock, + storeCache, + validUntil + }; +}; + +/** + * @typedef {object} LockfileEntry + * @property {string} resolved + * @property {string} integrity + * @property {string} contentType + */ + +/** + * @param {LockfileEntry} a first lockfile entry + * @param {LockfileEntry} b second lockfile entry + * @returns {boolean} true when equal, otherwise false + */ +const areLockfileEntriesEqual = (a, b) => + a.resolved === b.resolved && + a.integrity === b.integrity && + a.contentType === b.contentType; + +/** + * @param {LockfileEntry} entry lockfile entry + * @returns {`resolved: ${string}, integrity: ${string}, contentType: ${*}`} stringified entry + */ +const entryToString = entry => + `resolved: ${entry.resolved}, integrity: ${entry.integrity}, contentType: ${entry.contentType}`; + +class Lockfile { + constructor() { + this.version = 1; + /** @type {Map} */ + this.entries = new Map(); + } + + /** + * @param {string} content content of the lockfile + * @returns {Lockfile} lockfile + */ + static parse(content) { + // TODO handle merge conflicts + const data = JSON.parse(content); + if (data.version !== 1) + throw new Error(`Unsupported lockfile version ${data.version}`); + const lockfile = new Lockfile(); + for (const key of Object.keys(data)) { + if (key === "version") continue; + const entry = data[key]; + lockfile.entries.set( + key, + typeof entry === "string" + ? entry + : { + resolved: key, + ...entry + } + ); + } + return lockfile; + } + + /** + * @returns {string} stringified lockfile + */ + toString() { + let str = "{\n"; + const entries = Array.from(this.entries).sort(([a], [b]) => + a < b ? -1 : 1 + ); + for (const [key, entry] of entries) { + if (typeof entry === "string") { + str += ` ${JSON.stringify(key)}: ${JSON.stringify(entry)},\n`; + } else { + str += ` ${JSON.stringify(key)}: { `; + if (entry.resolved !== key) + str += `"resolved": ${JSON.stringify(entry.resolved)}, `; + str += `"integrity": ${JSON.stringify( + entry.integrity + )}, "contentType": ${JSON.stringify(entry.contentType)} },\n`; + } + } + str += ` "version": ${this.version}\n}\n`; + return str; + } +} + +/** + * @template R + * @param {function(function(Error | null, R=): void): void} fn function + * @returns {function(function(Error | null, R=): void): void} cached function + */ +const cachedWithoutKey = fn => { + let inFlight = false; + /** @type {Error | undefined} */ + let cachedError; + /** @type {R | undefined} */ + let cachedResult; + /** @type {(function(Error| null, R=): void)[] | undefined} */ + let cachedCallbacks; + return callback => { + if (inFlight) { + if (cachedResult !== undefined) return callback(null, cachedResult); + if (cachedError !== undefined) return callback(cachedError); + if (cachedCallbacks === undefined) cachedCallbacks = [callback]; + else cachedCallbacks.push(callback); + return; + } + inFlight = true; + fn((err, result) => { + if (err) cachedError = err; + else cachedResult = result; + const callbacks = cachedCallbacks; + cachedCallbacks = undefined; + callback(err, result); + if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); + }); + }; +}; + +/** + * @template T + * @template R + * @param {function(T, function(Error | null, R=): void): void} fn function + * @param {function(T, function(Error | null, R=): void): void=} forceFn function for the second try + * @returns {(function(T, function(Error | null, R=): void): void) & { force: function(T, function(Error | null, R=): void): void }} cached function + */ +const cachedWithKey = (fn, forceFn = fn) => { + /** + * @template R + * @typedef {{ result?: R, error?: Error, callbacks?: (function(Error | null, R=): void)[], force?: true }} CacheEntry + */ + /** @type {Map>} */ + const cache = new Map(); + /** + * @param {T} arg arg + * @param {function(Error | null, R=): void} callback callback + * @returns {void} + */ + const resultFn = (arg, callback) => { + const cacheEntry = cache.get(arg); + if (cacheEntry !== undefined) { + if (cacheEntry.result !== undefined) + return callback(null, cacheEntry.result); + if (cacheEntry.error !== undefined) return callback(cacheEntry.error); + if (cacheEntry.callbacks === undefined) cacheEntry.callbacks = [callback]; + else cacheEntry.callbacks.push(callback); + return; + } + /** @type {CacheEntry} */ + const newCacheEntry = { + result: undefined, + error: undefined, + callbacks: undefined + }; + cache.set(arg, newCacheEntry); + fn(arg, (err, result) => { + if (err) newCacheEntry.error = err; + else newCacheEntry.result = result; + const callbacks = newCacheEntry.callbacks; + newCacheEntry.callbacks = undefined; + callback(err, result); + if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); + }); + }; + /** + * @param {T} arg arg + * @param {function(Error | null, R=): void} callback callback + * @returns {void} + */ + resultFn.force = (arg, callback) => { + const cacheEntry = cache.get(arg); + if (cacheEntry !== undefined && cacheEntry.force) { + if (cacheEntry.result !== undefined) + return callback(null, cacheEntry.result); + if (cacheEntry.error !== undefined) return callback(cacheEntry.error); + if (cacheEntry.callbacks === undefined) cacheEntry.callbacks = [callback]; + else cacheEntry.callbacks.push(callback); + return; + } + /** @type {CacheEntry} */ + const newCacheEntry = { + result: undefined, + error: undefined, + callbacks: undefined, + force: true + }; + cache.set(arg, newCacheEntry); + forceFn(arg, (err, result) => { + if (err) newCacheEntry.error = err; + else newCacheEntry.result = result; + const callbacks = newCacheEntry.callbacks; + newCacheEntry.callbacks = undefined; + callback(err, result); + if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); + }); + }; + return resultFn; +}; + +/** + * @typedef {object} LockfileCache + * @property {Lockfile} lockfile lockfile + * @property {Snapshot} snapshot snapshot + */ + +/** + * @typedef {object} ResolveContentResult + * @property {LockfileEntry} entry lockfile entry + * @property {Buffer} content content + * @property {boolean} storeLock need store lockfile + */ + +/** @typedef {{ storeCache: boolean, storeLock: boolean, validUntil: number, etag: string | undefined, fresh: boolean }} FetchResultMeta */ +/** @typedef {FetchResultMeta & { location: string }} RedirectFetchResult */ +/** @typedef {FetchResultMeta & { entry: LockfileEntry, content: Buffer }} ContentFetchResult */ +/** @typedef {RedirectFetchResult | ContentFetchResult} FetchResult */ + +class HttpUriPlugin { + /** + * @param {HttpUriPluginOptions} options options + */ + constructor(options) { + validate(options); + this._lockfileLocation = options.lockfileLocation; + this._cacheLocation = options.cacheLocation; + this._upgrade = options.upgrade; + this._frozen = options.frozen; + this._allowedUris = options.allowedUris; + this._proxy = options.proxy; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const proxy = + this._proxy || process.env.http_proxy || process.env.HTTP_PROXY; + const schemes = [ + { + scheme: "http", + fetch: proxyFetch(getHttp(), proxy) + }, + { + scheme: "https", + fetch: proxyFetch(getHttps(), proxy) + } + ]; + /** @type {LockfileCache} */ + let lockfileCache; + compiler.hooks.compilation.tap( + "HttpUriPlugin", + (compilation, { normalModuleFactory }) => { + const intermediateFs = + /** @type {IntermediateFileSystem} */ + (compiler.intermediateFileSystem); + const fs = compilation.inputFileSystem; + const cache = compilation.getCache("webpack.HttpUriPlugin"); + const logger = compilation.getLogger("webpack.HttpUriPlugin"); + /** @type {string} */ + const lockfileLocation = + this._lockfileLocation || + join( + intermediateFs, + compiler.context, + compiler.name + ? `${toSafePath(compiler.name)}.webpack.lock` + : "webpack.lock" + ); + /** @type {string | false} */ + const cacheLocation = + this._cacheLocation !== undefined + ? this._cacheLocation + : `${lockfileLocation}.data`; + const upgrade = this._upgrade || false; + const frozen = this._frozen || false; + const hashFunction = "sha512"; + const hashDigest = "hex"; + const hashDigestLength = 20; + const allowedUris = this._allowedUris; + + let warnedAboutEol = false; + + /** @type {Map} */ + const cacheKeyCache = new Map(); + /** + * @param {string} url the url + * @returns {string} the key + */ + const getCacheKey = url => { + const cachedResult = cacheKeyCache.get(url); + if (cachedResult !== undefined) return cachedResult; + const result = _getCacheKey(url); + cacheKeyCache.set(url, result); + return result; + }; + + /** + * @param {string} url the url + * @returns {string} the key + */ + const _getCacheKey = url => { + const parsedUrl = new URL(url); + const folder = toSafePath(parsedUrl.origin); + const name = toSafePath(parsedUrl.pathname); + const query = toSafePath(parsedUrl.search); + let ext = extname(name); + if (ext.length > 20) ext = ""; + const basename = ext ? name.slice(0, -ext.length) : name; + const hash = createHash(hashFunction); + hash.update(url); + const digest = hash.digest(hashDigest).slice(0, hashDigestLength); + return `${folder.slice(-50)}/${`${basename}${ + query ? `_${query}` : "" + }`.slice(0, 150)}_${digest}${ext}`; + }; + + const getLockfile = cachedWithoutKey( + /** + * @param {function(Error | null, Lockfile=): void} callback callback + * @returns {void} + */ + callback => { + const readLockfile = () => { + intermediateFs.readFile(lockfileLocation, (err, buffer) => { + if (err && err.code !== "ENOENT") { + compilation.missingDependencies.add(lockfileLocation); + return callback(err); + } + compilation.fileDependencies.add(lockfileLocation); + compilation.fileSystemInfo.createSnapshot( + compiler.fsStartTime, + buffer ? [lockfileLocation] : [], + [], + buffer ? [] : [lockfileLocation], + { timestamp: true }, + (err, s) => { + if (err) return callback(err); + const lockfile = buffer + ? Lockfile.parse(buffer.toString("utf-8")) + : new Lockfile(); + lockfileCache = { + lockfile, + snapshot: /** @type {Snapshot} */ (s) + }; + callback(null, lockfile); + } + ); + }); + }; + if (lockfileCache) { + compilation.fileSystemInfo.checkSnapshotValid( + lockfileCache.snapshot, + (err, valid) => { + if (err) return callback(err); + if (!valid) return readLockfile(); + callback(null, lockfileCache.lockfile); + } + ); + } else { + readLockfile(); + } + } + ); + + /** @typedef {Map} LockfileUpdates */ + + /** @type {LockfileUpdates | undefined} */ + let lockfileUpdates; + + /** + * @param {Lockfile} lockfile lockfile instance + * @param {string} url url to store + * @param {LockfileEntry | "ignore" | "no-cache"} entry lockfile entry + */ + const storeLockEntry = (lockfile, url, entry) => { + const oldEntry = lockfile.entries.get(url); + if (lockfileUpdates === undefined) lockfileUpdates = new Map(); + lockfileUpdates.set(url, entry); + lockfile.entries.set(url, entry); + if (!oldEntry) { + logger.log(`${url} added to lockfile`); + } else if (typeof oldEntry === "string") { + if (typeof entry === "string") { + logger.log(`${url} updated in lockfile: ${oldEntry} -> ${entry}`); + } else { + logger.log( + `${url} updated in lockfile: ${oldEntry} -> ${entry.resolved}` + ); + } + } else if (typeof entry === "string") { + logger.log( + `${url} updated in lockfile: ${oldEntry.resolved} -> ${entry}` + ); + } else if (oldEntry.resolved !== entry.resolved) { + logger.log( + `${url} updated in lockfile: ${oldEntry.resolved} -> ${entry.resolved}` + ); + } else if (oldEntry.integrity !== entry.integrity) { + logger.log(`${url} updated in lockfile: content changed`); + } else if (oldEntry.contentType !== entry.contentType) { + logger.log( + `${url} updated in lockfile: ${oldEntry.contentType} -> ${entry.contentType}` + ); + } else { + logger.log(`${url} updated in lockfile`); + } + }; + + /** + * @param {Lockfile} lockfile lockfile + * @param {string} url url + * @param {ResolveContentResult} result result + * @param {function(Error | null, ResolveContentResult=): void} callback callback + * @returns {void} + */ + const storeResult = (lockfile, url, result, callback) => { + if (result.storeLock) { + storeLockEntry(lockfile, url, result.entry); + if (!cacheLocation || !result.content) + return callback(null, result); + const key = getCacheKey(result.entry.resolved); + const filePath = join(intermediateFs, cacheLocation, key); + mkdirp(intermediateFs, dirname(intermediateFs, filePath), err => { + if (err) return callback(err); + intermediateFs.writeFile(filePath, result.content, err => { + if (err) return callback(err); + callback(null, result); + }); + }); + } else { + storeLockEntry(lockfile, url, "no-cache"); + callback(null, result); + } + }; + + for (const { scheme, fetch } of schemes) { + /** + * @param {string} url URL + * @param {string | null} integrity integrity + * @param {function(Error | null, ResolveContentResult=): void} callback callback + */ + const resolveContent = (url, integrity, callback) => { + /** + * @param {Error | null} err error + * @param {TODO} result result result + * @returns {void} + */ + const handleResult = (err, result) => { + if (err) return callback(err); + if ("location" in result) { + return resolveContent( + result.location, + integrity, + (err, innerResult) => { + if (err) return callback(err); + const { entry, content, storeLock } = + /** @type {ResolveContentResult} */ (innerResult); + callback(null, { + entry, + content, + storeLock: storeLock && result.storeLock + }); + } + ); + } + if ( + !result.fresh && + integrity && + result.entry.integrity !== integrity && + !verifyIntegrity(result.content, integrity) + ) { + return fetchContent.force(url, handleResult); + } + return callback(null, { + entry: result.entry, + content: result.content, + storeLock: result.storeLock + }); + }; + fetchContent(url, handleResult); + }; + + /** + * @param {string} url URL + * @param {FetchResult | RedirectFetchResult | undefined} cachedResult result from cache + * @param {function(Error | null, FetchResult=): void} callback callback + * @returns {void} + */ + const fetchContentRaw = (url, cachedResult, callback) => { + const requestTime = Date.now(); + fetch( + new URL(url), + { + headers: { + "accept-encoding": "gzip, deflate, br", + "user-agent": "webpack", + "if-none-match": /** @type {TODO} */ ( + cachedResult ? cachedResult.etag || null : null + ) + } + }, + res => { + const etag = res.headers.etag; + const location = res.headers.location; + const cacheControl = res.headers["cache-control"]; + const { storeLock, storeCache, validUntil } = parseCacheControl( + cacheControl, + requestTime + ); + /** + * @param {Partial> & (Pick | Pick)} partialResult result + * @returns {void} + */ + const finishWith = partialResult => { + if ("location" in partialResult) { + logger.debug( + `GET ${url} [${res.statusCode}] -> ${partialResult.location}` + ); + } else { + logger.debug( + `GET ${url} [${res.statusCode}] ${Math.ceil( + partialResult.content.length / 1024 + )} kB${!storeLock ? " no-cache" : ""}` + ); + } + const result = { + ...partialResult, + fresh: true, + storeLock, + storeCache, + validUntil, + etag + }; + if (!storeCache) { + logger.log( + `${url} can't be stored in cache, due to Cache-Control header: ${cacheControl}` + ); + return callback(null, result); + } + cache.store( + url, + null, + { + ...result, + fresh: false + }, + err => { + if (err) { + logger.warn( + `${url} can't be stored in cache: ${err.message}` + ); + logger.debug(err.stack); + } + callback(null, result); + } + ); + }; + if (res.statusCode === 304) { + const result = /** @type {FetchResult} */ (cachedResult); + if ( + result.validUntil < validUntil || + result.storeLock !== storeLock || + result.storeCache !== storeCache || + result.etag !== etag + ) { + return finishWith(result); + } + logger.debug(`GET ${url} [${res.statusCode}] (unchanged)`); + return callback(null, { ...result, fresh: true }); + } + if ( + location && + res.statusCode && + res.statusCode >= 301 && + res.statusCode <= 308 + ) { + const result = { + location: new URL(location, url).href + }; + if ( + !cachedResult || + !("location" in cachedResult) || + cachedResult.location !== result.location || + cachedResult.validUntil < validUntil || + cachedResult.storeLock !== storeLock || + cachedResult.storeCache !== storeCache || + cachedResult.etag !== etag + ) { + return finishWith(result); + } + logger.debug(`GET ${url} [${res.statusCode}] (unchanged)`); + return callback(null, { + ...result, + fresh: true, + storeLock, + storeCache, + validUntil, + etag + }); + } + const contentType = res.headers["content-type"] || ""; + /** @type {Buffer[]} */ + const bufferArr = []; + + const contentEncoding = res.headers["content-encoding"]; + /** @type {Readable} */ + let stream = res; + if (contentEncoding === "gzip") { + stream = stream.pipe(createGunzip()); + } else if (contentEncoding === "br") { + stream = stream.pipe(createBrotliDecompress()); + } else if (contentEncoding === "deflate") { + stream = stream.pipe(createInflate()); + } + + stream.on("data", chunk => { + bufferArr.push(chunk); + }); + + stream.on("end", () => { + if (!res.complete) { + logger.log(`GET ${url} [${res.statusCode}] (terminated)`); + return callback(new Error(`${url} request was terminated`)); + } + + const content = Buffer.concat(bufferArr); + + if (res.statusCode !== 200) { + logger.log(`GET ${url} [${res.statusCode}]`); + return callback( + new Error( + `${url} request status code = ${ + res.statusCode + }\n${content.toString("utf-8")}` + ) + ); + } + + const integrity = computeIntegrity(content); + const entry = { resolved: url, integrity, contentType }; + + finishWith({ + entry, + content + }); + }); + } + ).on("error", err => { + logger.log(`GET ${url} (error)`); + err.message += `\nwhile fetching ${url}`; + callback(err); + }); + }; + + const fetchContent = cachedWithKey( + /** + * @param {string} url URL + * @param {function(Error | null, { validUntil: number, etag?: string, entry: LockfileEntry, content: Buffer, fresh: boolean } | { validUntil: number, etag?: string, location: string, fresh: boolean }=): void} callback callback + * @returns {void} + */ + (url, callback) => { + cache.get(url, null, (err, cachedResult) => { + if (err) return callback(err); + if (cachedResult) { + const isValid = cachedResult.validUntil >= Date.now(); + if (isValid) return callback(null, cachedResult); + } + fetchContentRaw(url, cachedResult, callback); + }); + }, + (url, callback) => fetchContentRaw(url, undefined, callback) + ); + + /** + * @param {string} uri uri + * @returns {boolean} true when allowed, otherwise false + */ + const isAllowed = uri => { + for (const allowed of allowedUris) { + if (typeof allowed === "string") { + if (uri.startsWith(allowed)) return true; + } else if (typeof allowed === "function") { + if (allowed(uri)) return true; + } else if (allowed.test(uri)) { + return true; + } + } + return false; + }; + + /** @typedef {{ entry: LockfileEntry, content: Buffer }} Info */ + + const getInfo = cachedWithKey( + /** + * @param {string} url the url + * @param {function(Error | null, Info=): void} callback callback + * @returns {void} + */ + // eslint-disable-next-line no-loop-func + (url, callback) => { + if (!isAllowed(url)) { + return callback( + new Error( + `${url} doesn't match the allowedUris policy. These URIs are allowed:\n${allowedUris + .map(uri => ` - ${uri}`) + .join("\n")}` + ) + ); + } + getLockfile((err, _lockfile) => { + if (err) return callback(err); + const lockfile = /** @type {Lockfile} */ (_lockfile); + const entryOrString = lockfile.entries.get(url); + if (!entryOrString) { + if (frozen) { + return callback( + new Error( + `${url} has no lockfile entry and lockfile is frozen` + ) + ); + } + resolveContent(url, null, (err, result) => { + if (err) return callback(err); + storeResult( + /** @type {Lockfile} */ + (lockfile), + url, + /** @type {ResolveContentResult} */ + (result), + callback + ); + }); + return; + } + if (typeof entryOrString === "string") { + const entryTag = entryOrString; + resolveContent(url, null, (err, _result) => { + if (err) return callback(err); + const result = + /** @type {ResolveContentResult} */ + (_result); + if (!result.storeLock || entryTag === "ignore") + return callback(null, result); + if (frozen) { + return callback( + new Error( + `${url} used to have ${entryTag} lockfile entry and has content now, but lockfile is frozen` + ) + ); + } + if (!upgrade) { + return callback( + new Error( + `${url} used to have ${entryTag} lockfile entry and has content now. +This should be reflected in the lockfile, so this lockfile entry must be upgraded, but upgrading is not enabled. +Remove this line from the lockfile to force upgrading.` + ) + ); + } + storeResult(lockfile, url, result, callback); + }); + return; + } + let entry = entryOrString; + /** + * @param {Buffer=} lockedContent locked content + */ + const doFetch = lockedContent => { + resolveContent(url, entry.integrity, (err, _result) => { + if (err) { + if (lockedContent) { + logger.warn( + `Upgrade request to ${url} failed: ${err.message}` + ); + logger.debug(err.stack); + return callback(null, { + entry, + content: lockedContent + }); + } + return callback(err); + } + const result = + /** @type {ResolveContentResult} */ + (_result); + if (!result.storeLock) { + // When the lockfile entry should be no-cache + // we need to update the lockfile + if (frozen) { + return callback( + new Error( + `${url} has a lockfile entry and is no-cache now, but lockfile is frozen\nLockfile: ${entryToString( + entry + )}` + ) + ); + } + storeResult(lockfile, url, result, callback); + return; + } + if (!areLockfileEntriesEqual(result.entry, entry)) { + // When the lockfile entry is outdated + // we need to update the lockfile + if (frozen) { + return callback( + new Error( + `${url} has an outdated lockfile entry, but lockfile is frozen\nLockfile: ${entryToString( + entry + )}\nExpected: ${entryToString(result.entry)}` + ) + ); + } + storeResult(lockfile, url, result, callback); + return; + } + if (!lockedContent && cacheLocation) { + // When the lockfile cache content is missing + // we need to update the lockfile + if (frozen) { + return callback( + new Error( + `${url} is missing content in the lockfile cache, but lockfile is frozen\nLockfile: ${entryToString( + entry + )}` + ) + ); + } + storeResult(lockfile, url, result, callback); + return; + } + return callback(null, result); + }); + }; + if (cacheLocation) { + // When there is a lockfile cache + // we read the content from there + const key = getCacheKey(entry.resolved); + const filePath = join(intermediateFs, cacheLocation, key); + fs.readFile(filePath, (err, result) => { + if (err) { + if (err.code === "ENOENT") return doFetch(); + return callback(err); + } + const content = /** @type {Buffer} */ (result); + /** + * @param {Buffer | undefined} _result result + * @returns {void} + */ + const continueWithCachedContent = _result => { + if (!upgrade) { + // When not in upgrade mode, we accept the result from the lockfile cache + return callback(null, { entry, content }); + } + return doFetch(content); + }; + if (!verifyIntegrity(content, entry.integrity)) { + /** @type {Buffer | undefined} */ + let contentWithChangedEol; + let isEolChanged = false; + try { + contentWithChangedEol = Buffer.from( + content.toString("utf-8").replace(/\r\n/g, "\n") + ); + isEolChanged = verifyIntegrity( + contentWithChangedEol, + entry.integrity + ); + } catch (_err) { + // ignore + } + if (isEolChanged) { + if (!warnedAboutEol) { + const explainer = `Incorrect end of line sequence was detected in the lockfile cache. +The lockfile cache is protected by integrity checks, so any external modification will lead to a corrupted lockfile cache. +When using git make sure to configure .gitattributes correctly for the lockfile cache: + **/*webpack.lock.data/** -text +This will avoid that the end of line sequence is changed by git on Windows.`; + if (frozen) { + logger.error(explainer); + } else { + logger.warn(explainer); + logger.info( + "Lockfile cache will be automatically fixed now, but when lockfile is frozen this would result in an error." + ); + } + warnedAboutEol = true; + } + if (!frozen) { + // "fix" the end of line sequence of the lockfile content + logger.log( + `${filePath} fixed end of line sequence (\\r\\n instead of \\n).` + ); + intermediateFs.writeFile( + filePath, + /** @type {Buffer} */ + (contentWithChangedEol), + err => { + if (err) return callback(err); + continueWithCachedContent( + /** @type {Buffer} */ + (contentWithChangedEol) + ); + } + ); + return; + } + } + if (frozen) { + return callback( + new Error( + `${ + entry.resolved + } integrity mismatch, expected content with integrity ${ + entry.integrity + } but got ${computeIntegrity(content)}. +Lockfile corrupted (${ + isEolChanged + ? "end of line sequence was unexpectedly changed" + : "incorrectly merged? changed by other tools?" + }). +Run build with un-frozen lockfile to automatically fix lockfile.` + ) + ); + } + // "fix" the lockfile entry to the correct integrity + // the content has priority over the integrity value + entry = { + ...entry, + integrity: computeIntegrity(content) + }; + storeLockEntry(lockfile, url, entry); + } + continueWithCachedContent(result); + }); + } else { + doFetch(); + } + }); + } + ); + + /** + * @param {URL} url url + * @param {ResourceDataWithData} resourceData resource data + * @param {function(Error | null, true | void): void} callback callback + */ + const respondWithUrlModule = (url, resourceData, callback) => { + getInfo(url.href, (err, _result) => { + if (err) return callback(err); + const result = /** @type {Info} */ (_result); + resourceData.resource = url.href; + resourceData.path = url.origin + url.pathname; + resourceData.query = url.search; + resourceData.fragment = url.hash; + resourceData.context = new URL( + ".", + result.entry.resolved + ).href.slice(0, -1); + resourceData.data.mimetype = result.entry.contentType; + callback(null, true); + }); + }; + normalModuleFactory.hooks.resolveForScheme + .for(scheme) + .tapAsync( + "HttpUriPlugin", + (resourceData, resolveData, callback) => { + respondWithUrlModule( + new URL(resourceData.resource), + resourceData, + callback + ); + } + ); + normalModuleFactory.hooks.resolveInScheme + .for(scheme) + .tapAsync("HttpUriPlugin", (resourceData, data, callback) => { + // Only handle relative urls (./xxx, ../xxx, /xxx, //xxx) + if ( + data.dependencyType !== "url" && + !/^\.{0,2}\//.test(resourceData.resource) + ) { + return callback(); + } + respondWithUrlModule( + new URL(resourceData.resource, `${data.context}/`), + resourceData, + callback + ); + }); + const hooks = NormalModule.getCompilationHooks(compilation); + hooks.readResourceForScheme + .for(scheme) + .tapAsync("HttpUriPlugin", (resource, module, callback) => + getInfo(resource, (err, _result) => { + if (err) return callback(err); + const result = /** @type {Info} */ (_result); + /** @type {BuildInfo} */ + (module.buildInfo).resourceIntegrity = result.entry.integrity; + callback(null, result.content); + }) + ); + hooks.needBuild.tapAsync( + "HttpUriPlugin", + (module, context, callback) => { + if ( + module.resource && + module.resource.startsWith(`${scheme}://`) + ) { + getInfo(module.resource, (err, _result) => { + if (err) return callback(err); + const result = /** @type {Info} */ (_result); + if ( + result.entry.integrity !== + /** @type {BuildInfo} */ + (module.buildInfo).resourceIntegrity + ) { + return callback(null, true); + } + callback(); + }); + } else { + return callback(); + } + } + ); + } + compilation.hooks.finishModules.tapAsync( + "HttpUriPlugin", + (modules, callback) => { + if (!lockfileUpdates) return callback(); + const ext = extname(lockfileLocation); + const tempFile = join( + intermediateFs, + dirname(intermediateFs, lockfileLocation), + `.${basename(lockfileLocation, ext)}.${ + (Math.random() * 10000) | 0 + }${ext}` + ); + + const writeDone = () => { + const nextOperation = + /** @type {InProgressWriteItem[]} */ + (inProgressWrite).shift(); + if (nextOperation) { + nextOperation(); + } else { + inProgressWrite = undefined; + } + }; + const runWrite = () => { + intermediateFs.readFile(lockfileLocation, (err, buffer) => { + if (err && err.code !== "ENOENT") { + writeDone(); + return callback(err); + } + const lockfile = buffer + ? Lockfile.parse(buffer.toString("utf-8")) + : new Lockfile(); + for (const [key, value] of /** @type {LockfileUpdates} */ ( + lockfileUpdates + )) { + lockfile.entries.set(key, value); + } + intermediateFs.writeFile(tempFile, lockfile.toString(), err => { + if (err) { + writeDone(); + return ( + /** @type {NonNullable} */ + (intermediateFs.unlink)(tempFile, () => callback(err)) + ); + } + intermediateFs.rename(tempFile, lockfileLocation, err => { + if (err) { + writeDone(); + return ( + /** @type {NonNullable} */ + (intermediateFs.unlink)(tempFile, () => callback(err)) + ); + } + writeDone(); + callback(); + }); + }); + }); + }; + if (inProgressWrite) { + inProgressWrite.push(runWrite); + } else { + inProgressWrite = []; + runWrite(); + } + } + ); + } + ); + } +} + +module.exports = HttpUriPlugin; diff --git a/webpack-lib/lib/serialization/ArraySerializer.js b/webpack-lib/lib/serialization/ArraySerializer.js new file mode 100644 index 00000000000..021c82ca5d4 --- /dev/null +++ b/webpack-lib/lib/serialization/ArraySerializer.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ArraySerializer { + /** + * @template T + * @param {T[]} array array + * @param {ObjectSerializerContext} context context + */ + serialize(array, context) { + context.write(array.length); + for (const item of array) context.write(item); + } + + /** + * @template T + * @param {ObjectDeserializerContext} context context + * @returns {T[]} array + */ + deserialize(context) { + /** @type {number} */ + const length = context.read(); + /** @type {T[]} */ + const array = []; + for (let i = 0; i < length; i++) { + array.push(context.read()); + } + return array; + } +} + +module.exports = ArraySerializer; diff --git a/webpack-lib/lib/serialization/BinaryMiddleware.js b/webpack-lib/lib/serialization/BinaryMiddleware.js new file mode 100644 index 00000000000..2db7487a253 --- /dev/null +++ b/webpack-lib/lib/serialization/BinaryMiddleware.js @@ -0,0 +1,1142 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const memoize = require("../util/memoize"); +const SerializerMiddleware = require("./SerializerMiddleware"); + +/** @typedef {import("./types").BufferSerializableType} BufferSerializableType */ +/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */ + +/* +Format: + +File -> Section* + +Section -> NullsSection | + BooleansSection | + F64NumbersSection | + I32NumbersSection | + I8NumbersSection | + ShortStringSection | + BigIntSection | + I32BigIntSection | + I8BigIntSection + StringSection | + BufferSection | + NopSection + + + +NullsSection -> + NullHeaderByte | Null2HeaderByte | Null3HeaderByte | + Nulls8HeaderByte 0xnn (n:count - 4) | + Nulls32HeaderByte n:ui32 (n:count - 260) | +BooleansSection -> TrueHeaderByte | FalseHeaderByte | BooleansSectionHeaderByte BooleansCountAndBitsByte +F64NumbersSection -> F64NumbersSectionHeaderByte f64* +I32NumbersSection -> I32NumbersSectionHeaderByte i32* +I8NumbersSection -> I8NumbersSectionHeaderByte i8* +ShortStringSection -> ShortStringSectionHeaderByte ascii-byte* +StringSection -> StringSectionHeaderByte i32:length utf8-byte* +BufferSection -> BufferSectionHeaderByte i32:length byte* +NopSection --> NopSectionHeaderByte +BigIntSection -> BigIntSectionHeaderByte i32:length ascii-byte* +I32BigIntSection -> I32BigIntSectionHeaderByte i32 +I8BigIntSection -> I8BigIntSectionHeaderByte i8 + +ShortStringSectionHeaderByte -> 0b1nnn_nnnn (n:length) + +F64NumbersSectionHeaderByte -> 0b001n_nnnn (n:count - 1) +I32NumbersSectionHeaderByte -> 0b010n_nnnn (n:count - 1) +I8NumbersSectionHeaderByte -> 0b011n_nnnn (n:count - 1) + +NullsSectionHeaderByte -> 0b0001_nnnn (n:count - 1) +BooleansCountAndBitsByte -> + 0b0000_1xxx (count = 3) | + 0b0001_xxxx (count = 4) | + 0b001x_xxxx (count = 5) | + 0b01xx_xxxx (count = 6) | + 0b1nnn_nnnn (n:count - 7, 7 <= count <= 133) + 0xff n:ui32 (n:count, 134 <= count < 2^32) + +StringSectionHeaderByte -> 0b0000_1110 +BufferSectionHeaderByte -> 0b0000_1111 +NopSectionHeaderByte -> 0b0000_1011 +BigIntSectionHeaderByte -> 0b0001_1010 +I32BigIntSectionHeaderByte -> 0b0001_1100 +I8BigIntSectionHeaderByte -> 0b0001_1011 +FalseHeaderByte -> 0b0000_1100 +TrueHeaderByte -> 0b0000_1101 + +RawNumber -> n (n <= 10) + +*/ + +const LAZY_HEADER = 0x0b; +const TRUE_HEADER = 0x0c; +const FALSE_HEADER = 0x0d; +const BOOLEANS_HEADER = 0x0e; +const NULL_HEADER = 0x10; +const NULL2_HEADER = 0x11; +const NULL3_HEADER = 0x12; +const NULLS8_HEADER = 0x13; +const NULLS32_HEADER = 0x14; +const NULL_AND_I8_HEADER = 0x15; +const NULL_AND_I32_HEADER = 0x16; +const NULL_AND_TRUE_HEADER = 0x17; +const NULL_AND_FALSE_HEADER = 0x18; +const BIGINT_HEADER = 0x1a; +const BIGINT_I8_HEADER = 0x1b; +const BIGINT_I32_HEADER = 0x1c; +const STRING_HEADER = 0x1e; +const BUFFER_HEADER = 0x1f; +const I8_HEADER = 0x60; +const I32_HEADER = 0x40; +const F64_HEADER = 0x20; +const SHORT_STRING_HEADER = 0x80; + +/** Uplift high-order bits */ +const NUMBERS_HEADER_MASK = 0xe0; // 0b1010_0000 +const NUMBERS_COUNT_MASK = 0x1f; // 0b0001_1111 +const SHORT_STRING_LENGTH_MASK = 0x7f; // 0b0111_1111 + +const HEADER_SIZE = 1; +const I8_SIZE = 1; +const I32_SIZE = 4; +const F64_SIZE = 8; + +const MEASURE_START_OPERATION = Symbol("MEASURE_START_OPERATION"); +const MEASURE_END_OPERATION = Symbol("MEASURE_END_OPERATION"); + +/** @typedef {typeof MEASURE_START_OPERATION} MEASURE_START_OPERATION_TYPE */ +/** @typedef {typeof MEASURE_END_OPERATION} MEASURE_END_OPERATION_TYPE */ + +/** + * @param {number} n number + * @returns {0 | 1 | 2} type of number for serialization + */ +const identifyNumber = n => { + if (n === (n | 0)) { + if (n <= 127 && n >= -128) return 0; + if (n <= 2147483647 && n >= -2147483648) return 1; + } + return 2; +}; + +/** + * @param {bigint} n bigint + * @returns {0 | 1 | 2} type of bigint for serialization + */ +const identifyBigInt = n => { + if (n <= BigInt(127) && n >= BigInt(-128)) return 0; + if (n <= BigInt(2147483647) && n >= BigInt(-2147483648)) return 1; + return 2; +}; + +/** @typedef {TODO} Context */ + +/** + * @typedef {PrimitiveSerializableType[]} DeserializedType + * @typedef {BufferSerializableType[]} SerializedType + * @extends {SerializerMiddleware} + */ +class BinaryMiddleware extends SerializerMiddleware { + /** + * @param {DeserializedType} data data + * @param {object} context context object + * @returns {SerializedType|Promise} serialized data + */ + serialize(data, context) { + return this._serialize(data, context); + } + + /** + * @param {function(): Promise | any} fn lazy function + * @param {TODO} context serialize function + * @returns {function(): Promise | any} new lazy + */ + _serializeLazy(fn, context) { + return SerializerMiddleware.serializeLazy(fn, data => + this._serialize(data, context) + ); + } + + /** + * @param {DeserializedType} data data + * @param {TODO} context context object + * @param {{ leftOverBuffer: Buffer | null, allocationSize: number, increaseCounter: number }} allocationScope allocation scope + * @returns {SerializedType} serialized data + */ + _serialize( + data, + context, + allocationScope = { + allocationSize: 1024, + increaseCounter: 0, + leftOverBuffer: null + } + ) { + /** @type {Buffer | null} */ + let leftOverBuffer = null; + /** @type {BufferSerializableType[]} */ + let buffers = []; + /** @type {Buffer | null} */ + let currentBuffer = allocationScope ? allocationScope.leftOverBuffer : null; + allocationScope.leftOverBuffer = null; + let currentPosition = 0; + if (currentBuffer === null) { + currentBuffer = Buffer.allocUnsafe(allocationScope.allocationSize); + } + /** + * @param {number} bytesNeeded bytes needed + */ + const allocate = bytesNeeded => { + if (currentBuffer !== null) { + if (currentBuffer.length - currentPosition >= bytesNeeded) return; + flush(); + } + if (leftOverBuffer && leftOverBuffer.length >= bytesNeeded) { + currentBuffer = leftOverBuffer; + leftOverBuffer = null; + } else { + currentBuffer = Buffer.allocUnsafe( + Math.max(bytesNeeded, allocationScope.allocationSize) + ); + if ( + !(allocationScope.increaseCounter = + (allocationScope.increaseCounter + 1) % 4) && + allocationScope.allocationSize < 16777216 + ) { + allocationScope.allocationSize = allocationScope.allocationSize << 1; + } + } + }; + const flush = () => { + if (currentBuffer !== null) { + if (currentPosition > 0) { + buffers.push( + Buffer.from( + currentBuffer.buffer, + currentBuffer.byteOffset, + currentPosition + ) + ); + } + if ( + !leftOverBuffer || + leftOverBuffer.length < currentBuffer.length - currentPosition + ) { + leftOverBuffer = Buffer.from( + currentBuffer.buffer, + currentBuffer.byteOffset + currentPosition, + currentBuffer.byteLength - currentPosition + ); + } + + currentBuffer = null; + currentPosition = 0; + } + }; + /** + * @param {number} byte byte + */ + const writeU8 = byte => { + /** @type {Buffer} */ + (currentBuffer).writeUInt8(byte, currentPosition++); + }; + /** + * @param {number} ui32 ui32 + */ + const writeU32 = ui32 => { + /** @type {Buffer} */ + (currentBuffer).writeUInt32LE(ui32, currentPosition); + currentPosition += 4; + }; + /** @type {number[]} */ + const measureStack = []; + const measureStart = () => { + measureStack.push(buffers.length, currentPosition); + }; + /** + * @returns {number} size + */ + const measureEnd = () => { + const oldPos = /** @type {number} */ (measureStack.pop()); + const buffersIndex = /** @type {number} */ (measureStack.pop()); + let size = currentPosition - oldPos; + for (let i = buffersIndex; i < buffers.length; i++) { + size += buffers[i].length; + } + return size; + }; + for (let i = 0; i < data.length; i++) { + const thing = data[i]; + switch (typeof thing) { + case "function": { + if (!SerializerMiddleware.isLazy(thing)) + throw new Error(`Unexpected function ${thing}`); + /** @type {SerializedType | (() => SerializedType)} */ + let serializedData = + SerializerMiddleware.getLazySerializedValue(thing); + if (serializedData === undefined) { + if (SerializerMiddleware.isLazy(thing, this)) { + flush(); + allocationScope.leftOverBuffer = leftOverBuffer; + const result = + /** @type {(Exclude>)[]} */ ( + thing() + ); + const data = this._serialize(result, context, allocationScope); + leftOverBuffer = allocationScope.leftOverBuffer; + allocationScope.leftOverBuffer = null; + SerializerMiddleware.setLazySerializedValue(thing, data); + serializedData = data; + } else { + serializedData = this._serializeLazy(thing, context); + flush(); + buffers.push(serializedData); + break; + } + } else if (typeof serializedData === "function") { + flush(); + buffers.push(serializedData); + break; + } + /** @type {number[]} */ + const lengths = []; + for (const item of serializedData) { + let last; + if (typeof item === "function") { + lengths.push(0); + } else if (item.length === 0) { + // ignore + } else if ( + lengths.length > 0 && + (last = lengths[lengths.length - 1]) !== 0 + ) { + const remaining = 0xffffffff - last; + if (remaining >= item.length) { + lengths[lengths.length - 1] += item.length; + } else { + lengths.push(item.length - remaining); + lengths[lengths.length - 2] = 0xffffffff; + } + } else { + lengths.push(item.length); + } + } + allocate(5 + lengths.length * 4); + writeU8(LAZY_HEADER); + writeU32(lengths.length); + for (const l of lengths) { + writeU32(l); + } + flush(); + for (const item of serializedData) { + buffers.push(item); + } + break; + } + case "string": { + const len = Buffer.byteLength(thing); + if (len >= 128 || len !== thing.length) { + allocate(len + HEADER_SIZE + I32_SIZE); + writeU8(STRING_HEADER); + writeU32(len); + currentBuffer.write(thing, currentPosition); + currentPosition += len; + } else if (len >= 70) { + allocate(len + HEADER_SIZE); + writeU8(SHORT_STRING_HEADER | len); + + currentBuffer.write(thing, currentPosition, "latin1"); + currentPosition += len; + } else { + allocate(len + HEADER_SIZE); + writeU8(SHORT_STRING_HEADER | len); + + for (let i = 0; i < len; i++) { + currentBuffer[currentPosition++] = thing.charCodeAt(i); + } + } + break; + } + case "bigint": { + const type = identifyBigInt(thing); + if (type === 0 && thing >= 0 && thing <= BigInt(10)) { + // shortcut for very small bigints + allocate(HEADER_SIZE + I8_SIZE); + writeU8(BIGINT_I8_HEADER); + writeU8(Number(thing)); + break; + } + + switch (type) { + case 0: { + let n = 1; + allocate(HEADER_SIZE + I8_SIZE * n); + writeU8(BIGINT_I8_HEADER | (n - 1)); + while (n > 0) { + currentBuffer.writeInt8( + Number(/** @type {bigint} */ (data[i])), + currentPosition + ); + currentPosition += I8_SIZE; + n--; + i++; + } + i--; + break; + } + case 1: { + let n = 1; + allocate(HEADER_SIZE + I32_SIZE * n); + writeU8(BIGINT_I32_HEADER | (n - 1)); + while (n > 0) { + currentBuffer.writeInt32LE( + Number(/** @type {bigint} */ (data[i])), + currentPosition + ); + currentPosition += I32_SIZE; + n--; + i++; + } + i--; + break; + } + default: { + const value = thing.toString(); + const len = Buffer.byteLength(value); + allocate(len + HEADER_SIZE + I32_SIZE); + writeU8(BIGINT_HEADER); + writeU32(len); + currentBuffer.write(value, currentPosition); + currentPosition += len; + break; + } + } + break; + } + case "number": { + const type = identifyNumber(thing); + if (type === 0 && thing >= 0 && thing <= 10) { + // shortcut for very small numbers + allocate(I8_SIZE); + writeU8(thing); + break; + } + /** + * amount of numbers to write + * @type {number} + */ + let n = 1; + for (; n < 32 && i + n < data.length; n++) { + const item = data[i + n]; + if (typeof item !== "number") break; + if (identifyNumber(item) !== type) break; + } + switch (type) { + case 0: + allocate(HEADER_SIZE + I8_SIZE * n); + writeU8(I8_HEADER | (n - 1)); + while (n > 0) { + currentBuffer.writeInt8( + /** @type {number} */ (data[i]), + currentPosition + ); + currentPosition += I8_SIZE; + n--; + i++; + } + break; + case 1: + allocate(HEADER_SIZE + I32_SIZE * n); + writeU8(I32_HEADER | (n - 1)); + while (n > 0) { + currentBuffer.writeInt32LE( + /** @type {number} */ (data[i]), + currentPosition + ); + currentPosition += I32_SIZE; + n--; + i++; + } + break; + case 2: + allocate(HEADER_SIZE + F64_SIZE * n); + writeU8(F64_HEADER | (n - 1)); + while (n > 0) { + currentBuffer.writeDoubleLE( + /** @type {number} */ (data[i]), + currentPosition + ); + currentPosition += F64_SIZE; + n--; + i++; + } + break; + } + + i--; + break; + } + case "boolean": { + let lastByte = thing === true ? 1 : 0; + const bytes = []; + let count = 1; + let n; + for (n = 1; n < 0xffffffff && i + n < data.length; n++) { + const item = data[i + n]; + if (typeof item !== "boolean") break; + const pos = count & 0x7; + if (pos === 0) { + bytes.push(lastByte); + lastByte = item === true ? 1 : 0; + } else if (item === true) { + lastByte |= 1 << pos; + } + count++; + } + i += count - 1; + if (count === 1) { + allocate(HEADER_SIZE); + writeU8(lastByte === 1 ? TRUE_HEADER : FALSE_HEADER); + } else if (count === 2) { + allocate(HEADER_SIZE * 2); + writeU8(lastByte & 1 ? TRUE_HEADER : FALSE_HEADER); + writeU8(lastByte & 2 ? TRUE_HEADER : FALSE_HEADER); + } else if (count <= 6) { + allocate(HEADER_SIZE + I8_SIZE); + writeU8(BOOLEANS_HEADER); + writeU8((1 << count) | lastByte); + } else if (count <= 133) { + allocate(HEADER_SIZE + I8_SIZE + I8_SIZE * bytes.length + I8_SIZE); + writeU8(BOOLEANS_HEADER); + writeU8(0x80 | (count - 7)); + for (const byte of bytes) writeU8(byte); + writeU8(lastByte); + } else { + allocate( + HEADER_SIZE + + I8_SIZE + + I32_SIZE + + I8_SIZE * bytes.length + + I8_SIZE + ); + writeU8(BOOLEANS_HEADER); + writeU8(0xff); + writeU32(count); + for (const byte of bytes) writeU8(byte); + writeU8(lastByte); + } + break; + } + case "object": { + if (thing === null) { + let n; + for (n = 1; n < 0x100000104 && i + n < data.length; n++) { + const item = data[i + n]; + if (item !== null) break; + } + i += n - 1; + if (n === 1) { + if (i + 1 < data.length) { + const next = data[i + 1]; + if (next === true) { + allocate(HEADER_SIZE); + writeU8(NULL_AND_TRUE_HEADER); + i++; + } else if (next === false) { + allocate(HEADER_SIZE); + writeU8(NULL_AND_FALSE_HEADER); + i++; + } else if (typeof next === "number") { + const type = identifyNumber(next); + if (type === 0) { + allocate(HEADER_SIZE + I8_SIZE); + writeU8(NULL_AND_I8_HEADER); + currentBuffer.writeInt8(next, currentPosition); + currentPosition += I8_SIZE; + i++; + } else if (type === 1) { + allocate(HEADER_SIZE + I32_SIZE); + writeU8(NULL_AND_I32_HEADER); + currentBuffer.writeInt32LE(next, currentPosition); + currentPosition += I32_SIZE; + i++; + } else { + allocate(HEADER_SIZE); + writeU8(NULL_HEADER); + } + } else { + allocate(HEADER_SIZE); + writeU8(NULL_HEADER); + } + } else { + allocate(HEADER_SIZE); + writeU8(NULL_HEADER); + } + } else if (n === 2) { + allocate(HEADER_SIZE); + writeU8(NULL2_HEADER); + } else if (n === 3) { + allocate(HEADER_SIZE); + writeU8(NULL3_HEADER); + } else if (n < 260) { + allocate(HEADER_SIZE + I8_SIZE); + writeU8(NULLS8_HEADER); + writeU8(n - 4); + } else { + allocate(HEADER_SIZE + I32_SIZE); + writeU8(NULLS32_HEADER); + writeU32(n - 260); + } + } else if (Buffer.isBuffer(thing)) { + if (thing.length < 8192) { + allocate(HEADER_SIZE + I32_SIZE + thing.length); + writeU8(BUFFER_HEADER); + writeU32(thing.length); + thing.copy(currentBuffer, currentPosition); + currentPosition += thing.length; + } else { + allocate(HEADER_SIZE + I32_SIZE); + writeU8(BUFFER_HEADER); + writeU32(thing.length); + flush(); + buffers.push(thing); + } + } + break; + } + case "symbol": { + if (thing === MEASURE_START_OPERATION) { + measureStart(); + } else if (thing === MEASURE_END_OPERATION) { + const size = measureEnd(); + allocate(HEADER_SIZE + I32_SIZE); + writeU8(I32_HEADER); + currentBuffer.writeInt32LE(size, currentPosition); + currentPosition += I32_SIZE; + } + break; + } + default: { + throw new Error( + `Unknown typeof "${typeof thing}" in binary middleware` + ); + } + } + } + flush(); + + allocationScope.leftOverBuffer = leftOverBuffer; + + // avoid leaking memory + currentBuffer = null; + leftOverBuffer = null; + allocationScope = /** @type {EXPECTED_ANY} */ (undefined); + const _buffers = buffers; + buffers = /** @type {EXPECTED_ANY} */ (undefined); + return _buffers; + } + + /** + * @param {SerializedType} data data + * @param {object} context context object + * @returns {DeserializedType|Promise} deserialized data + */ + deserialize(data, context) { + return this._deserialize(data, context); + } + + _createLazyDeserialized(content, context) { + return SerializerMiddleware.createLazy( + memoize(() => this._deserialize(content, context)), + this, + undefined, + content + ); + } + + _deserializeLazy(fn, context) { + return SerializerMiddleware.deserializeLazy(fn, data => + this._deserialize(data, context) + ); + } + + /** + * @param {SerializedType} data data + * @param {TODO} context context object + * @returns {DeserializedType} deserialized data + */ + _deserialize(data, context) { + let currentDataItem = 0; + /** @type {BufferSerializableType | null} */ + let currentBuffer = data[0]; + let currentIsBuffer = Buffer.isBuffer(currentBuffer); + let currentPosition = 0; + + /** @type {(x: Buffer) => Buffer} */ + const retainedBuffer = context.retainedBuffer || (x => x); + + const checkOverflow = () => { + if (currentPosition >= /** @type {Buffer} */ (currentBuffer).length) { + currentPosition = 0; + currentDataItem++; + currentBuffer = + currentDataItem < data.length ? data[currentDataItem] : null; + currentIsBuffer = Buffer.isBuffer(currentBuffer); + } + }; + /** + * @param {number} n n + * @returns {boolean} true when in current buffer, otherwise false + */ + const isInCurrentBuffer = n => + currentIsBuffer && + n + currentPosition <= /** @type {Buffer} */ (currentBuffer).length; + const ensureBuffer = () => { + if (!currentIsBuffer) { + throw new Error( + currentBuffer === null + ? "Unexpected end of stream" + : "Unexpected lazy element in stream" + ); + } + }; + /** + * Reads n bytes + * @param {number} n amount of bytes to read + * @returns {Buffer} buffer with bytes + */ + const read = n => { + ensureBuffer(); + const rem = + /** @type {Buffer} */ (currentBuffer).length - currentPosition; + if (rem < n) { + const buffers = [read(rem)]; + n -= rem; + ensureBuffer(); + while (/** @type {Buffer} */ (currentBuffer).length < n) { + const b = /** @type {Buffer} */ (currentBuffer); + buffers.push(b); + n -= b.length; + currentDataItem++; + currentBuffer = + currentDataItem < data.length ? data[currentDataItem] : null; + currentIsBuffer = Buffer.isBuffer(currentBuffer); + ensureBuffer(); + } + buffers.push(read(n)); + return Buffer.concat(buffers); + } + const b = /** @type {Buffer} */ (currentBuffer); + const res = Buffer.from(b.buffer, b.byteOffset + currentPosition, n); + currentPosition += n; + checkOverflow(); + return res; + }; + /** + * Reads up to n bytes + * @param {number} n amount of bytes to read + * @returns {Buffer} buffer with bytes + */ + const readUpTo = n => { + ensureBuffer(); + const rem = + /** @type {Buffer} */ + (currentBuffer).length - currentPosition; + if (rem < n) { + n = rem; + } + const b = /** @type {Buffer} */ (currentBuffer); + const res = Buffer.from(b.buffer, b.byteOffset + currentPosition, n); + currentPosition += n; + checkOverflow(); + return res; + }; + /** + * @returns {number} U8 + */ + const readU8 = () => { + ensureBuffer(); + /** + * There is no need to check remaining buffer size here + * since {@link checkOverflow} guarantees at least one byte remaining + */ + const byte = + /** @type {Buffer} */ + (currentBuffer).readUInt8(currentPosition); + currentPosition += I8_SIZE; + checkOverflow(); + return byte; + }; + /** + * @returns {number} U32 + */ + const readU32 = () => read(I32_SIZE).readUInt32LE(0); + /** + * @param {number} data data + * @param {number} n n + */ + const readBits = (data, n) => { + let mask = 1; + while (n !== 0) { + result.push((data & mask) !== 0); + mask = mask << 1; + n--; + } + }; + const dispatchTable = Array.from({ length: 256 }).map((_, header) => { + switch (header) { + case LAZY_HEADER: + return () => { + const count = readU32(); + const lengths = Array.from({ length: count }).map(() => readU32()); + const content = []; + for (let l of lengths) { + if (l === 0) { + if (typeof currentBuffer !== "function") { + throw new Error("Unexpected non-lazy element in stream"); + } + content.push(currentBuffer); + currentDataItem++; + currentBuffer = + currentDataItem < data.length ? data[currentDataItem] : null; + currentIsBuffer = Buffer.isBuffer(currentBuffer); + } else { + do { + const buf = readUpTo(l); + l -= buf.length; + content.push(retainedBuffer(buf)); + } while (l > 0); + } + } + result.push(this._createLazyDeserialized(content, context)); + }; + case BUFFER_HEADER: + return () => { + const len = readU32(); + result.push(retainedBuffer(read(len))); + }; + case TRUE_HEADER: + return () => result.push(true); + case FALSE_HEADER: + return () => result.push(false); + case NULL3_HEADER: + return () => result.push(null, null, null); + case NULL2_HEADER: + return () => result.push(null, null); + case NULL_HEADER: + return () => result.push(null); + case NULL_AND_TRUE_HEADER: + return () => result.push(null, true); + case NULL_AND_FALSE_HEADER: + return () => result.push(null, false); + case NULL_AND_I8_HEADER: + return () => { + if (currentIsBuffer) { + result.push( + null, + /** @type {Buffer} */ (currentBuffer).readInt8(currentPosition) + ); + currentPosition += I8_SIZE; + checkOverflow(); + } else { + result.push(null, read(I8_SIZE).readInt8(0)); + } + }; + case NULL_AND_I32_HEADER: + return () => { + result.push(null); + if (isInCurrentBuffer(I32_SIZE)) { + result.push( + /** @type {Buffer} */ (currentBuffer).readInt32LE( + currentPosition + ) + ); + currentPosition += I32_SIZE; + checkOverflow(); + } else { + result.push(read(I32_SIZE).readInt32LE(0)); + } + }; + case NULLS8_HEADER: + return () => { + const len = readU8() + 4; + for (let i = 0; i < len; i++) { + result.push(null); + } + }; + case NULLS32_HEADER: + return () => { + const len = readU32() + 260; + for (let i = 0; i < len; i++) { + result.push(null); + } + }; + case BOOLEANS_HEADER: + return () => { + const innerHeader = readU8(); + if ((innerHeader & 0xf0) === 0) { + readBits(innerHeader, 3); + } else if ((innerHeader & 0xe0) === 0) { + readBits(innerHeader, 4); + } else if ((innerHeader & 0xc0) === 0) { + readBits(innerHeader, 5); + } else if ((innerHeader & 0x80) === 0) { + readBits(innerHeader, 6); + } else if (innerHeader !== 0xff) { + let count = (innerHeader & 0x7f) + 7; + while (count > 8) { + readBits(readU8(), 8); + count -= 8; + } + readBits(readU8(), count); + } else { + let count = readU32(); + while (count > 8) { + readBits(readU8(), 8); + count -= 8; + } + readBits(readU8(), count); + } + }; + case STRING_HEADER: + return () => { + const len = readU32(); + if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) { + result.push( + /** @type {Buffer} */ + (currentBuffer).toString( + undefined, + currentPosition, + currentPosition + len + ) + ); + currentPosition += len; + checkOverflow(); + } else { + result.push(read(len).toString()); + } + }; + case SHORT_STRING_HEADER: + return () => result.push(""); + case SHORT_STRING_HEADER | 1: + return () => { + if (currentIsBuffer && currentPosition < 0x7ffffffe) { + result.push( + /** @type {Buffer} */ + (currentBuffer).toString( + "latin1", + currentPosition, + currentPosition + 1 + ) + ); + currentPosition++; + checkOverflow(); + } else { + result.push(read(1).toString("latin1")); + } + }; + case I8_HEADER: + return () => { + if (currentIsBuffer) { + result.push( + /** @type {Buffer} */ (currentBuffer).readInt8(currentPosition) + ); + currentPosition++; + checkOverflow(); + } else { + result.push(read(1).readInt8(0)); + } + }; + case BIGINT_I8_HEADER: { + const len = 1; + return () => { + const need = I8_SIZE * len; + + if (isInCurrentBuffer(need)) { + for (let i = 0; i < len; i++) { + const value = + /** @type {Buffer} */ + (currentBuffer).readInt8(currentPosition); + result.push(BigInt(value)); + currentPosition += I8_SIZE; + } + checkOverflow(); + } else { + const buf = read(need); + for (let i = 0; i < len; i++) { + const value = buf.readInt8(i * I8_SIZE); + result.push(BigInt(value)); + } + } + }; + } + case BIGINT_I32_HEADER: { + const len = 1; + return () => { + const need = I32_SIZE * len; + if (isInCurrentBuffer(need)) { + for (let i = 0; i < len; i++) { + const value = /** @type {Buffer} */ (currentBuffer).readInt32LE( + currentPosition + ); + result.push(BigInt(value)); + currentPosition += I32_SIZE; + } + checkOverflow(); + } else { + const buf = read(need); + for (let i = 0; i < len; i++) { + const value = buf.readInt32LE(i * I32_SIZE); + result.push(BigInt(value)); + } + } + }; + } + case BIGINT_HEADER: { + return () => { + const len = readU32(); + if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) { + const value = + /** @type {Buffer} */ + (currentBuffer).toString( + undefined, + currentPosition, + currentPosition + len + ); + + result.push(BigInt(value)); + currentPosition += len; + checkOverflow(); + } else { + const value = read(len).toString(); + result.push(BigInt(value)); + } + }; + } + default: + if (header <= 10) { + return () => result.push(header); + } else if ((header & SHORT_STRING_HEADER) === SHORT_STRING_HEADER) { + const len = header & SHORT_STRING_LENGTH_MASK; + return () => { + if ( + isInCurrentBuffer(len) && + currentPosition + len < 0x7fffffff + ) { + result.push( + /** @type {Buffer} */ + (currentBuffer).toString( + "latin1", + currentPosition, + currentPosition + len + ) + ); + currentPosition += len; + checkOverflow(); + } else { + result.push(read(len).toString("latin1")); + } + }; + } else if ((header & NUMBERS_HEADER_MASK) === F64_HEADER) { + const len = (header & NUMBERS_COUNT_MASK) + 1; + return () => { + const need = F64_SIZE * len; + if (isInCurrentBuffer(need)) { + for (let i = 0; i < len; i++) { + result.push( + /** @type {Buffer} */ (currentBuffer).readDoubleLE( + currentPosition + ) + ); + currentPosition += F64_SIZE; + } + checkOverflow(); + } else { + const buf = read(need); + for (let i = 0; i < len; i++) { + result.push(buf.readDoubleLE(i * F64_SIZE)); + } + } + }; + } else if ((header & NUMBERS_HEADER_MASK) === I32_HEADER) { + const len = (header & NUMBERS_COUNT_MASK) + 1; + return () => { + const need = I32_SIZE * len; + if (isInCurrentBuffer(need)) { + for (let i = 0; i < len; i++) { + result.push( + /** @type {Buffer} */ (currentBuffer).readInt32LE( + currentPosition + ) + ); + currentPosition += I32_SIZE; + } + checkOverflow(); + } else { + const buf = read(need); + for (let i = 0; i < len; i++) { + result.push(buf.readInt32LE(i * I32_SIZE)); + } + } + }; + } else if ((header & NUMBERS_HEADER_MASK) === I8_HEADER) { + const len = (header & NUMBERS_COUNT_MASK) + 1; + return () => { + const need = I8_SIZE * len; + if (isInCurrentBuffer(need)) { + for (let i = 0; i < len; i++) { + result.push( + /** @type {Buffer} */ (currentBuffer).readInt8( + currentPosition + ) + ); + currentPosition += I8_SIZE; + } + checkOverflow(); + } else { + const buf = read(need); + for (let i = 0; i < len; i++) { + result.push(buf.readInt8(i * I8_SIZE)); + } + } + }; + } + return () => { + throw new Error(`Unexpected header byte 0x${header.toString(16)}`); + }; + } + }); + + /** @type {DeserializedType} */ + let result = []; + while (currentBuffer !== null) { + if (typeof currentBuffer === "function") { + result.push(this._deserializeLazy(currentBuffer, context)); + currentDataItem++; + currentBuffer = + currentDataItem < data.length ? data[currentDataItem] : null; + currentIsBuffer = Buffer.isBuffer(currentBuffer); + } else { + const header = readU8(); + dispatchTable[header](); + } + } + + // avoid leaking memory in context + // eslint-disable-next-line prefer-const + let _result = result; + result = /** @type {EXPECTED_ANY} */ (undefined); + return _result; + } +} + +module.exports = BinaryMiddleware; + +module.exports.MEASURE_START_OPERATION = MEASURE_START_OPERATION; +module.exports.MEASURE_END_OPERATION = MEASURE_END_OPERATION; diff --git a/webpack-lib/lib/serialization/DateObjectSerializer.js b/webpack-lib/lib/serialization/DateObjectSerializer.js new file mode 100644 index 00000000000..c69ccfe8c7c --- /dev/null +++ b/webpack-lib/lib/serialization/DateObjectSerializer.js @@ -0,0 +1,28 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class DateObjectSerializer { + /** + * @param {Date} obj date + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + context.write(obj.getTime()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {Date} date + */ + deserialize(context) { + return new Date(context.read()); + } +} + +module.exports = DateObjectSerializer; diff --git a/webpack-lib/lib/serialization/ErrorObjectSerializer.js b/webpack-lib/lib/serialization/ErrorObjectSerializer.js new file mode 100644 index 00000000000..b0869155ff4 --- /dev/null +++ b/webpack-lib/lib/serialization/ErrorObjectSerializer.js @@ -0,0 +1,44 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ErrorObjectSerializer { + /** + * @param {ErrorConstructor | EvalErrorConstructor | RangeErrorConstructor | ReferenceErrorConstructor | SyntaxErrorConstructor | TypeErrorConstructor} Type error type + */ + constructor(Type) { + this.Type = Type; + } + + /** + * @param {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} obj error + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + context.write(obj.message); + context.write(obj.stack); + context.write(/** @type {Error & { cause: "unknown" }} */ (obj).cause); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} error + */ + deserialize(context) { + const err = new this.Type(); + + err.message = context.read(); + err.stack = context.read(); + /** @type {Error & { cause: "unknown" }} */ + (err).cause = context.read(); + + return err; + } +} + +module.exports = ErrorObjectSerializer; diff --git a/webpack-lib/lib/serialization/FileMiddleware.js b/webpack-lib/lib/serialization/FileMiddleware.js new file mode 100644 index 00000000000..b8de8a958d9 --- /dev/null +++ b/webpack-lib/lib/serialization/FileMiddleware.js @@ -0,0 +1,731 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { constants } = require("buffer"); +const { pipeline } = require("stream"); +const { + createBrotliCompress, + createBrotliDecompress, + createGzip, + createGunzip, + constants: zConstants +} = require("zlib"); +const createHash = require("../util/createHash"); +const { dirname, join, mkdirp } = require("../util/fs"); +const memoize = require("../util/memoize"); +const SerializerMiddleware = require("./SerializerMiddleware"); + +/** @typedef {typeof import("../util/Hash")} Hash */ +/** @typedef {import("../util/fs").IStats} IStats */ +/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ +/** @typedef {import("./types").BufferSerializableType} BufferSerializableType */ + +/* +Format: + +File -> Header Section* + +Version -> u32 +AmountOfSections -> u32 +SectionSize -> i32 (if less than zero represents lazy value) + +Header -> Version AmountOfSections SectionSize* + +Buffer -> n bytes +Section -> Buffer + +*/ + +// "wpc" + 1 in little-endian +const VERSION = 0x01637077; +const WRITE_LIMIT_TOTAL = 0x7fff0000; +const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024; + +/** + * @param {Buffer[]} buffers buffers + * @param {string | Hash} hashFunction hash function to use + * @returns {string} hash + */ +const hashForName = (buffers, hashFunction) => { + const hash = createHash(hashFunction); + for (const buf of buffers) hash.update(buf); + return /** @type {string} */ (hash.digest("hex")); +}; + +const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024; +const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024; + +/** @type {function(Buffer, number, number): void} */ +const writeUInt64LE = Buffer.prototype.writeBigUInt64LE + ? (buf, value, offset) => { + buf.writeBigUInt64LE(BigInt(value), offset); + } + : (buf, value, offset) => { + const low = value % 0x100000000; + const high = (value - low) / 0x100000000; + buf.writeUInt32LE(low, offset); + buf.writeUInt32LE(high, offset + 4); + }; + +/** @type {function(Buffer, number): void} */ +const readUInt64LE = Buffer.prototype.readBigUInt64LE + ? (buf, offset) => Number(buf.readBigUInt64LE(offset)) + : (buf, offset) => { + const low = buf.readUInt32LE(offset); + const high = buf.readUInt32LE(offset + 4); + return high * 0x100000000 + low; + }; + +/** + * @typedef {object} SerializeResult + * @property {string | false} name + * @property {number} size + * @property {Promise=} backgroundJob + */ + +/** + * @param {FileMiddleware} middleware this + * @param {BufferSerializableType[] | Promise} data data to be serialized + * @param {string | boolean} name file base name + * @param {function(string | false, Buffer[], number): Promise} writeFile writes a file + * @param {string | Hash} hashFunction hash function to use + * @returns {Promise} resulting file pointer and promise + */ +const serialize = async ( + middleware, + data, + name, + writeFile, + hashFunction = "md4" +) => { + /** @type {(Buffer[] | Buffer | SerializeResult | Promise)[]} */ + const processedData = []; + /** @type {WeakMap>} */ + const resultToLazy = new WeakMap(); + /** @type {Buffer[] | undefined} */ + let lastBuffers; + for (const item of await data) { + if (typeof item === "function") { + if (!SerializerMiddleware.isLazy(item)) + throw new Error("Unexpected function"); + if (!SerializerMiddleware.isLazy(item, middleware)) { + throw new Error( + "Unexpected lazy value with non-this target (can't pass through lazy values)" + ); + } + lastBuffers = undefined; + const serializedInfo = SerializerMiddleware.getLazySerializedValue(item); + if (serializedInfo) { + if (typeof serializedInfo === "function") { + throw new Error( + "Unexpected lazy value with non-this target (can't pass through lazy values)" + ); + } else { + processedData.push(serializedInfo); + } + } else { + const content = item(); + if (content) { + const options = SerializerMiddleware.getLazyOptions(item); + processedData.push( + serialize( + middleware, + content, + (options && options.name) || true, + writeFile, + hashFunction + ).then(result => { + /** @type {any} */ (item).options.size = result.size; + resultToLazy.set(result, item); + return result; + }) + ); + } else { + throw new Error( + "Unexpected falsy value returned by lazy value function" + ); + } + } + } else if (item) { + if (lastBuffers) { + lastBuffers.push(item); + } else { + lastBuffers = [item]; + processedData.push(lastBuffers); + } + } else { + throw new Error("Unexpected falsy value in items array"); + } + } + /** @type {Promise[]} */ + const backgroundJobs = []; + const resolvedData = ( + await Promise.all( + /** @type {Promise[]} */ + (processedData) + ) + ).map(item => { + if (Array.isArray(item) || Buffer.isBuffer(item)) return item; + + backgroundJobs.push(item.backgroundJob); + // create pointer buffer from size and name + const name = /** @type {string} */ (item.name); + const nameBuffer = Buffer.from(name); + const buf = Buffer.allocUnsafe(8 + nameBuffer.length); + writeUInt64LE(buf, item.size, 0); + nameBuffer.copy(buf, 8, 0); + const lazy = resultToLazy.get(item); + SerializerMiddleware.setLazySerializedValue(lazy, buf); + return buf; + }); + /** @type {number[]} */ + const lengths = []; + for (const item of resolvedData) { + if (Array.isArray(item)) { + let l = 0; + for (const b of item) l += b.length; + while (l > 0x7fffffff) { + lengths.push(0x7fffffff); + l -= 0x7fffffff; + } + lengths.push(l); + } else if (item) { + lengths.push(-item.length); + } else { + throw new Error(`Unexpected falsy value in resolved data ${item}`); + } + } + const header = Buffer.allocUnsafe(8 + lengths.length * 4); + header.writeUInt32LE(VERSION, 0); + header.writeUInt32LE(lengths.length, 4); + for (let i = 0; i < lengths.length; i++) { + header.writeInt32LE(lengths[i], 8 + i * 4); + } + /** @type {Buffer[]} */ + const buf = [header]; + for (const item of resolvedData) { + if (Array.isArray(item)) { + for (const b of item) buf.push(b); + } else if (item) { + buf.push(item); + } + } + if (name === true) { + name = hashForName(buf, hashFunction); + } + let size = 0; + for (const b of buf) size += b.length; + backgroundJobs.push(writeFile(name, buf, size)); + return { + size, + name, + backgroundJob: + backgroundJobs.length === 1 + ? backgroundJobs[0] + : Promise.all(backgroundJobs) + }; +}; + +/** + * @param {FileMiddleware} middleware this + * @param {string | false} name filename + * @param {function(string | false): Promise} readFile read content of a file + * @returns {Promise} deserialized data + */ +const deserialize = async (middleware, name, readFile) => { + const contents = await readFile(name); + if (contents.length === 0) throw new Error(`Empty file ${name}`); + let contentsIndex = 0; + let contentItem = contents[0]; + let contentItemLength = contentItem.length; + let contentPosition = 0; + if (contentItemLength === 0) throw new Error(`Empty file ${name}`); + const nextContent = () => { + contentsIndex++; + contentItem = contents[contentsIndex]; + contentItemLength = contentItem.length; + contentPosition = 0; + }; + /** + * @param {number} n number of bytes to ensure + */ + const ensureData = n => { + if (contentPosition === contentItemLength) { + nextContent(); + } + while (contentItemLength - contentPosition < n) { + const remaining = contentItem.slice(contentPosition); + let lengthFromNext = n - remaining.length; + const buffers = [remaining]; + for (let i = contentsIndex + 1; i < contents.length; i++) { + const l = contents[i].length; + if (l > lengthFromNext) { + buffers.push(contents[i].slice(0, lengthFromNext)); + contents[i] = contents[i].slice(lengthFromNext); + lengthFromNext = 0; + break; + } else { + buffers.push(contents[i]); + contentsIndex = i; + lengthFromNext -= l; + } + } + if (lengthFromNext > 0) throw new Error("Unexpected end of data"); + contentItem = Buffer.concat(buffers, n); + contentItemLength = n; + contentPosition = 0; + } + }; + /** + * @returns {number} value value + */ + const readUInt32LE = () => { + ensureData(4); + const value = contentItem.readUInt32LE(contentPosition); + contentPosition += 4; + return value; + }; + /** + * @returns {number} value value + */ + const readInt32LE = () => { + ensureData(4); + const value = contentItem.readInt32LE(contentPosition); + contentPosition += 4; + return value; + }; + /** + * @param {number} l length + * @returns {Buffer} buffer + */ + const readSlice = l => { + ensureData(l); + if (contentPosition === 0 && contentItemLength === l) { + const result = contentItem; + if (contentsIndex + 1 < contents.length) { + nextContent(); + } else { + contentPosition = l; + } + return result; + } + const result = contentItem.slice(contentPosition, contentPosition + l); + contentPosition += l; + // we clone the buffer here to allow the original content to be garbage collected + return l * 2 < contentItem.buffer.byteLength ? Buffer.from(result) : result; + }; + const version = readUInt32LE(); + if (version !== VERSION) { + throw new Error("Invalid file version"); + } + const sectionCount = readUInt32LE(); + const lengths = []; + let lastLengthPositive = false; + for (let i = 0; i < sectionCount; i++) { + const value = readInt32LE(); + const valuePositive = value >= 0; + if (lastLengthPositive && valuePositive) { + lengths[lengths.length - 1] += value; + } else { + lengths.push(value); + lastLengthPositive = valuePositive; + } + } + const result = []; + for (let length of lengths) { + if (length < 0) { + const slice = readSlice(-length); + const size = Number(readUInt64LE(slice, 0)); + const nameBuffer = slice.slice(8); + const name = nameBuffer.toString(); + result.push( + SerializerMiddleware.createLazy( + memoize(() => deserialize(middleware, name, readFile)), + middleware, + { + name, + size + }, + slice + ) + ); + } else { + if (contentPosition === contentItemLength) { + nextContent(); + } else if (contentPosition !== 0) { + if (length <= contentItemLength - contentPosition) { + result.push( + Buffer.from( + contentItem.buffer, + contentItem.byteOffset + contentPosition, + length + ) + ); + contentPosition += length; + length = 0; + } else { + const l = contentItemLength - contentPosition; + result.push( + Buffer.from( + contentItem.buffer, + contentItem.byteOffset + contentPosition, + l + ) + ); + length -= l; + contentPosition = contentItemLength; + } + } else if (length >= contentItemLength) { + result.push(contentItem); + length -= contentItemLength; + contentPosition = contentItemLength; + } else { + result.push( + Buffer.from(contentItem.buffer, contentItem.byteOffset, length) + ); + contentPosition += length; + length = 0; + } + while (length > 0) { + nextContent(); + if (length >= contentItemLength) { + result.push(contentItem); + length -= contentItemLength; + contentPosition = contentItemLength; + } else { + result.push( + Buffer.from(contentItem.buffer, contentItem.byteOffset, length) + ); + contentPosition += length; + length = 0; + } + } + } + } + return result; +}; + +/** @typedef {{ filename: string, extension?: string }} FileMiddlewareContext */ + +/** + * @typedef {BufferSerializableType[]} DeserializedType + * @typedef {true} SerializedType + * @extends {SerializerMiddleware} + */ +class FileMiddleware extends SerializerMiddleware { + /** + * @param {IntermediateFileSystem} fs filesystem + * @param {string | Hash} hashFunction hash function to use + */ + constructor(fs, hashFunction = "md4") { + super(); + this.fs = fs; + this._hashFunction = hashFunction; + } + + /** + * @param {DeserializedType} data data + * @param {object} context context object + * @returns {SerializedType|Promise} serialized data + */ + serialize(data, context) { + const { filename, extension = "" } = context; + return new Promise((resolve, reject) => { + mkdirp(this.fs, dirname(this.fs, filename), err => { + if (err) return reject(err); + + // It's important that we don't touch existing files during serialization + // because serialize may read existing files (when deserializing) + const allWrittenFiles = new Set(); + /** + * @param {string | false} name name + * @param {Buffer[]} content content + * @param {number} size size + * @returns {Promise} + */ + const writeFile = async (name, content, size) => { + const file = name + ? join(this.fs, filename, `../${name}${extension}`) + : filename; + await new Promise( + /** + * @param {(value?: undefined) => void} resolve resolve + * @param {(reason?: Error | null) => void} reject reject + */ + (resolve, reject) => { + let stream = this.fs.createWriteStream(`${file}_`); + let compression; + if (file.endsWith(".gz")) { + compression = createGzip({ + chunkSize: COMPRESSION_CHUNK_SIZE, + level: zConstants.Z_BEST_SPEED + }); + } else if (file.endsWith(".br")) { + compression = createBrotliCompress({ + chunkSize: COMPRESSION_CHUNK_SIZE, + params: { + [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT, + [zConstants.BROTLI_PARAM_QUALITY]: 2, + [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true, + [zConstants.BROTLI_PARAM_SIZE_HINT]: size + } + }); + } + if (compression) { + pipeline(compression, stream, reject); + stream = compression; + stream.on("finish", () => resolve()); + } else { + stream.on("error", err => reject(err)); + stream.on("finish", () => resolve()); + } + // split into chunks for WRITE_LIMIT_CHUNK size + /** @type {TODO[]} */ + const chunks = []; + for (const b of content) { + if (b.length < WRITE_LIMIT_CHUNK) { + chunks.push(b); + } else { + for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) { + chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK)); + } + } + } + + const len = chunks.length; + let i = 0; + /** + * @param {(Error | null)=} err err + */ + const batchWrite = err => { + // will be handled in "on" error handler + if (err) return; + + if (i === len) { + stream.end(); + return; + } + + // queue up a batch of chunks up to the write limit + // end is exclusive + let end = i; + let sum = chunks[end++].length; + while (end < len) { + sum += chunks[end].length; + if (sum > WRITE_LIMIT_TOTAL) break; + end++; + } + while (i < end - 1) { + stream.write(chunks[i++]); + } + stream.write(chunks[i++], batchWrite); + }; + batchWrite(); + } + ); + if (name) allWrittenFiles.add(file); + }; + + resolve( + serialize(this, data, false, writeFile, this._hashFunction).then( + async ({ backgroundJob }) => { + await backgroundJob; + + // Rename the index file to disallow access during inconsistent file state + await new Promise( + /** + * @param {(value?: undefined) => void} resolve resolve + */ + resolve => { + this.fs.rename(filename, `${filename}.old`, err => { + resolve(); + }); + } + ); + + // update all written files + await Promise.all( + Array.from( + allWrittenFiles, + file => + new Promise( + /** + * @param {(value?: undefined) => void} resolve resolve + * @param {(reason?: Error | null) => void} reject reject + * @returns {void} + */ + (resolve, reject) => { + this.fs.rename(`${file}_`, file, err => { + if (err) return reject(err); + resolve(); + }); + } + ) + ) + ); + + // As final step automatically update the index file to have a consistent pack again + await new Promise( + /** + * @param {(value?: undefined) => void} resolve resolve + * @returns {void} + */ + resolve => { + this.fs.rename(`${filename}_`, filename, err => { + if (err) return reject(err); + resolve(); + }); + } + ); + return /** @type {true} */ (true); + } + ) + ); + }); + }); + } + + /** + * @param {SerializedType} data data + * @param {object} context context object + * @returns {DeserializedType|Promise} deserialized data + */ + deserialize(data, context) { + const { filename, extension = "" } = context; + /** + * @param {string | boolean} name name + * @returns {Promise} result + */ + const readFile = name => + new Promise((resolve, reject) => { + const file = name + ? join(this.fs, filename, `../${name}${extension}`) + : filename; + this.fs.stat(file, (err, stats) => { + if (err) { + reject(err); + return; + } + let remaining = /** @type {IStats} */ (stats).size; + /** @type {Buffer | undefined} */ + let currentBuffer; + /** @type {number | undefined} */ + let currentBufferUsed; + /** @type {any[]} */ + const buf = []; + /** @type {import("zlib").Zlib & import("stream").Transform | undefined} */ + let decompression; + if (file.endsWith(".gz")) { + decompression = createGunzip({ + chunkSize: DECOMPRESSION_CHUNK_SIZE + }); + } else if (file.endsWith(".br")) { + decompression = createBrotliDecompress({ + chunkSize: DECOMPRESSION_CHUNK_SIZE + }); + } + if (decompression) { + let newResolve; + let newReject; + resolve( + Promise.all([ + new Promise((rs, rj) => { + newResolve = rs; + newReject = rj; + }), + new Promise((resolve, reject) => { + decompression.on("data", chunk => buf.push(chunk)); + decompression.on("end", () => resolve()); + decompression.on("error", err => reject(err)); + }) + ]).then(() => buf) + ); + resolve = newResolve; + reject = newReject; + } + this.fs.open(file, "r", (err, _fd) => { + if (err) { + reject(err); + return; + } + const fd = /** @type {number} */ (_fd); + const read = () => { + if (currentBuffer === undefined) { + currentBuffer = Buffer.allocUnsafeSlow( + Math.min( + constants.MAX_LENGTH, + remaining, + decompression ? DECOMPRESSION_CHUNK_SIZE : Infinity + ) + ); + currentBufferUsed = 0; + } + let readBuffer = currentBuffer; + let readOffset = /** @type {number} */ (currentBufferUsed); + let readLength = + currentBuffer.length - + /** @type {number} */ (currentBufferUsed); + // values passed to fs.read must be valid int32 values + if (readOffset > 0x7fffffff) { + readBuffer = currentBuffer.slice(readOffset); + readOffset = 0; + } + if (readLength > 0x7fffffff) { + readLength = 0x7fffffff; + } + this.fs.read( + fd, + readBuffer, + readOffset, + readLength, + null, + (err, bytesRead) => { + if (err) { + this.fs.close(fd, () => { + reject(err); + }); + return; + } + /** @type {number} */ + (currentBufferUsed) += bytesRead; + remaining -= bytesRead; + if ( + currentBufferUsed === + /** @type {Buffer} */ (currentBuffer).length + ) { + if (decompression) { + decompression.write(currentBuffer); + } else { + buf.push(currentBuffer); + } + currentBuffer = undefined; + if (remaining === 0) { + if (decompression) { + decompression.end(); + } + this.fs.close(fd, err => { + if (err) { + reject(err); + return; + } + resolve(buf); + }); + return; + } + } + read(); + } + ); + }; + read(); + }); + }); + }); + return deserialize(this, false, readFile); + } +} + +module.exports = FileMiddleware; diff --git a/webpack-lib/lib/serialization/MapObjectSerializer.js b/webpack-lib/lib/serialization/MapObjectSerializer.js new file mode 100644 index 00000000000..0b1f4182b96 --- /dev/null +++ b/webpack-lib/lib/serialization/MapObjectSerializer.js @@ -0,0 +1,48 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class MapObjectSerializer { + /** + * @template K, V + * @param {Map} obj map + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + context.write(obj.size); + for (const key of obj.keys()) { + context.write(key); + } + for (const value of obj.values()) { + context.write(value); + } + } + + /** + * @template K, V + * @param {ObjectDeserializerContext} context context + * @returns {Map} map + */ + deserialize(context) { + /** @type {number} */ + const size = context.read(); + /** @type {Map} */ + const map = new Map(); + /** @type {K[]} */ + const keys = []; + for (let i = 0; i < size; i++) { + keys.push(context.read()); + } + for (let i = 0; i < size; i++) { + map.set(keys[i], context.read()); + } + return map; + } +} + +module.exports = MapObjectSerializer; diff --git a/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js b/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js new file mode 100644 index 00000000000..32adaeaaede --- /dev/null +++ b/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js @@ -0,0 +1,51 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class NullPrototypeObjectSerializer { + /** + * @template {object} T + * @param {T} obj null object + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + /** @type {string[]} */ + const keys = Object.keys(obj); + for (const key of keys) { + context.write(key); + } + context.write(null); + for (const key of keys) { + context.write(obj[/** @type {keyof T} */ (key)]); + } + } + + /** + * @template {object} T + * @param {ObjectDeserializerContext} context context + * @returns {T} null object + */ + deserialize(context) { + /** @type {T} */ + const obj = Object.create(null); + /** @type {string[]} */ + const keys = []; + /** @type {string | null} */ + let key = context.read(); + while (key !== null) { + keys.push(key); + key = context.read(); + } + for (const key of keys) { + obj[/** @type {keyof T} */ (key)] = context.read(); + } + return obj; + } +} + +module.exports = NullPrototypeObjectSerializer; diff --git a/webpack-lib/lib/serialization/ObjectMiddleware.js b/webpack-lib/lib/serialization/ObjectMiddleware.js new file mode 100644 index 00000000000..13464478199 --- /dev/null +++ b/webpack-lib/lib/serialization/ObjectMiddleware.js @@ -0,0 +1,778 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const createHash = require("../util/createHash"); +const ArraySerializer = require("./ArraySerializer"); +const DateObjectSerializer = require("./DateObjectSerializer"); +const ErrorObjectSerializer = require("./ErrorObjectSerializer"); +const MapObjectSerializer = require("./MapObjectSerializer"); +const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer"); +const PlainObjectSerializer = require("./PlainObjectSerializer"); +const RegExpObjectSerializer = require("./RegExpObjectSerializer"); +const SerializerMiddleware = require("./SerializerMiddleware"); +const SetObjectSerializer = require("./SetObjectSerializer"); + +/** @typedef {typeof import("../util/Hash")} Hash */ +/** @typedef {import("./types").ComplexSerializableType} ComplexSerializableType */ +/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */ + +/** @typedef {new (...params: any[]) => any} Constructor */ + +/* + +Format: + +File -> Section* +Section -> ObjectSection | ReferenceSection | EscapeSection | OtherSection + +ObjectSection -> ESCAPE ( + number:relativeOffset (number > 0) | + string:request (string|null):export +) Section:value* ESCAPE ESCAPE_END_OBJECT +ReferenceSection -> ESCAPE number:relativeOffset (number < 0) +EscapeSection -> ESCAPE ESCAPE_ESCAPE_VALUE (escaped value ESCAPE) +EscapeSection -> ESCAPE ESCAPE_UNDEFINED (escaped value ESCAPE) +OtherSection -> any (except ESCAPE) + +Why using null as escape value? +Multiple null values can merged by the BinaryMiddleware, which makes it very efficient +Technically any value can be used. + +*/ + +/** + * @typedef {object} ObjectSerializerContext + * @property {function(any): void} write + * @property {(function(any): void)=} writeLazy + * @property {(function(any, object=): (() => Promise | any))=} writeSeparate + * @property {function(any): void} setCircularReference + */ + +/** + * @typedef {object} ObjectDeserializerContext + * @property {function(): any} read + * @property {function(any): void} setCircularReference + */ + +/** + * @typedef {object} ObjectSerializer + * @property {function(any, ObjectSerializerContext): void} serialize + * @property {function(ObjectDeserializerContext): any} deserialize + */ + +/** + * @template T + * @param {Set} set set + * @param {number} size count of items to keep + */ +const setSetSize = (set, size) => { + let i = 0; + for (const item of set) { + if (i++ >= size) { + set.delete(item); + } + } +}; + +/** + * @template K, X + * @param {Map} map map + * @param {number} size count of items to keep + */ +const setMapSize = (map, size) => { + let i = 0; + for (const item of map.keys()) { + if (i++ >= size) { + map.delete(item); + } + } +}; + +/** + * @param {Buffer} buffer buffer + * @param {string | Hash} hashFunction hash function to use + * @returns {string} hash + */ +const toHash = (buffer, hashFunction) => { + const hash = createHash(hashFunction); + hash.update(buffer); + return /** @type {string} */ (hash.digest("latin1")); +}; + +const ESCAPE = null; +const ESCAPE_ESCAPE_VALUE = null; +const ESCAPE_END_OBJECT = true; +const ESCAPE_UNDEFINED = false; + +const CURRENT_VERSION = 2; + +/** @type {Map} */ +const serializers = new Map(); +/** @type {Map} */ +const serializerInversed = new Map(); + +/** @type {Set} */ +const loadedRequests = new Set(); + +const NOT_SERIALIZABLE = {}; + +const jsTypes = new Map(); +jsTypes.set(Object, new PlainObjectSerializer()); +jsTypes.set(Array, new ArraySerializer()); +jsTypes.set(null, new NullPrototypeObjectSerializer()); +jsTypes.set(Map, new MapObjectSerializer()); +jsTypes.set(Set, new SetObjectSerializer()); +jsTypes.set(Date, new DateObjectSerializer()); +jsTypes.set(RegExp, new RegExpObjectSerializer()); +jsTypes.set(Error, new ErrorObjectSerializer(Error)); +jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError)); +jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError)); +jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError)); +jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError)); +jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError)); + +// If in a sandboxed environment (e. g. jest), this escapes the sandbox and registers +// real Object and Array types to. These types may occur in the wild too, e. g. when +// using Structured Clone in postMessage. +// eslint-disable-next-line n/exports-style +if (exports.constructor !== Object) { + // eslint-disable-next-line jsdoc/check-types, n/exports-style + const Obj = /** @type {typeof Object} */ (exports.constructor); + const Fn = /** @type {typeof Function} */ (Obj.constructor); + for (const [type, config] of Array.from(jsTypes)) { + if (type) { + const Type = new Fn(`return ${type.name};`)(); + jsTypes.set(Type, config); + } + } +} + +{ + let i = 1; + for (const [type, serializer] of jsTypes) { + serializers.set(type, { + request: "", + name: i++, + serializer + }); + } +} + +for (const { request, name, serializer } of serializers.values()) { + serializerInversed.set( + `${request}/${name}`, + /** @type {ObjectSerializer} */ (serializer) + ); +} + +/** @type {Map boolean>} */ +const loaders = new Map(); + +/** + * @typedef {ComplexSerializableType[]} DeserializedType + * @typedef {PrimitiveSerializableType[]} SerializedType + * @extends {SerializerMiddleware} + */ +class ObjectMiddleware extends SerializerMiddleware { + /** + * @param {function(any): void} extendContext context extensions + * @param {string | Hash} hashFunction hash function to use + */ + constructor(extendContext, hashFunction = "md4") { + super(); + this.extendContext = extendContext; + this._hashFunction = hashFunction; + } + + /** + * @param {RegExp} regExp RegExp for which the request is tested + * @param {function(string): boolean} loader loader to load the request, returns true when successful + * @returns {void} + */ + static registerLoader(regExp, loader) { + loaders.set(regExp, loader); + } + + /** + * @param {Constructor} Constructor the constructor + * @param {string} request the request which will be required when deserializing + * @param {string | null} name the name to make multiple serializer unique when sharing a request + * @param {ObjectSerializer} serializer the serializer + * @returns {void} + */ + static register(Constructor, request, name, serializer) { + const key = `${request}/${name}`; + + if (serializers.has(Constructor)) { + throw new Error( + `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered` + ); + } + + if (serializerInversed.has(key)) { + throw new Error( + `ObjectMiddleware.register: serializer for ${key} is already registered` + ); + } + + serializers.set(Constructor, { + request, + name, + serializer + }); + + serializerInversed.set(key, serializer); + } + + /** + * @param {Constructor} Constructor the constructor + * @returns {void} + */ + static registerNotSerializable(Constructor) { + if (serializers.has(Constructor)) { + throw new Error( + `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered` + ); + } + + serializers.set(Constructor, NOT_SERIALIZABLE); + } + + static getSerializerFor(object) { + const proto = Object.getPrototypeOf(object); + let c; + if (proto === null) { + // Object created with Object.create(null) + c = null; + } else { + c = proto.constructor; + if (!c) { + throw new Error( + "Serialization of objects with prototype without valid constructor property not possible" + ); + } + } + const config = serializers.get(c); + + if (!config) throw new Error(`No serializer registered for ${c.name}`); + if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE; + + return config; + } + + /** + * @param {string} request request + * @param {TODO} name name + * @returns {ObjectSerializer} serializer + */ + static getDeserializerFor(request, name) { + const key = `${request}/${name}`; + const serializer = serializerInversed.get(key); + + if (serializer === undefined) { + throw new Error(`No deserializer registered for ${key}`); + } + + return serializer; + } + + /** + * @param {string} request request + * @param {string} name name + * @returns {ObjectSerializer | undefined} serializer + */ + static _getDeserializerForWithoutError(request, name) { + const key = `${request}/${name}`; + const serializer = serializerInversed.get(key); + return serializer; + } + + /** + * @param {DeserializedType} data data + * @param {object} context context object + * @returns {SerializedType|Promise} serialized data + */ + serialize(data, context) { + /** @type {any[]} */ + let result = [CURRENT_VERSION]; + let currentPos = 0; + let referenceable = new Map(); + const addReferenceable = item => { + referenceable.set(item, currentPos++); + }; + let bufferDedupeMap = new Map(); + /** + * @param {Buffer} buf buffer + * @returns {Buffer} deduped buffer + */ + const dedupeBuffer = buf => { + const len = buf.length; + const entry = bufferDedupeMap.get(len); + if (entry === undefined) { + bufferDedupeMap.set(len, buf); + return buf; + } + if (Buffer.isBuffer(entry)) { + if (len < 32) { + if (buf.equals(entry)) { + return entry; + } + bufferDedupeMap.set(len, [entry, buf]); + return buf; + } + const hash = toHash(entry, this._hashFunction); + const newMap = new Map(); + newMap.set(hash, entry); + bufferDedupeMap.set(len, newMap); + const hashBuf = toHash(buf, this._hashFunction); + if (hash === hashBuf) { + return entry; + } + return buf; + } else if (Array.isArray(entry)) { + if (entry.length < 16) { + for (const item of entry) { + if (buf.equals(item)) { + return item; + } + } + entry.push(buf); + return buf; + } + const newMap = new Map(); + const hash = toHash(buf, this._hashFunction); + let found; + for (const item of entry) { + const itemHash = toHash(item, this._hashFunction); + newMap.set(itemHash, item); + if (found === undefined && itemHash === hash) found = item; + } + bufferDedupeMap.set(len, newMap); + if (found === undefined) { + newMap.set(hash, buf); + return buf; + } + return found; + } + const hash = toHash(buf, this._hashFunction); + const item = entry.get(hash); + if (item !== undefined) { + return item; + } + entry.set(hash, buf); + return buf; + }; + let currentPosTypeLookup = 0; + let objectTypeLookup = new Map(); + const cycleStack = new Set(); + const stackToString = item => { + const arr = Array.from(cycleStack); + arr.push(item); + return arr + .map(item => { + if (typeof item === "string") { + if (item.length > 100) { + return `String ${JSON.stringify(item.slice(0, 100)).slice( + 0, + -1 + )}..."`; + } + return `String ${JSON.stringify(item)}`; + } + try { + const { request, name } = ObjectMiddleware.getSerializerFor(item); + if (request) { + return `${request}${name ? `.${name}` : ""}`; + } + } catch (_err) { + // ignore -> fallback + } + if (typeof item === "object" && item !== null) { + if (item.constructor) { + if (item.constructor === Object) + return `Object { ${Object.keys(item).join(", ")} }`; + if (item.constructor === Map) return `Map { ${item.size} items }`; + if (item.constructor === Array) + return `Array { ${item.length} items }`; + if (item.constructor === Set) return `Set { ${item.size} items }`; + if (item.constructor === RegExp) return item.toString(); + return `${item.constructor.name}`; + } + return `Object [null prototype] { ${Object.keys(item).join( + ", " + )} }`; + } + if (typeof item === "bigint") { + return `BigInt ${item}n`; + } + try { + return `${item}`; + } catch (err) { + return `(${err.message})`; + } + }) + .join(" -> "); + }; + /** @type {WeakSet} */ + let hasDebugInfoAttached; + let ctx = { + write(value, key) { + try { + process(value); + } catch (err) { + if (err !== NOT_SERIALIZABLE) { + if (hasDebugInfoAttached === undefined) + hasDebugInfoAttached = new WeakSet(); + if (!hasDebugInfoAttached.has(/** @type {Error} */ (err))) { + /** @type {Error} */ + (err).message += `\nwhile serializing ${stackToString(value)}`; + hasDebugInfoAttached.add(/** @type {Error} */ (err)); + } + } + throw err; + } + }, + setCircularReference(ref) { + addReferenceable(ref); + }, + snapshot() { + return { + length: result.length, + cycleStackSize: cycleStack.size, + referenceableSize: referenceable.size, + currentPos, + objectTypeLookupSize: objectTypeLookup.size, + currentPosTypeLookup + }; + }, + rollback(snapshot) { + result.length = snapshot.length; + setSetSize(cycleStack, snapshot.cycleStackSize); + setMapSize(referenceable, snapshot.referenceableSize); + currentPos = snapshot.currentPos; + setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize); + currentPosTypeLookup = snapshot.currentPosTypeLookup; + }, + ...context + }; + this.extendContext(ctx); + const process = item => { + if (Buffer.isBuffer(item)) { + // check if we can emit a reference + const ref = referenceable.get(item); + if (ref !== undefined) { + result.push(ESCAPE, ref - currentPos); + return; + } + const alreadyUsedBuffer = dedupeBuffer(item); + if (alreadyUsedBuffer !== item) { + const ref = referenceable.get(alreadyUsedBuffer); + if (ref !== undefined) { + referenceable.set(item, ref); + result.push(ESCAPE, ref - currentPos); + return; + } + item = alreadyUsedBuffer; + } + addReferenceable(item); + + result.push(item); + } else if (item === ESCAPE) { + result.push(ESCAPE, ESCAPE_ESCAPE_VALUE); + } else if ( + typeof item === "object" + // We don't have to check for null as ESCAPE is null and this has been checked before + ) { + // check if we can emit a reference + const ref = referenceable.get(item); + if (ref !== undefined) { + result.push(ESCAPE, ref - currentPos); + return; + } + + if (cycleStack.has(item)) { + throw new Error( + "This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize." + ); + } + + const { request, name, serializer } = + ObjectMiddleware.getSerializerFor(item); + const key = `${request}/${name}`; + const lastIndex = objectTypeLookup.get(key); + + if (lastIndex === undefined) { + objectTypeLookup.set(key, currentPosTypeLookup++); + + result.push(ESCAPE, request, name); + } else { + result.push(ESCAPE, currentPosTypeLookup - lastIndex); + } + + cycleStack.add(item); + + try { + serializer.serialize(item, ctx); + } finally { + cycleStack.delete(item); + } + + result.push(ESCAPE, ESCAPE_END_OBJECT); + + addReferenceable(item); + } else if (typeof item === "string") { + if (item.length > 1) { + // short strings are shorter when not emitting a reference (this saves 1 byte per empty string) + // check if we can emit a reference + const ref = referenceable.get(item); + if (ref !== undefined) { + result.push(ESCAPE, ref - currentPos); + return; + } + addReferenceable(item); + } + + if (item.length > 102400 && context.logger) { + context.logger.warn( + `Serializing big strings (${Math.round( + item.length / 1024 + )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)` + ); + } + + result.push(item); + } else if (typeof item === "function") { + if (!SerializerMiddleware.isLazy(item)) + throw new Error(`Unexpected function ${item}`); + /** @type {SerializedType} */ + const serializedData = + SerializerMiddleware.getLazySerializedValue(item); + if (serializedData !== undefined) { + if (typeof serializedData === "function") { + result.push(serializedData); + } else { + throw new Error("Not implemented"); + } + } else if (SerializerMiddleware.isLazy(item, this)) { + throw new Error("Not implemented"); + } else { + const data = SerializerMiddleware.serializeLazy(item, data => + this.serialize([data], context) + ); + SerializerMiddleware.setLazySerializedValue(item, data); + result.push(data); + } + } else if (item === undefined) { + result.push(ESCAPE, ESCAPE_UNDEFINED); + } else { + result.push(item); + } + }; + + try { + for (const item of data) { + process(item); + } + return result; + } catch (err) { + if (err === NOT_SERIALIZABLE) return null; + + throw err; + } finally { + // Get rid of these references to avoid leaking memory + // This happens because the optimized code v8 generates + // is optimized for our "ctx.write" method so it will reference + // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write + data = + result = + referenceable = + bufferDedupeMap = + objectTypeLookup = + ctx = + /** @type {EXPECTED_ANY} */ + (undefined); + } + } + + /** + * @param {SerializedType} data data + * @param {object} context context object + * @returns {DeserializedType|Promise} deserialized data + */ + deserialize(data, context) { + let currentDataPos = 0; + const read = () => { + if (currentDataPos >= data.length) + throw new Error("Unexpected end of stream"); + + return data[currentDataPos++]; + }; + + if (read() !== CURRENT_VERSION) + throw new Error("Version mismatch, serializer changed"); + + let currentPos = 0; + let referenceable = []; + const addReferenceable = item => { + referenceable.push(item); + currentPos++; + }; + let currentPosTypeLookup = 0; + let objectTypeLookup = []; + let result = []; + let ctx = { + read() { + return decodeValue(); + }, + setCircularReference(ref) { + addReferenceable(ref); + }, + ...context + }; + this.extendContext(ctx); + const decodeValue = () => { + const item = read(); + + if (item === ESCAPE) { + const nextItem = read(); + + if (nextItem === ESCAPE_ESCAPE_VALUE) { + return ESCAPE; + } else if (nextItem === ESCAPE_UNDEFINED) { + // Nothing + } else if (nextItem === ESCAPE_END_OBJECT) { + throw new Error( + `Unexpected end of object at position ${currentDataPos - 1}` + ); + } else { + const request = nextItem; + let serializer; + + if (typeof request === "number") { + if (request < 0) { + // relative reference + return referenceable[currentPos + request]; + } + serializer = objectTypeLookup[currentPosTypeLookup - request]; + } else { + if (typeof request !== "string") { + throw new Error( + `Unexpected type (${typeof request}) of request ` + + `at position ${currentDataPos - 1}` + ); + } + const name = /** @type {string} */ (read()); + + serializer = ObjectMiddleware._getDeserializerForWithoutError( + request, + name + ); + + if (serializer === undefined) { + if (request && !loadedRequests.has(request)) { + let loaded = false; + for (const [regExp, loader] of loaders) { + if (regExp.test(request) && loader(request)) { + loaded = true; + break; + } + } + if (!loaded) { + require(request); + } + + loadedRequests.add(request); + } + + serializer = ObjectMiddleware.getDeserializerFor(request, name); + } + + objectTypeLookup.push(serializer); + currentPosTypeLookup++; + } + try { + const item = serializer.deserialize(ctx); + const end1 = read(); + + if (end1 !== ESCAPE) { + throw new Error("Expected end of object"); + } + + const end2 = read(); + + if (end2 !== ESCAPE_END_OBJECT) { + throw new Error("Expected end of object"); + } + + addReferenceable(item); + + return item; + } catch (err) { + // As this is only for error handling, we omit creating a Map for + // faster access to this information, as this would affect performance + // in the good case + let serializerEntry; + for (const entry of serializers) { + if (entry[1].serializer === serializer) { + serializerEntry = entry; + break; + } + } + const name = !serializerEntry + ? "unknown" + : !serializerEntry[1].request + ? serializerEntry[0].name + : serializerEntry[1].name + ? `${serializerEntry[1].request} ${serializerEntry[1].name}` + : serializerEntry[1].request; + /** @type {Error} */ + (err).message += `\n(during deserialization of ${name})`; + throw err; + } + } + } else if (typeof item === "string") { + if (item.length > 1) { + addReferenceable(item); + } + + return item; + } else if (Buffer.isBuffer(item)) { + addReferenceable(item); + + return item; + } else if (typeof item === "function") { + return SerializerMiddleware.deserializeLazy( + item, + data => this.deserialize(data, context)[0] + ); + } else { + return item; + } + }; + + try { + while (currentDataPos < data.length) { + result.push(decodeValue()); + } + return result; + } finally { + // Get rid of these references to avoid leaking memory + // This happens because the optimized code v8 generates + // is optimized for our "ctx.read" method so it will reference + // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read + result = + referenceable = + data = + objectTypeLookup = + ctx = + /** @type {EXPECTED_ANY} */ + (undefined); + } + } +} + +module.exports = ObjectMiddleware; +module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE; diff --git a/webpack-lib/lib/serialization/PlainObjectSerializer.js b/webpack-lib/lib/serialization/PlainObjectSerializer.js new file mode 100644 index 00000000000..428825e388e --- /dev/null +++ b/webpack-lib/lib/serialization/PlainObjectSerializer.js @@ -0,0 +1,117 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +/** @typedef {(arg0?: any) => void} CacheAssoc */ + +/** + * @template T + * @typedef {WeakMap>} + */ +const cache = new WeakMap(); + +/** + * @template T + */ +class ObjectStructure { + constructor() { + this.keys = undefined; + this.children = undefined; + } + + /** + * @param {keyof T[]} keys keys + * @returns {keyof T[]} keys + */ + getKeys(keys) { + if (this.keys === undefined) this.keys = keys; + return this.keys; + } + + /** + * @param {keyof T} key key + * @returns {ObjectStructure} object structure + */ + key(key) { + if (this.children === undefined) this.children = new Map(); + const child = this.children.get(key); + if (child !== undefined) return child; + const newChild = new ObjectStructure(); + this.children.set(key, newChild); + return newChild; + } +} + +/** + * @template T + * @param {(keyof T)[]} keys keys + * @param {CacheAssoc} cacheAssoc cache assoc fn + * @returns {(keyof T)[]} keys + */ +const getCachedKeys = (keys, cacheAssoc) => { + let root = cache.get(cacheAssoc); + if (root === undefined) { + root = new ObjectStructure(); + cache.set(cacheAssoc, root); + } + let current = root; + for (const key of keys) { + current = current.key(key); + } + return current.getKeys(keys); +}; + +class PlainObjectSerializer { + /** + * @template {object} T + * @param {T} obj plain object + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + const keys = /** @type {(keyof T)[]} */ (Object.keys(obj)); + if (keys.length > 128) { + // Objects with so many keys are unlikely to share structure + // with other objects + context.write(keys); + for (const key of keys) { + context.write(obj[key]); + } + } else if (keys.length > 1) { + context.write(getCachedKeys(keys, context.write)); + for (const key of keys) { + context.write(obj[key]); + } + } else if (keys.length === 1) { + const key = keys[0]; + context.write(key); + context.write(obj[key]); + } else { + context.write(null); + } + } + + /** + * @template {object} T + * @param {ObjectDeserializerContext} context context + * @returns {T} plain object + */ + deserialize(context) { + const keys = context.read(); + const obj = /** @type {T} */ ({}); + if (Array.isArray(keys)) { + for (const key of keys) { + obj[/** @type {keyof T} */ (key)] = context.read(); + } + } else if (keys !== null) { + obj[/** @type {keyof T} */ (keys)] = context.read(); + } + return obj; + } +} + +module.exports = PlainObjectSerializer; diff --git a/webpack-lib/lib/serialization/RegExpObjectSerializer.js b/webpack-lib/lib/serialization/RegExpObjectSerializer.js new file mode 100644 index 00000000000..5ac7eec5105 --- /dev/null +++ b/webpack-lib/lib/serialization/RegExpObjectSerializer.js @@ -0,0 +1,29 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class RegExpObjectSerializer { + /** + * @param {RegExp} obj regexp + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + context.write(obj.source); + context.write(obj.flags); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RegExp} regexp + */ + deserialize(context) { + return new RegExp(context.read(), context.read()); + } +} + +module.exports = RegExpObjectSerializer; diff --git a/webpack-lib/lib/serialization/Serializer.js b/webpack-lib/lib/serialization/Serializer.js new file mode 100644 index 00000000000..ce241c67047 --- /dev/null +++ b/webpack-lib/lib/serialization/Serializer.js @@ -0,0 +1,64 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** + * @template T, K + * @typedef {import("./SerializerMiddleware")} SerializerMiddleware + */ + +class Serializer { + /** + * @param {SerializerMiddleware[]} middlewares serializer middlewares + * @param {TODO=} context context + */ + constructor(middlewares, context) { + this.serializeMiddlewares = middlewares.slice(); + this.deserializeMiddlewares = middlewares.slice().reverse(); + this.context = context; + } + + /** + * @param {any} obj object + * @param {TODO} context content + * @returns {Promise} result + */ + serialize(obj, context) { + const ctx = { ...context, ...this.context }; + let current = obj; + for (const middleware of this.serializeMiddlewares) { + if (current && typeof current.then === "function") { + current = current.then(data => data && middleware.serialize(data, ctx)); + } else if (current) { + try { + current = middleware.serialize(current, ctx); + } catch (err) { + current = Promise.reject(err); + } + } else break; + } + return current; + } + + /** + * @param {any} value value + * @param {TODO} context context + * @returns {Promise} result + */ + deserialize(value, context) { + const ctx = { ...context, ...this.context }; + /** @type {any} */ + let current = value; + for (const middleware of this.deserializeMiddlewares) { + current = + current && typeof current.then === "function" + ? current.then(data => middleware.deserialize(data, ctx)) + : middleware.deserialize(current, ctx); + } + return current; + } +} + +module.exports = Serializer; diff --git a/webpack-lib/lib/serialization/SerializerMiddleware.js b/webpack-lib/lib/serialization/SerializerMiddleware.js new file mode 100644 index 00000000000..0053fb0b6b6 --- /dev/null +++ b/webpack-lib/lib/serialization/SerializerMiddleware.js @@ -0,0 +1,154 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const memoize = require("../util/memoize"); + +const LAZY_TARGET = Symbol("lazy serialization target"); +const LAZY_SERIALIZED_VALUE = Symbol("lazy serialization data"); + +/** + * @template DeserializedType + * @template SerializedType + */ +class SerializerMiddleware { + /* istanbul ignore next */ + /** + * @abstract + * @param {DeserializedType} data data + * @param {object} context context object + * @returns {SerializedType|Promise} serialized data + */ + serialize(data, context) { + const AbstractMethodError = require("../AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * @abstract + * @param {SerializedType} data data + * @param {object} context context object + * @returns {DeserializedType|Promise} deserialized data + */ + deserialize(data, context) { + const AbstractMethodError = require("../AbstractMethodError"); + throw new AbstractMethodError(); + } + + /** + * @param {any | function(): Promise | any} value contained value or function to value + * @param {SerializerMiddleware} target target middleware + * @param {object=} options lazy options + * @param {any=} serializedValue serialized value + * @returns {function(): Promise | any} lazy function + */ + static createLazy(value, target, options = {}, serializedValue = undefined) { + if (SerializerMiddleware.isLazy(value, target)) return value; + const fn = typeof value === "function" ? value : () => value; + fn[LAZY_TARGET] = target; + /** @type {any} */ (fn).options = options; + fn[LAZY_SERIALIZED_VALUE] = serializedValue; + return fn; + } + + /** + * @param {function(): Promise | any} fn lazy function + * @param {SerializerMiddleware=} target target middleware + * @returns {boolean} true, when fn is a lazy function (optionally of that target) + */ + static isLazy(fn, target) { + if (typeof fn !== "function") return false; + const t = fn[LAZY_TARGET]; + return target ? t === target : Boolean(t); + } + + /** + * @param {function(): Promise | any} fn lazy function + * @returns {object | undefined} options + */ + static getLazyOptions(fn) { + if (typeof fn !== "function") return; + return /** @type {any} */ (fn).options; + } + + /** + * @param {function(): Promise | any} fn lazy function + * @returns {any | undefined} serialized value + */ + static getLazySerializedValue(fn) { + if (typeof fn !== "function") return; + return fn[LAZY_SERIALIZED_VALUE]; + } + + /** + * @param {function(): Promise | any} fn lazy function + * @param {any} value serialized value + * @returns {void} + */ + static setLazySerializedValue(fn, value) { + fn[LAZY_SERIALIZED_VALUE] = value; + } + + /** + * @param {function(): Promise | any} lazy lazy function + * @param {function(any): Promise | any} serialize serialize function + * @returns {function(): Promise | any} new lazy + */ + static serializeLazy(lazy, serialize) { + const fn = memoize(() => { + const r = lazy(); + if (r && typeof r.then === "function") { + return r.then(data => data && serialize(data)); + } + return serialize(r); + }); + fn[LAZY_TARGET] = lazy[LAZY_TARGET]; + /** @type {any} */ (fn).options = /** @type {any} */ (lazy).options; + lazy[LAZY_SERIALIZED_VALUE] = fn; + return fn; + } + + /** + * @template T + * @param {function(): Promise | any} lazy lazy function + * @param {function(T): Promise | T} deserialize deserialize function + * @returns {function(): Promise | T} new lazy + */ + static deserializeLazy(lazy, deserialize) { + const fn = memoize(() => { + const r = lazy(); + if (r && typeof r.then === "function") { + return r.then(data => deserialize(data)); + } + return deserialize(r); + }); + fn[LAZY_TARGET] = lazy[LAZY_TARGET]; + /** @type {any} */ (fn).options = /** @type {any} */ (lazy).options; + fn[LAZY_SERIALIZED_VALUE] = lazy; + return fn; + } + + /** + * @param {function(): Promise | any} lazy lazy function + * @returns {function(): Promise | any} new lazy + */ + static unMemoizeLazy(lazy) { + if (!SerializerMiddleware.isLazy(lazy)) return lazy; + const fn = () => { + throw new Error( + "A lazy value that has been unmemorized can't be called again" + ); + }; + fn[LAZY_SERIALIZED_VALUE] = SerializerMiddleware.unMemoizeLazy( + lazy[LAZY_SERIALIZED_VALUE] + ); + fn[LAZY_TARGET] = lazy[LAZY_TARGET]; + fn.options = /** @type {any} */ (lazy).options; + return fn; + } +} + +module.exports = SerializerMiddleware; diff --git a/webpack-lib/lib/serialization/SetObjectSerializer.js b/webpack-lib/lib/serialization/SetObjectSerializer.js new file mode 100644 index 00000000000..66811b87d16 --- /dev/null +++ b/webpack-lib/lib/serialization/SetObjectSerializer.js @@ -0,0 +1,40 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class SetObjectSerializer { + /** + * @template T + * @param {Set} obj set + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + context.write(obj.size); + for (const value of obj) { + context.write(value); + } + } + + /** + * @template T + * @param {ObjectDeserializerContext} context context + * @returns {Set} date + */ + deserialize(context) { + /** @type {number} */ + const size = context.read(); + /** @type {Set} */ + const set = new Set(); + for (let i = 0; i < size; i++) { + set.add(context.read()); + } + return set; + } +} + +module.exports = SetObjectSerializer; diff --git a/webpack-lib/lib/serialization/SingleItemMiddleware.js b/webpack-lib/lib/serialization/SingleItemMiddleware.js new file mode 100644 index 00000000000..faf95de6a17 --- /dev/null +++ b/webpack-lib/lib/serialization/SingleItemMiddleware.js @@ -0,0 +1,34 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const SerializerMiddleware = require("./SerializerMiddleware"); + +/** + * @typedef {any} DeserializedType + * @typedef {any[]} SerializedType + * @extends {SerializerMiddleware} + */ +class SingleItemMiddleware extends SerializerMiddleware { + /** + * @param {DeserializedType} data data + * @param {object} context context object + * @returns {SerializedType|Promise} serialized data + */ + serialize(data, context) { + return [data]; + } + + /** + * @param {SerializedType} data data + * @param {object} context context object + * @returns {DeserializedType|Promise} deserialized data + */ + deserialize(data, context) { + return data[0]; + } +} + +module.exports = SingleItemMiddleware; diff --git a/webpack-lib/lib/serialization/types.js b/webpack-lib/lib/serialization/types.js new file mode 100644 index 00000000000..b0f022f20d1 --- /dev/null +++ b/webpack-lib/lib/serialization/types.js @@ -0,0 +1,13 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @typedef {undefined | null | number | string | boolean | Buffer | object | (() => ComplexSerializableType[] | Promise)} ComplexSerializableType */ + +/** @typedef {undefined | null | number | bigint | string | boolean | Buffer | (() => PrimitiveSerializableType[] | Promise)} PrimitiveSerializableType */ + +/** @typedef {Buffer | (() => BufferSerializableType[] | Promise)} BufferSerializableType */ + +module.exports = {}; diff --git a/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js b/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js new file mode 100644 index 00000000000..bd6eefef13f --- /dev/null +++ b/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("../dependencies/ModuleDependency"); +const makeSerializable = require("../util/makeSerializable"); + +class ConsumeSharedFallbackDependency extends ModuleDependency { + /** + * @param {string} request the request + */ + constructor(request) { + super(request); + } + + get type() { + return "consume shared fallback"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ConsumeSharedFallbackDependency, + "webpack/lib/sharing/ConsumeSharedFallbackDependency" +); + +module.exports = ConsumeSharedFallbackDependency; diff --git a/webpack-lib/lib/sharing/ConsumeSharedModule.js b/webpack-lib/lib/sharing/ConsumeSharedModule.js new file mode 100644 index 00000000000..f9a745e3116 --- /dev/null +++ b/webpack-lib/lib/sharing/ConsumeSharedModule.js @@ -0,0 +1,267 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const Module = require("../Module"); +const { CONSUME_SHARED_TYPES } = require("../ModuleSourceTypesConstants"); +const { + WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE +} = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const { rangeToString, stringifyHoley } = require("../util/semver"); +const ConsumeSharedFallbackDependency = require("./ConsumeSharedFallbackDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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/semver").SemVerRange} SemVerRange */ + +/** + * @typedef {object} ConsumeOptions + * @property {string=} import fallback request + * @property {string=} importResolved resolved fallback request + * @property {string} shareKey global share key + * @property {string} shareScope share scope + * @property {SemVerRange | false | undefined} requiredVersion version requirement + * @property {string=} packageName package name to determine required version automatically + * @property {boolean} strictVersion don't use shared version even if version isn't valid + * @property {boolean} singleton use single global version + * @property {boolean} eager include the fallback module in a sync way + */ + +class ConsumeSharedModule extends Module { + /** + * @param {string} context context + * @param {ConsumeOptions} options consume options + */ + constructor(context, options) { + super(WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE, context); + this.options = options; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + const { + shareKey, + shareScope, + importResolved, + requiredVersion, + strictVersion, + singleton, + eager + } = this.options; + return `${WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE}|${shareScope}|${shareKey}|${ + requiredVersion && rangeToString(requiredVersion) + }|${strictVersion}|${importResolved}|${singleton}|${eager}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + const { + shareKey, + shareScope, + importResolved, + requiredVersion, + strictVersion, + singleton, + eager + } = this.options; + return `consume shared module (${shareScope}) ${shareKey}@${ + requiredVersion ? rangeToString(requiredVersion) : "*" + }${strictVersion ? " (strict)" : ""}${singleton ? " (singleton)" : ""}${ + importResolved + ? ` (fallback: ${requestShortener.shorten(importResolved)})` + : "" + }${eager ? " (eager)" : ""}`; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + const { shareKey, shareScope, import: request } = this.options; + return `${ + this.layer ? `(${this.layer})/` : "" + }webpack/sharing/consume/${shareScope}/${shareKey}${ + request ? `/${request}` : "" + }`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + callback(null, !this.buildInfo); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = {}; + if (this.options.import) { + const dep = new ConsumeSharedFallbackDependency(this.options.import); + if (this.options.eager) { + this.addDependency(dep); + } else { + const block = new AsyncDependenciesBlock({}); + block.addDependency(dep); + this.addBlock(block); + } + } + callback(); + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return CONSUME_SHARED_TYPES; + } + + /** + * @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; + } + + /** + * @param {Hash} hash the hash used to track dependencies + * @param {UpdateHashContext} context context + * @returns {void} + */ + updateHash(hash, context) { + hash.update(JSON.stringify(this.options)); + super.updateHash(hash, context); + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ chunkGraph, moduleGraph, runtimeTemplate }) { + const runtimeRequirements = new Set([RuntimeGlobals.shareScopeMap]); + const { + shareScope, + shareKey, + strictVersion, + requiredVersion, + import: request, + singleton, + eager + } = this.options; + let fallbackCode; + if (request) { + if (eager) { + const dep = this.dependencies[0]; + fallbackCode = runtimeTemplate.syncModuleFactory({ + dependency: dep, + chunkGraph, + runtimeRequirements, + request: this.options.import + }); + } else { + const block = this.blocks[0]; + fallbackCode = runtimeTemplate.asyncModuleFactory({ + block, + chunkGraph, + runtimeRequirements, + request: this.options.import + }); + } + } + + const args = [ + JSON.stringify(shareScope), + JSON.stringify(shareKey), + JSON.stringify(eager) + ]; + if (requiredVersion) { + args.push(stringifyHoley(requiredVersion)); + } + if (fallbackCode) { + args.push(fallbackCode); + } + + let fn; + + if (requiredVersion) { + if (strictVersion) { + fn = singleton ? "loadStrictSingletonVersion" : "loadStrictVersion"; + } else { + fn = singleton ? "loadSingletonVersion" : "loadVersion"; + } + } else { + fn = singleton ? "loadSingleton" : "load"; + } + + const code = runtimeTemplate.returningFunction(`${fn}(${args.join(", ")})`); + const sources = new Map(); + sources.set("consume-shared", new RawSource(code)); + return { + runtimeRequirements, + sources + }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this.options); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + */ + deserialize(context) { + const { read } = context; + this.options = read(); + super.deserialize(context); + } +} + +makeSerializable( + ConsumeSharedModule, + "webpack/lib/sharing/ConsumeSharedModule" +); + +module.exports = ConsumeSharedModule; diff --git a/webpack-lib/lib/sharing/ConsumeSharedPlugin.js b/webpack-lib/lib/sharing/ConsumeSharedPlugin.js new file mode 100644 index 00000000000..e6fab76f1d9 --- /dev/null +++ b/webpack-lib/lib/sharing/ConsumeSharedPlugin.js @@ -0,0 +1,379 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleNotFoundError = require("../ModuleNotFoundError"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const WebpackError = require("../WebpackError"); +const { parseOptions } = require("../container/options"); +const LazySet = require("../util/LazySet"); +const createSchemaValidation = require("../util/create-schema-validation"); +const { parseRange } = require("../util/semver"); +const ConsumeSharedFallbackDependency = require("./ConsumeSharedFallbackDependency"); +const ConsumeSharedModule = require("./ConsumeSharedModule"); +const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule"); +const ProvideForSharedDependency = require("./ProvideForSharedDependency"); +const { resolveMatchedConfigs } = require("./resolveMatchedConfigs"); +const { + isRequiredVersion, + getDescriptionFile, + getRequiredVersionFromDescriptionFile +} = require("./utils"); + +/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */ +/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */ +/** @typedef {import("../util/semver").SemVerRange} SemVerRange */ +/** @typedef {import("./ConsumeSharedModule").ConsumeOptions} ConsumeOptions */ +/** @typedef {import("./utils").DescriptionFile} DescriptionFile */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/sharing/ConsumeSharedPlugin.check.js"), + () => require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json"), + { + name: "Consume Shared Plugin", + baseDataPath: "options" + } +); + +/** @type {ResolveOptionsWithDependencyType} */ +const RESOLVE_OPTIONS = { dependencyType: "esm" }; +const PLUGIN_NAME = "ConsumeSharedPlugin"; + +class ConsumeSharedPlugin { + /** + * @param {ConsumeSharedPluginOptions} options options + */ + constructor(options) { + if (typeof options !== "string") { + validate(options); + } + + /** @type {[string, ConsumeOptions][]} */ + this._consumes = parseOptions( + options.consumes, + (item, key) => { + if (Array.isArray(item)) throw new Error("Unexpected array in options"); + /** @type {ConsumeOptions} */ + const result = + item === key || !isRequiredVersion(item) + ? // item is a request/key + { + import: key, + shareScope: options.shareScope || "default", + shareKey: key, + requiredVersion: undefined, + packageName: undefined, + strictVersion: false, + singleton: false, + eager: false + } + : // key is a request/key + // item is a version + { + import: key, + shareScope: options.shareScope || "default", + shareKey: key, + requiredVersion: parseRange(item), + strictVersion: true, + packageName: undefined, + singleton: false, + eager: false + }; + return result; + }, + (item, key) => ({ + import: item.import === false ? undefined : item.import || key, + shareScope: item.shareScope || options.shareScope || "default", + shareKey: item.shareKey || key, + requiredVersion: + typeof item.requiredVersion === "string" + ? parseRange(item.requiredVersion) + : item.requiredVersion, + strictVersion: + typeof item.strictVersion === "boolean" + ? item.strictVersion + : item.import !== false && !item.singleton, + packageName: item.packageName, + singleton: Boolean(item.singleton), + eager: Boolean(item.eager) + }) + ); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + ConsumeSharedFallbackDependency, + normalModuleFactory + ); + + /** @type {Map} */ + let unresolvedConsumes; + /** @type {Map} */ + let resolvedConsumes; + /** @type {Map} */ + let prefixedConsumes; + const promise = resolveMatchedConfigs(compilation, this._consumes).then( + ({ resolved, unresolved, prefixed }) => { + resolvedConsumes = resolved; + unresolvedConsumes = unresolved; + prefixedConsumes = prefixed; + } + ); + + const resolver = compilation.resolverFactory.get( + "normal", + RESOLVE_OPTIONS + ); + + /** + * @param {string} context issuer directory + * @param {string} request request + * @param {ConsumeOptions} config options + * @returns {Promise} create module + */ + const createConsumeSharedModule = (context, request, config) => { + /** + * @param {string} details details + */ + const requiredVersionWarning = details => { + const error = new WebpackError( + `No required version specified and unable to automatically determine one. ${details}` + ); + error.file = `shared module ${request}`; + compilation.warnings.push(error); + }; + const directFallback = + config.import && + /^(\.\.?(\/|$)|\/|[A-Za-z]:|\\\\)/.test(config.import); + return Promise.all([ + new Promise( + /** + * @param {(value?: string) => void} resolve resolve + */ + resolve => { + if (!config.import) { + resolve(); + return; + } + const resolveContext = { + /** @type {LazySet} */ + fileDependencies: new LazySet(), + /** @type {LazySet} */ + contextDependencies: new LazySet(), + /** @type {LazySet} */ + missingDependencies: new LazySet() + }; + resolver.resolve( + {}, + directFallback ? compiler.context : context, + config.import, + resolveContext, + (err, result) => { + compilation.contextDependencies.addAll( + resolveContext.contextDependencies + ); + compilation.fileDependencies.addAll( + resolveContext.fileDependencies + ); + compilation.missingDependencies.addAll( + resolveContext.missingDependencies + ); + if (err) { + compilation.errors.push( + new ModuleNotFoundError(null, err, { + name: `resolving fallback for shared module ${request}` + }) + ); + return resolve(); + } + resolve(/** @type {string} */ (result)); + } + ); + } + ), + new Promise( + /** + * @param {(value?: SemVerRange) => void} resolve resolve + */ + resolve => { + if (config.requiredVersion !== undefined) { + resolve(/** @type {SemVerRange} */ (config.requiredVersion)); + return; + } + let packageName = config.packageName; + if (packageName === undefined) { + if (/^(\/|[A-Za-z]:|\\\\)/.test(request)) { + // For relative or absolute requests we don't automatically use a packageName. + // If wished one can specify one with the packageName option. + resolve(); + return; + } + const match = /^((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(request); + if (!match) { + requiredVersionWarning( + "Unable to extract the package name from request." + ); + resolve(); + return; + } + packageName = match[0]; + } + + getDescriptionFile( + compilation.inputFileSystem, + context, + ["package.json"], + (err, result, checkedDescriptionFilePaths) => { + if (err) { + requiredVersionWarning( + `Unable to read description file: ${err}` + ); + return resolve(); + } + const { data } = + /** @type {DescriptionFile} */ + (result || {}); + if (!data) { + if (checkedDescriptionFilePaths) { + requiredVersionWarning( + [ + `Unable to find required version for "${packageName}" in description file/s`, + checkedDescriptionFilePaths.join("\n"), + "It need to be in dependencies, devDependencies or peerDependencies." + ].join("\n") + ); + } else { + requiredVersionWarning( + `Unable to find description file in ${context}.` + ); + } + + return resolve(); + } + if (data.name === packageName) { + // Package self-referencing + return resolve(); + } + const requiredVersion = + getRequiredVersionFromDescriptionFile(data, packageName); + + if (requiredVersion) { + return resolve(parseRange(requiredVersion)); + } + + resolve(); + }, + result => { + if (!result) return false; + const maybeRequiredVersion = + getRequiredVersionFromDescriptionFile( + result.data, + packageName + ); + return ( + result.data.name === packageName || + typeof maybeRequiredVersion === "string" + ); + } + ); + } + ) + ]).then( + ([importResolved, requiredVersion]) => + new ConsumeSharedModule( + directFallback ? compiler.context : context, + { + ...config, + importResolved, + import: importResolved ? config.import : undefined, + requiredVersion + } + ) + ); + }; + + normalModuleFactory.hooks.factorize.tapPromise( + PLUGIN_NAME, + ({ context, request, dependencies }) => + // wait for resolving to be complete + promise.then(() => { + if ( + dependencies[0] instanceof ConsumeSharedFallbackDependency || + dependencies[0] instanceof ProvideForSharedDependency + ) { + return; + } + const match = unresolvedConsumes.get(request); + if (match !== undefined) { + return createConsumeSharedModule(context, request, match); + } + for (const [prefix, options] of prefixedConsumes) { + if (request.startsWith(prefix)) { + const remainder = request.slice(prefix.length); + return createConsumeSharedModule(context, request, { + ...options, + import: options.import + ? options.import + remainder + : undefined, + shareKey: options.shareKey + remainder + }); + } + } + }) + ); + normalModuleFactory.hooks.createModule.tapPromise( + PLUGIN_NAME, + ({ resource }, { context, dependencies }) => { + if ( + dependencies[0] instanceof ConsumeSharedFallbackDependency || + dependencies[0] instanceof ProvideForSharedDependency + ) { + return Promise.resolve(); + } + const options = resolvedConsumes.get( + /** @type {string} */ (resource) + ); + if (options !== undefined) { + return createConsumeSharedModule( + context, + /** @type {string} */ (resource), + options + ); + } + return Promise.resolve(); + } + ); + compilation.hooks.additionalTreeRuntimeRequirements.tap( + PLUGIN_NAME, + (chunk, set) => { + set.add(RuntimeGlobals.module); + set.add(RuntimeGlobals.moduleCache); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.shareScopeMap); + set.add(RuntimeGlobals.initializeSharing); + set.add(RuntimeGlobals.hasOwnProperty); + compilation.addRuntimeModule( + chunk, + new ConsumeSharedRuntimeModule(set) + ); + } + ); + } + ); + } +} + +module.exports = ConsumeSharedPlugin; diff --git a/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js b/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js new file mode 100644 index 00000000000..095d24c099b --- /dev/null +++ b/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js @@ -0,0 +1,355 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + parseVersionRuntimeCode, + versionLtRuntimeCode, + rangeToStringRuntimeCode, + satisfyRuntimeCode +} = require("../util/semver"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("./ConsumeSharedModule")} ConsumeSharedModule */ + +class ConsumeSharedRuntimeModule extends RuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("consumes", RuntimeModule.STAGE_ATTACH); + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const { runtimeTemplate, codeGenerationResults } = compilation; + /** @type {Record} */ + const chunkToModuleMapping = {}; + /** @type {Map} */ + const moduleIdToSourceMapping = new Map(); + /** @type {(string | number)[]} */ + const initialConsumes = []; + /** + * @param {Iterable} modules modules + * @param {Chunk} chunk the chunk + * @param {(string | number)[]} list list of ids + */ + const addModules = (modules, chunk, list) => { + for (const m of modules) { + const module = m; + const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + list.push(id); + moduleIdToSourceMapping.set( + id, + codeGenerationResults.getSource( + module, + chunk.runtime, + "consume-shared" + ) + ); + } + }; + for (const chunk of /** @type {Chunk} */ ( + this.chunk + ).getAllReferencedChunks()) { + const modules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + "consume-shared" + ); + if (!modules) continue; + addModules( + modules, + chunk, + (chunkToModuleMapping[/** @type {ChunkId} */ (chunk.id)] = []) + ); + } + for (const chunk of /** @type {Chunk} */ ( + this.chunk + ).getAllInitialChunks()) { + const modules = chunkGraph.getChunkModulesIterableBySourceType( + chunk, + "consume-shared" + ); + if (!modules) continue; + addModules(modules, chunk, initialConsumes); + } + if (moduleIdToSourceMapping.size === 0) return null; + return Template.asString([ + parseVersionRuntimeCode(runtimeTemplate), + versionLtRuntimeCode(runtimeTemplate), + rangeToStringRuntimeCode(runtimeTemplate), + satisfyRuntimeCode(runtimeTemplate), + `var exists = ${runtimeTemplate.basicFunction("scope, key", [ + `return scope && ${RuntimeGlobals.hasOwnProperty}(scope, key);` + ])}`, + `var get = ${runtimeTemplate.basicFunction("entry", [ + "entry.loaded = 1;", + "return entry.get()" + ])};`, + `var eagerOnly = ${runtimeTemplate.basicFunction("versions", [ + `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction( + "filtered, version", + Template.indent([ + "if (versions[version].eager) {", + Template.indent(["filtered[version] = versions[version];"]), + "}", + "return filtered;" + ]) + )}, {});` + ])};`, + `var findLatestVersion = ${runtimeTemplate.basicFunction( + "scope, key, eager", + [ + "var versions = eager ? eagerOnly(scope[key]) : scope[key];", + `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction( + "a, b", + ["return !a || versionLt(a, b) ? b : a;"] + )}, 0);`, + "return key && versions[key];" + ] + )};`, + `var findSatisfyingVersion = ${runtimeTemplate.basicFunction( + "scope, key, requiredVersion, eager", + [ + "var versions = eager ? eagerOnly(scope[key]) : scope[key];", + `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction( + "a, b", + [ + "if (!satisfy(requiredVersion, b)) return a;", + "return !a || versionLt(a, b) ? b : a;" + ] + )}, 0);`, + "return key && versions[key]" + ] + )};`, + `var findSingletonVersionKey = ${runtimeTemplate.basicFunction( + "scope, key, eager", + [ + "var versions = eager ? eagerOnly(scope[key]) : scope[key];", + `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction( + "a, b", + ["return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;"] + )}, 0);` + ] + )};`, + `var getInvalidSingletonVersionMessage = ${runtimeTemplate.basicFunction( + "scope, key, version, requiredVersion", + [ + 'return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")"' + ] + )};`, + `var getInvalidVersionMessage = ${runtimeTemplate.basicFunction( + "scope, scopeName, key, requiredVersion, eager", + [ + "var versions = scope[key];", + 'return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\\n" +', + `\t"Available versions: " + Object.keys(versions).map(${runtimeTemplate.basicFunction( + "key", + ['return key + " from " + versions[key].from;'] + )}).join(", ");` + ] + )};`, + `var fail = ${runtimeTemplate.basicFunction("msg", [ + "throw new Error(msg);" + ])}`, + `var failAsNotExist = ${runtimeTemplate.basicFunction("scopeName, key", [ + 'return fail("Shared module " + key + " doesn\'t exist in shared scope " + scopeName);' + ])}`, + `var warn = /*#__PURE__*/ ${ + compilation.outputOptions.ignoreBrowserWarnings + ? runtimeTemplate.basicFunction("", "") + : runtimeTemplate.basicFunction("msg", [ + 'if (typeof console !== "undefined" && console.warn) console.warn(msg);' + ]) + };`, + `var init = ${runtimeTemplate.returningFunction( + Template.asString([ + "function(scopeName, key, eager, c, d) {", + Template.indent([ + `var promise = ${RuntimeGlobals.initializeSharing}(scopeName);`, + // if we require eager shared, we expect it to be already loaded before it requested, no need to wait the whole scope loaded. + "if (promise && promise.then && !eager) { ", + Template.indent([ + `return promise.then(fn.bind(fn, scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, false, c, d));` + ]), + "}", + `return fn(scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, eager, c, d);` + ]), + "}" + ]), + "fn" + )};`, + "", + `var useFallback = ${runtimeTemplate.basicFunction( + "scopeName, key, fallback", + ["return fallback ? fallback() : failAsNotExist(scopeName, key);"] + )}`, + `var load = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "return get(findLatestVersion(scope, key, eager));" + ] + )});`, + `var loadVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, requiredVersion, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);", + "if (satisfyingVersion) return get(satisfyingVersion);", + "warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))", + "return get(findLatestVersion(scope, key, eager));" + ] + )});`, + `var loadStrictVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, requiredVersion, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);", + "if (satisfyingVersion) return get(satisfyingVersion);", + "if (fallback) return fallback();", + "fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));" + ] + )});`, + `var loadSingleton = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "var version = findSingletonVersionKey(scope, key, eager);", + "return get(scope[key][version]);" + ] + )});`, + `var loadSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, requiredVersion, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "var version = findSingletonVersionKey(scope, key, eager);", + "if (!satisfy(requiredVersion, version)) {", + Template.indent([ + "warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));" + ]), + "}", + "return get(scope[key][version]);" + ] + )});`, + `var loadStrictSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( + "scopeName, scope, key, eager, requiredVersion, fallback", + [ + "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", + "var version = findSingletonVersionKey(scope, key, eager);", + "if (!satisfy(requiredVersion, version)) {", + Template.indent([ + "fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));" + ]), + "}", + "return get(scope[key][version]);" + ] + )});`, + "var installedModules = {};", + "var moduleToHandlerMapping = {", + Template.indent( + Array.from( + moduleIdToSourceMapping, + ([key, source]) => `${JSON.stringify(key)}: ${source.source()}` + ).join(",\n") + ), + "};", + + initialConsumes.length > 0 + ? Template.asString([ + `var initialConsumes = ${JSON.stringify(initialConsumes)};`, + `initialConsumes.forEach(${runtimeTemplate.basicFunction("id", [ + `${ + RuntimeGlobals.moduleFactories + }[id] = ${runtimeTemplate.basicFunction("module", [ + "// Handle case when module is used sync", + "installedModules[id] = 0;", + `delete ${RuntimeGlobals.moduleCache}[id];`, + "var factory = moduleToHandlerMapping[id]();", + 'if(typeof factory !== "function") throw new Error("Shared module is not available for eager consumption: " + id);', + "module.exports = factory();" + ])}` + ])});` + ]) + : "// no consumes in initial chunks", + this._runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) + ? Template.asString([ + `var chunkMapping = ${JSON.stringify( + chunkToModuleMapping, + null, + "\t" + )};`, + "var startedInstallModules = {};", + `${ + RuntimeGlobals.ensureChunkHandlers + }.consumes = ${runtimeTemplate.basicFunction("chunkId, promises", [ + `if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`, + Template.indent([ + `chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction( + "id", + [ + `if(${RuntimeGlobals.hasOwnProperty}(installedModules, id)) return promises.push(installedModules[id]);`, + "if(!startedInstallModules[id]) {", + `var onFactory = ${runtimeTemplate.basicFunction( + "factory", + [ + "installedModules[id] = 0;", + `${ + RuntimeGlobals.moduleFactories + }[id] = ${runtimeTemplate.basicFunction("module", [ + `delete ${RuntimeGlobals.moduleCache}[id];`, + "module.exports = factory();" + ])}` + ] + )};`, + "startedInstallModules[id] = true;", + `var onError = ${runtimeTemplate.basicFunction("error", [ + "delete installedModules[id];", + `${ + RuntimeGlobals.moduleFactories + }[id] = ${runtimeTemplate.basicFunction("module", [ + `delete ${RuntimeGlobals.moduleCache}[id];`, + "throw error;" + ])}` + ])};`, + "try {", + Template.indent([ + "var promise = moduleToHandlerMapping[id]();", + "if(promise.then) {", + Template.indent( + "promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));" + ), + "} else onFactory(promise);" + ]), + "} catch(e) { onError(e); }", + "}" + ] + )});` + ]), + "}" + ])}` + ]) + : "// no chunk loading of consumes" + ]); + } +} + +module.exports = ConsumeSharedRuntimeModule; diff --git a/webpack-lib/lib/sharing/ProvideForSharedDependency.js b/webpack-lib/lib/sharing/ProvideForSharedDependency.js new file mode 100644 index 00000000000..4de679a6a74 --- /dev/null +++ b/webpack-lib/lib/sharing/ProvideForSharedDependency.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleDependency = require("../dependencies/ModuleDependency"); +const makeSerializable = require("../util/makeSerializable"); + +class ProvideForSharedDependency extends ModuleDependency { + /** + * @param {string} request request string + */ + constructor(request) { + super(request); + } + + get type() { + return "provide module for shared"; + } + + get category() { + return "esm"; + } +} + +makeSerializable( + ProvideForSharedDependency, + "webpack/lib/sharing/ProvideForSharedDependency" +); + +module.exports = ProvideForSharedDependency; diff --git a/webpack-lib/lib/sharing/ProvideSharedDependency.js b/webpack-lib/lib/sharing/ProvideSharedDependency.js new file mode 100644 index 00000000000..2df18a618ed --- /dev/null +++ b/webpack-lib/lib/sharing/ProvideSharedDependency.js @@ -0,0 +1,80 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Dependency = require("../Dependency"); +const makeSerializable = require("../util/makeSerializable"); + +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +class ProvideSharedDependency extends Dependency { + /** + * @param {string} shareScope share scope + * @param {string} name module name + * @param {string | false} version version + * @param {string} request request + * @param {boolean} eager true, if this is an eager dependency + */ + constructor(shareScope, name, version, request, eager) { + super(); + this.shareScope = shareScope; + this.name = name; + this.version = version; + this.request = request; + this.eager = eager; + } + + get type() { + return "provide shared module"; + } + + /** + * @returns {string | null} an identifier to merge equal requests + */ + getResourceIdentifier() { + return `provide module (${this.shareScope}) ${this.request} as ${ + this.name + } @ ${this.version}${this.eager ? " (eager)" : ""}`; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + context.write(this.shareScope); + context.write(this.name); + context.write(this.request); + context.write(this.version); + context.write(this.eager); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ProvideSharedDependency} deserialize fallback dependency + */ + static deserialize(context) { + const { read } = context; + const obj = new ProvideSharedDependency( + read(), + read(), + read(), + read(), + read() + ); + this.shareScope = context.read(); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + ProvideSharedDependency, + "webpack/lib/sharing/ProvideSharedDependency" +); + +module.exports = ProvideSharedDependency; diff --git a/webpack-lib/lib/sharing/ProvideSharedModule.js b/webpack-lib/lib/sharing/ProvideSharedModule.js new file mode 100644 index 00000000000..a0c1d0fd838 --- /dev/null +++ b/webpack-lib/lib/sharing/ProvideSharedModule.js @@ -0,0 +1,194 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); +const Module = require("../Module"); +const { SHARED_INIT_TYPES } = require("../ModuleSourceTypesConstants"); +const { WEBPACK_MODULE_TYPE_PROVIDE } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const makeSerializable = require("../util/makeSerializable"); +const ProvideForSharedDependency = require("./ProvideForSharedDependency"); + +/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ +/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ +/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ +/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ +/** @typedef {import("../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 */ + +class ProvideSharedModule extends Module { + /** + * @param {string} shareScope shared scope name + * @param {string} name shared key + * @param {string | false} version version + * @param {string} request request to the provided module + * @param {boolean} eager include the module in sync way + */ + constructor(shareScope, name, version, request, eager) { + super(WEBPACK_MODULE_TYPE_PROVIDE); + this._shareScope = shareScope; + this._name = name; + this._version = version; + this._request = request; + this._eager = eager; + } + + /** + * @returns {string} a unique identifier of the module + */ + identifier() { + return `provide module (${this._shareScope}) ${this._name}@${this._version} = ${this._request}`; + } + + /** + * @param {RequestShortener} requestShortener the request shortener + * @returns {string} a user readable identifier of the module + */ + readableIdentifier(requestShortener) { + return `provide shared module (${this._shareScope}) ${this._name}@${ + this._version + } = ${requestShortener.shorten(this._request)}`; + } + + /** + * @param {LibIdentOptions} options options + * @returns {string | null} an identifier for library inclusion + */ + libIdent(options) { + return `${this.layer ? `(${this.layer})/` : ""}webpack/sharing/provide/${ + this._shareScope + }/${this._name}`; + } + + /** + * @param {NeedBuildContext} context context info + * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild + * @returns {void} + */ + needBuild(context, callback) { + callback(null, !this.buildInfo); + } + + /** + * @param {WebpackOptions} options webpack options + * @param {Compilation} compilation the compilation + * @param {ResolverWithOptions} resolver the resolver + * @param {InputFileSystem} fs the file system + * @param {function(WebpackError=): void} callback callback function + * @returns {void} + */ + build(options, compilation, resolver, fs, callback) { + this.buildMeta = {}; + this.buildInfo = { + strict: true + }; + + this.clearDependenciesAndBlocks(); + const dep = new ProvideForSharedDependency(this._request); + if (this._eager) { + this.addDependency(dep); + } else { + const block = new AsyncDependenciesBlock({}); + block.addDependency(dep); + this.addBlock(block); + } + + callback(); + } + + /** + * @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; + } + + /** + * @returns {SourceTypes} types available (do not mutate) + */ + getSourceTypes() { + return SHARED_INIT_TYPES; + } + + /** + * @param {CodeGenerationContext} context context for code generation + * @returns {CodeGenerationResult} result + */ + codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { + const runtimeRequirements = new Set([RuntimeGlobals.initializeSharing]); + const code = `register(${JSON.stringify(this._name)}, ${JSON.stringify( + this._version || "0" + )}, ${ + this._eager + ? runtimeTemplate.syncModuleFactory({ + dependency: this.dependencies[0], + chunkGraph, + request: this._request, + runtimeRequirements + }) + : runtimeTemplate.asyncModuleFactory({ + block: this.blocks[0], + chunkGraph, + request: this._request, + runtimeRequirements + }) + }${this._eager ? ", 1" : ""});`; + const sources = new Map(); + const data = new Map(); + data.set("share-init", [ + { + shareScope: this._shareScope, + initStage: 10, + init: code + } + ]); + return { sources, data, runtimeRequirements }; + } + + /** + * @param {ObjectSerializerContext} context context + */ + serialize(context) { + const { write } = context; + write(this._shareScope); + write(this._name); + write(this._version); + write(this._request); + write(this._eager); + super.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ProvideSharedModule} deserialize fallback dependency + */ + static deserialize(context) { + const { read } = context; + const obj = new ProvideSharedModule(read(), read(), read(), read(), read()); + obj.deserialize(context); + return obj; + } +} + +makeSerializable( + ProvideSharedModule, + "webpack/lib/sharing/ProvideSharedModule" +); + +module.exports = ProvideSharedModule; diff --git a/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js b/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js new file mode 100644 index 00000000000..d5bcc829f99 --- /dev/null +++ b/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const ModuleFactory = require("../ModuleFactory"); +const ProvideSharedModule = require("./ProvideSharedModule"); + +/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ +/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ +/** @typedef {import("./ProvideSharedDependency")} ProvideSharedDependency */ + +class ProvideSharedModuleFactory extends ModuleFactory { + /** + * @param {ModuleFactoryCreateData} data data object + * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback + * @returns {void} + */ + create(data, callback) { + const dep = /** @type {ProvideSharedDependency} */ (data.dependencies[0]); + callback(null, { + module: new ProvideSharedModule( + dep.shareScope, + dep.name, + dep.version, + dep.request, + dep.eager + ) + }); + } +} + +module.exports = ProvideSharedModuleFactory; diff --git a/webpack-lib/lib/sharing/ProvideSharedPlugin.js b/webpack-lib/lib/sharing/ProvideSharedPlugin.js new file mode 100644 index 00000000000..c57b76324ab --- /dev/null +++ b/webpack-lib/lib/sharing/ProvideSharedPlugin.js @@ -0,0 +1,244 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); +const { parseOptions } = require("../container/options"); +const createSchemaValidation = require("../util/create-schema-validation"); +const ProvideForSharedDependency = require("./ProvideForSharedDependency"); +const ProvideSharedDependency = require("./ProvideSharedDependency"); +const ProvideSharedModuleFactory = require("./ProvideSharedModuleFactory"); + +/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../NormalModuleFactory").NormalModuleCreateData} NormalModuleCreateData */ + +const validate = createSchemaValidation( + require("../../schemas/plugins/sharing/ProvideSharedPlugin.check.js"), + () => require("../../schemas/plugins/sharing/ProvideSharedPlugin.json"), + { + name: "Provide Shared Plugin", + baseDataPath: "options" + } +); + +/** + * @typedef {object} ProvideOptions + * @property {string} shareKey + * @property {string} shareScope + * @property {string | undefined | false} version + * @property {boolean} eager + */ + +/** @typedef {Map} ResolvedProvideMap */ + +class ProvideSharedPlugin { + /** + * @param {ProvideSharedPluginOptions} options options + */ + constructor(options) { + validate(options); + + this._provides = /** @type {[string, ProvideOptions][]} */ ( + parseOptions( + options.provides, + item => { + if (Array.isArray(item)) + throw new Error("Unexpected array of provides"); + /** @type {ProvideOptions} */ + const result = { + shareKey: item, + version: undefined, + shareScope: options.shareScope || "default", + eager: false + }; + return result; + }, + item => ({ + shareKey: item.shareKey, + version: item.version, + shareScope: item.shareScope || options.shareScope || "default", + eager: Boolean(item.eager) + }) + ) + ); + this._provides.sort(([a], [b]) => { + if (a < b) return -1; + if (b < a) return 1; + return 0; + }); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + /** @type {WeakMap} */ + const compilationData = new WeakMap(); + + compiler.hooks.compilation.tap( + "ProvideSharedPlugin", + (compilation, { normalModuleFactory }) => { + /** @type {ResolvedProvideMap} */ + const resolvedProvideMap = new Map(); + /** @type {Map} */ + const matchProvides = new Map(); + /** @type {Map} */ + const prefixMatchProvides = new Map(); + for (const [request, config] of this._provides) { + if (/^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))/.test(request)) { + // relative request + resolvedProvideMap.set(request, { + config, + version: config.version + }); + } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) { + // absolute path + resolvedProvideMap.set(request, { + config, + version: config.version + }); + } else if (request.endsWith("/")) { + // module request prefix + prefixMatchProvides.set(request, config); + } else { + // module request + matchProvides.set(request, config); + } + } + compilationData.set(compilation, resolvedProvideMap); + /** + * @param {string} key key + * @param {ProvideOptions} config config + * @param {NormalModuleCreateData["resource"]} resource resource + * @param {NormalModuleCreateData["resourceResolveData"]} resourceResolveData resource resolve data + */ + const provideSharedModule = ( + key, + config, + resource, + resourceResolveData + ) => { + let version = config.version; + if (version === undefined) { + let details = ""; + if (!resourceResolveData) { + details = "No resolve data provided from resolver."; + } else { + const descriptionFileData = + resourceResolveData.descriptionFileData; + if (!descriptionFileData) { + details = + "No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config."; + } else if (!descriptionFileData.version) { + details = `No version in description file (usually package.json). Add version to description file ${resourceResolveData.descriptionFilePath}, or manually specify version in shared config.`; + } else { + version = descriptionFileData.version; + } + } + if (!version) { + const error = new WebpackError( + `No version specified and unable to automatically determine one. ${details}` + ); + error.file = `shared module ${key} -> ${resource}`; + compilation.warnings.push(error); + } + } + resolvedProvideMap.set(resource, { + config, + version + }); + }; + normalModuleFactory.hooks.module.tap( + "ProvideSharedPlugin", + (module, { resource, resourceResolveData }, resolveData) => { + if (resolvedProvideMap.has(/** @type {string} */ (resource))) { + return module; + } + const { request } = resolveData; + { + const config = matchProvides.get(request); + if (config !== undefined) { + provideSharedModule( + request, + config, + /** @type {string} */ (resource), + resourceResolveData + ); + resolveData.cacheable = false; + } + } + for (const [prefix, config] of prefixMatchProvides) { + if (request.startsWith(prefix)) { + const remainder = request.slice(prefix.length); + provideSharedModule( + /** @type {string} */ (resource), + { + ...config, + shareKey: config.shareKey + remainder + }, + /** @type {string} */ (resource), + resourceResolveData + ); + resolveData.cacheable = false; + } + } + return module; + } + ); + } + ); + compiler.hooks.finishMake.tapPromise("ProvideSharedPlugin", compilation => { + const resolvedProvideMap = compilationData.get(compilation); + if (!resolvedProvideMap) return Promise.resolve(); + return Promise.all( + Array.from( + resolvedProvideMap, + ([resource, { config, version }]) => + new Promise((resolve, reject) => { + compilation.addInclude( + compiler.context, + new ProvideSharedDependency( + config.shareScope, + config.shareKey, + version || false, + resource, + config.eager + ), + { + name: undefined + }, + err => { + if (err) return reject(err); + resolve(null); + } + ); + }) + ) + ).then(() => {}); + }); + + compiler.hooks.compilation.tap( + "ProvideSharedPlugin", + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + ProvideForSharedDependency, + normalModuleFactory + ); + + compilation.dependencyFactories.set( + ProvideSharedDependency, + new ProvideSharedModuleFactory() + ); + } + ); + } +} + +module.exports = ProvideSharedPlugin; diff --git a/webpack-lib/lib/sharing/SharePlugin.js b/webpack-lib/lib/sharing/SharePlugin.js new file mode 100644 index 00000000000..65935b30b99 --- /dev/null +++ b/webpack-lib/lib/sharing/SharePlugin.js @@ -0,0 +1,92 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy +*/ + +"use strict"; + +const { parseOptions } = require("../container/options"); +const ConsumeSharedPlugin = require("./ConsumeSharedPlugin"); +const ProvideSharedPlugin = require("./ProvideSharedPlugin"); +const { isRequiredVersion } = require("./utils"); + +/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */ +/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */ +/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */ +/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvidesConfig} ProvidesConfig */ +/** @typedef {import("../../declarations/plugins/sharing/SharePlugin").SharePluginOptions} SharePluginOptions */ +/** @typedef {import("../../declarations/plugins/sharing/SharePlugin").SharedConfig} SharedConfig */ +/** @typedef {import("../Compiler")} Compiler */ + +class SharePlugin { + /** + * @param {SharePluginOptions} options options + */ + constructor(options) { + /** @type {[string, SharedConfig][]} */ + const sharedOptions = parseOptions( + options.shared, + (item, key) => { + if (typeof item !== "string") + throw new Error("Unexpected array in shared"); + /** @type {SharedConfig} */ + const config = + item === key || !isRequiredVersion(item) + ? { + import: item + } + : { + import: key, + requiredVersion: item + }; + return config; + }, + item => item + ); + /** @type {Record[]} */ + const consumes = sharedOptions.map(([key, options]) => ({ + [key]: { + import: options.import, + shareKey: options.shareKey || key, + shareScope: options.shareScope, + requiredVersion: options.requiredVersion, + strictVersion: options.strictVersion, + singleton: options.singleton, + packageName: options.packageName, + eager: options.eager + } + })); + /** @type {Record[]} */ + const provides = sharedOptions + .filter(([, options]) => options.import !== false) + .map(([key, options]) => ({ + [options.import || key]: { + shareKey: options.shareKey || key, + shareScope: options.shareScope, + version: options.version, + eager: options.eager + } + })); + this._shareScope = options.shareScope; + this._consumes = consumes; + this._provides = provides; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + new ConsumeSharedPlugin({ + shareScope: this._shareScope, + consumes: this._consumes + }).apply(compiler); + new ProvideSharedPlugin({ + shareScope: this._shareScope, + provides: this._provides + }).apply(compiler); + } +} + +module.exports = SharePlugin; diff --git a/webpack-lib/lib/sharing/ShareRuntimeModule.js b/webpack-lib/lib/sharing/ShareRuntimeModule.js new file mode 100644 index 00000000000..0f63ef68d7d --- /dev/null +++ b/webpack-lib/lib/sharing/ShareRuntimeModule.js @@ -0,0 +1,151 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + compareModulesByIdentifier, + compareStrings +} = require("../util/comparators"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ + +class ShareRuntimeModule extends RuntimeModule { + constructor() { + super("sharing"); + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { + runtimeTemplate, + codeGenerationResults, + outputOptions: { uniqueName, ignoreBrowserWarnings } + } = compilation; + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + /** @type {Map>>} */ + const initCodePerScope = new Map(); + for (const chunk of /** @type {Chunk} */ ( + this.chunk + ).getAllReferencedChunks()) { + const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( + chunk, + "share-init", + compareModulesByIdentifier + ); + if (!modules) continue; + for (const m of modules) { + const data = codeGenerationResults.getData( + m, + chunk.runtime, + "share-init" + ); + if (!data) continue; + for (const item of data) { + const { shareScope, initStage, init } = item; + let stages = initCodePerScope.get(shareScope); + if (stages === undefined) { + initCodePerScope.set(shareScope, (stages = new Map())); + } + let list = stages.get(initStage || 0); + if (list === undefined) { + stages.set(initStage || 0, (list = new Set())); + } + list.add(init); + } + } + } + return Template.asString([ + `${RuntimeGlobals.shareScopeMap} = {};`, + "var initPromises = {};", + "var initTokens = {};", + `${RuntimeGlobals.initializeSharing} = ${runtimeTemplate.basicFunction( + "name, initScope", + [ + "if(!initScope) initScope = [];", + "// handling circular init calls", + "var 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(!${RuntimeGlobals.hasOwnProperty}(${RuntimeGlobals.shareScopeMap}, name)) ${RuntimeGlobals.shareScopeMap}[name] = {};`, + "// runs all init snippets from all modules reachable", + `var scope = ${RuntimeGlobals.shareScopeMap}[name];`, + `var warn = ${ + ignoreBrowserWarnings + ? runtimeTemplate.basicFunction("", "") + : runtimeTemplate.basicFunction("msg", [ + 'if (typeof console !== "undefined" && console.warn) console.warn(msg);' + ]) + };`, + `var uniqueName = ${JSON.stringify(uniqueName || undefined)};`, + `var register = ${runtimeTemplate.basicFunction( + "name, version, factory, eager", + [ + "var versions = scope[name] = scope[name] || {};", + "var activeVersion = versions[version];", + "if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };" + ] + )};`, + `var initExternal = ${runtimeTemplate.basicFunction("id", [ + `var handleError = ${runtimeTemplate.expressionFunction( + 'warn("Initialization of sharing external failed: " + err)', + "err" + )};`, + "try {", + Template.indent([ + `var module = ${RuntimeGlobals.require}(id);`, + "if(!module) return;", + `var initFn = ${runtimeTemplate.returningFunction( + `module && module.init && module.init(${RuntimeGlobals.shareScopeMap}[name], initScope)`, + "module" + )}`, + "if(module.then) return promises.push(module.then(initFn, handleError));", + "var initResult = initFn(module);", + "if(initResult && initResult.then) return promises.push(initResult['catch'](handleError));" + ]), + "} catch(err) { handleError(err); }" + ])}`, + "var promises = [];", + "switch(name) {", + ...Array.from(initCodePerScope) + .sort(([a], [b]) => compareStrings(a, b)) + .map(([name, stages]) => + Template.indent([ + `case ${JSON.stringify(name)}: {`, + Template.indent( + Array.from(stages) + .sort(([a], [b]) => a - b) + .map(([, initCode]) => + Template.asString(Array.from(initCode)) + ) + ), + "}", + "break;" + ]) + ), + "}", + "if(!promises.length) return initPromises[name] = 1;", + `return initPromises[name] = Promise.all(promises).then(${runtimeTemplate.returningFunction( + "initPromises[name] = 1" + )});` + ] + )};` + ]); + } +} + +module.exports = ShareRuntimeModule; diff --git a/webpack-lib/lib/sharing/resolveMatchedConfigs.js b/webpack-lib/lib/sharing/resolveMatchedConfigs.js new file mode 100644 index 00000000000..a54a76abb41 --- /dev/null +++ b/webpack-lib/lib/sharing/resolveMatchedConfigs.js @@ -0,0 +1,92 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ModuleNotFoundError = require("../ModuleNotFoundError"); +const LazySet = require("../util/LazySet"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */ + +/** + * @template T + * @typedef {object} MatchedConfigs + * @property {Map} resolved + * @property {Map} unresolved + * @property {Map} prefixed + */ + +/** @type {ResolveOptionsWithDependencyType} */ +const RESOLVE_OPTIONS = { dependencyType: "esm" }; + +/** + * @template T + * @param {Compilation} compilation the compilation + * @param {[string, T][]} configs to be processed configs + * @returns {Promise>} resolved matchers + */ +module.exports.resolveMatchedConfigs = (compilation, configs) => { + /** @type {Map} */ + const resolved = new Map(); + /** @type {Map} */ + const unresolved = new Map(); + /** @type {Map} */ + const prefixed = new Map(); + const resolveContext = { + /** @type {LazySet} */ + fileDependencies: new LazySet(), + /** @type {LazySet} */ + contextDependencies: new LazySet(), + /** @type {LazySet} */ + missingDependencies: new LazySet() + }; + const resolver = compilation.resolverFactory.get("normal", RESOLVE_OPTIONS); + const context = compilation.compiler.context; + + return Promise.all( + // eslint-disable-next-line array-callback-return + configs.map(([request, config]) => { + if (/^\.\.?(\/|$)/.test(request)) { + // relative request + return new Promise(resolve => { + resolver.resolve( + {}, + context, + request, + resolveContext, + (err, result) => { + if (err || result === false) { + err = err || new Error(`Can't resolve ${request}`); + compilation.errors.push( + new ModuleNotFoundError(null, err, { + name: `shared module ${request}` + }) + ); + return resolve(null); + } + resolved.set(/** @type {string} */ (result), config); + resolve(null); + } + ); + }); + } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) { + // absolute path + resolved.set(request, config); + } else if (request.endsWith("/")) { + // module request prefix + prefixed.set(request, config); + } else { + // module request + unresolved.set(request, config); + } + }) + ).then(() => { + compilation.contextDependencies.addAll(resolveContext.contextDependencies); + compilation.fileDependencies.addAll(resolveContext.fileDependencies); + compilation.missingDependencies.addAll(resolveContext.missingDependencies); + return { resolved, unresolved, prefixed }; + }); +}; diff --git a/webpack-lib/lib/sharing/utils.js b/webpack-lib/lib/sharing/utils.js new file mode 100644 index 00000000000..bdeba0045c0 --- /dev/null +++ b/webpack-lib/lib/sharing/utils.js @@ -0,0 +1,425 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { join, dirname, readJson } = require("../util/fs"); + +/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ +/** @typedef {import("../util/fs").JsonObject} JsonObject */ +/** @typedef {import("../util/fs").JsonPrimitive} JsonPrimitive */ + +// Extreme shorthand only for github. eg: foo/bar +const RE_URL_GITHUB_EXTREME_SHORT = /^[^/@:.\s][^/@:\s]*\/[^@:\s]*[^/@:\s]#\S+/; + +// Short url with specific protocol. eg: github:foo/bar +const RE_GIT_URL_SHORT = /^(github|gitlab|bitbucket|gist):\/?[^/.]+\/?/i; + +// Currently supported protocols +const RE_PROTOCOL = + /^((git\+)?(ssh|https?|file)|git|github|gitlab|bitbucket|gist):$/i; + +// Has custom protocol +const RE_CUSTOM_PROTOCOL = /^((git\+)?(ssh|https?|file)|git):\/\//i; + +// Valid hash format for npm / yarn ... +const RE_URL_HASH_VERSION = /#(?:semver:)?(.+)/; + +// Simple hostname validate +const RE_HOSTNAME = /^(?:[^/.]+(\.[^/]+)+|localhost)$/; + +// For hostname with colon. eg: ssh://user@github.com:foo/bar +const RE_HOSTNAME_WITH_COLON = + /([^/@#:.]+(?:\.[^/@#:.]+)+|localhost):([^#/0-9]+)/; + +// Reg for url without protocol +const RE_NO_PROTOCOL = /^([^/@#:.]+(?:\.[^/@#:.]+)+)/; + +// RegExp for version string +const VERSION_PATTERN_REGEXP = /^([\d^=v<>~]|[*xX]$)/; + +// Specific protocol for short url without normal hostname +const PROTOCOLS_FOR_SHORT = [ + "github:", + "gitlab:", + "bitbucket:", + "gist:", + "file:" +]; + +// Default protocol for git url +const DEF_GIT_PROTOCOL = "git+ssh://"; + +// thanks to https://github.com/npm/hosted-git-info/blob/latest/git-host-info.js +const extractCommithashByDomain = { + /** + * @param {string} pathname pathname + * @param {string} hash hash + * @returns {string | undefined} hash + */ + "github.com": (pathname, hash) => { + let [, user, project, type, commithash] = pathname.split("/", 5); + if (type && type !== "tree") { + return; + } + + commithash = !type ? hash : `#${commithash}`; + + if (project && project.endsWith(".git")) { + project = project.slice(0, -4); + } + + if (!user || !project) { + return; + } + + return commithash; + }, + /** + * @param {string} pathname pathname + * @param {string} hash hash + * @returns {string | undefined} hash + */ + "gitlab.com": (pathname, hash) => { + const path = pathname.slice(1); + if (path.includes("/-/") || path.includes("/archive.tar.gz")) { + return; + } + + const segments = path.split("/"); + let project = /** @type {string} */ (segments.pop()); + if (project.endsWith(".git")) { + project = project.slice(0, -4); + } + + const user = segments.join("/"); + if (!user || !project) { + return; + } + + return hash; + }, + /** + * @param {string} pathname pathname + * @param {string} hash hash + * @returns {string | undefined} hash + */ + "bitbucket.org": (pathname, hash) => { + let [, user, project, aux] = pathname.split("/", 4); + if (["get"].includes(aux)) { + return; + } + + if (project && project.endsWith(".git")) { + project = project.slice(0, -4); + } + + if (!user || !project) { + return; + } + + return hash; + }, + /** + * @param {string} pathname pathname + * @param {string} hash hash + * @returns {string | undefined} hash + */ + "gist.github.com": (pathname, hash) => { + let [, user, project, aux] = pathname.split("/", 4); + if (aux === "raw") { + return; + } + + if (!project) { + if (!user) { + return; + } + + project = user; + } + + if (project.endsWith(".git")) { + project = project.slice(0, -4); + } + + return hash; + } +}; + +/** + * extract commit hash from parsed url + * @inner + * @param {URL} urlParsed parsed url + * @returns {string} commithash + */ +function getCommithash(urlParsed) { + let { hostname, pathname, hash } = urlParsed; + hostname = hostname.replace(/^www\./, ""); + + try { + hash = decodeURIComponent(hash); + // eslint-disable-next-line no-empty + } catch (_err) {} + + if ( + extractCommithashByDomain[ + /** @type {keyof extractCommithashByDomain} */ (hostname) + ] + ) { + return ( + extractCommithashByDomain[ + /** @type {keyof extractCommithashByDomain} */ (hostname) + ](pathname, hash) || "" + ); + } + + return hash; +} + +/** + * make url right for URL parse + * @inner + * @param {string} gitUrl git url + * @returns {string} fixed url + */ +function correctUrl(gitUrl) { + // like: + // proto://hostname.com:user/repo -> proto://hostname.com/user/repo + return gitUrl.replace(RE_HOSTNAME_WITH_COLON, "$1/$2"); +} + +/** + * make url protocol right for URL parse + * @inner + * @param {string} gitUrl git url + * @returns {string} fixed url + */ +function correctProtocol(gitUrl) { + // eg: github:foo/bar#v1.0. Should not add double slash, in case of error parsed `pathname` + if (RE_GIT_URL_SHORT.test(gitUrl)) { + return gitUrl; + } + + // eg: user@github.com:foo/bar + if (!RE_CUSTOM_PROTOCOL.test(gitUrl)) { + return `${DEF_GIT_PROTOCOL}${gitUrl}`; + } + + return gitUrl; +} + +/** + * extract git dep version from hash + * @inner + * @param {string} hash hash + * @returns {string} git dep version + */ +function getVersionFromHash(hash) { + const matched = hash.match(RE_URL_HASH_VERSION); + + return (matched && matched[1]) || ""; +} + +/** + * if string can be decoded + * @inner + * @param {string} str str to be checked + * @returns {boolean} if can be decoded + */ +function canBeDecoded(str) { + try { + decodeURIComponent(str); + } catch (_err) { + return false; + } + + return true; +} + +/** + * get right dep version from git url + * @inner + * @param {string} gitUrl git url + * @returns {string} dep version + */ +function getGitUrlVersion(gitUrl) { + const oriGitUrl = gitUrl; + // github extreme shorthand + gitUrl = RE_URL_GITHUB_EXTREME_SHORT.test(gitUrl) + ? `github:${gitUrl}` + : correctProtocol(gitUrl); + + gitUrl = correctUrl(gitUrl); + + let parsed; + try { + parsed = new URL(gitUrl); + // eslint-disable-next-line no-empty + } catch (_err) {} + + if (!parsed) { + return ""; + } + + const { protocol, hostname, pathname, username, password } = parsed; + if (!RE_PROTOCOL.test(protocol)) { + return ""; + } + + // pathname shouldn't be empty or URL malformed + if (!pathname || !canBeDecoded(pathname)) { + return ""; + } + + // without protocol, there should have auth info + if (RE_NO_PROTOCOL.test(oriGitUrl) && !username && !password) { + return ""; + } + + if (!PROTOCOLS_FOR_SHORT.includes(protocol.toLowerCase())) { + if (!RE_HOSTNAME.test(hostname)) { + return ""; + } + + const commithash = getCommithash(parsed); + return getVersionFromHash(commithash) || commithash; + } + + // for protocol short + return getVersionFromHash(gitUrl); +} + +/** + * @param {string} str maybe required version + * @returns {boolean} true, if it looks like a version + */ +function isRequiredVersion(str) { + return VERSION_PATTERN_REGEXP.test(str); +} + +module.exports.isRequiredVersion = isRequiredVersion; + +/** + * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#urls-as-dependencies + * @param {string} versionDesc version to be normalized + * @returns {string} normalized version + */ +function normalizeVersion(versionDesc) { + versionDesc = (versionDesc && versionDesc.trim()) || ""; + + if (isRequiredVersion(versionDesc)) { + return versionDesc; + } + + // add handle for URL Dependencies + return getGitUrlVersion(versionDesc.toLowerCase()); +} + +module.exports.normalizeVersion = normalizeVersion; + +/** @typedef {{ data: JsonObject, path: string }} DescriptionFile */ + +/** + * @param {InputFileSystem} fs file system + * @param {string} directory directory to start looking into + * @param {string[]} descriptionFiles possible description filenames + * @param {function((Error | null)=, DescriptionFile=, string[]=): void} callback callback + * @param {function(DescriptionFile=): boolean} satisfiesDescriptionFileData file data compliance check + * @param {Set} checkedFilePaths set of file paths that have been checked + */ +const getDescriptionFile = ( + fs, + directory, + descriptionFiles, + callback, + satisfiesDescriptionFileData, + checkedFilePaths = new Set() +) => { + let i = 0; + + const satisfiesDescriptionFileDataInternal = { + check: satisfiesDescriptionFileData, + checkedFilePaths + }; + + const tryLoadCurrent = () => { + if (i >= descriptionFiles.length) { + const parentDirectory = dirname(fs, directory); + if (!parentDirectory || parentDirectory === directory) { + return callback( + null, + undefined, + Array.from(satisfiesDescriptionFileDataInternal.checkedFilePaths) + ); + } + return getDescriptionFile( + fs, + parentDirectory, + descriptionFiles, + callback, + satisfiesDescriptionFileDataInternal.check, + satisfiesDescriptionFileDataInternal.checkedFilePaths + ); + } + const filePath = join(fs, directory, descriptionFiles[i]); + readJson(fs, filePath, (err, data) => { + if (err) { + if ("code" in err && err.code === "ENOENT") { + i++; + return tryLoadCurrent(); + } + return callback(err); + } + if (!data || typeof data !== "object" || Array.isArray(data)) { + return callback( + new Error(`Description file ${filePath} is not an object`) + ); + } + if ( + typeof satisfiesDescriptionFileDataInternal.check === "function" && + !satisfiesDescriptionFileDataInternal.check({ data, path: filePath }) + ) { + i++; + satisfiesDescriptionFileDataInternal.checkedFilePaths.add(filePath); + return tryLoadCurrent(); + } + callback(null, { data, path: filePath }); + }); + }; + tryLoadCurrent(); +}; +module.exports.getDescriptionFile = getDescriptionFile; + +/** + * @param {JsonObject} data description file data i.e.: package.json + * @param {string} packageName name of the dependency + * @returns {string | undefined} normalized version + */ +const getRequiredVersionFromDescriptionFile = (data, packageName) => { + const dependencyTypes = [ + "optionalDependencies", + "dependencies", + "peerDependencies", + "devDependencies" + ]; + + for (const dependencyType of dependencyTypes) { + const dependency = /** @type {JsonObject} */ (data[dependencyType]); + if ( + dependency && + typeof dependency === "object" && + packageName in dependency + ) { + return normalizeVersion( + /** @type {Exclude} */ ( + dependency[packageName] + ) + ); + } + } +}; +module.exports.getRequiredVersionFromDescriptionFile = + getRequiredVersionFromDescriptionFile; diff --git a/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js b/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js new file mode 100644 index 00000000000..471d7135296 --- /dev/null +++ b/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js @@ -0,0 +1,2614 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const { WEBPACK_MODULE_TYPE_RUNTIME } = require("../ModuleTypeConstants"); +const ModuleDependency = require("../dependencies/ModuleDependency"); +const formatLocation = require("../formatLocation"); +const { LogType } = require("../logging/Logger"); +const AggressiveSplittingPlugin = require("../optimize/AggressiveSplittingPlugin"); +const SizeLimitsPlugin = require("../performance/SizeLimitsPlugin"); +const { countIterable } = require("../util/IterableHelpers"); +const { + compareLocations, + compareChunksById, + compareNumbers, + compareIds, + concatComparators, + compareSelect, + compareModulesByIdentifier +} = require("../util/comparators"); +const { makePathsRelative, parseResource } = require("../util/identifier"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../ChunkGroup").OriginRecord} OriginRecord */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").Asset} Asset */ +/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ +/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ +/** @typedef {import("../ModuleProfile")} ModuleProfile */ +/** @typedef {import("../RequestShortener")} RequestShortener */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ +/** + * @template T + * @typedef {import("../util/comparators").Comparator} Comparator + */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** + * @template T, R + * @typedef {import("../util/smartGrouping").GroupConfig} GroupConfig + */ +/** @typedef {import("./StatsFactory")} StatsFactory */ +/** @typedef {import("./StatsFactory").StatsFactoryContext} StatsFactoryContext */ +/** @typedef {Record & KnownStatsCompilation} StatsCompilation */ +/** + * @typedef {object} KnownStatsCompilation + * @property {any=} env + * @property {string=} name + * @property {string=} hash + * @property {string=} version + * @property {number=} time + * @property {number=} builtAt + * @property {boolean=} needAdditionalPass + * @property {string=} publicPath + * @property {string=} outputPath + * @property {Record=} assetsByChunkName + * @property {StatsAsset[]=} assets + * @property {number=} filteredAssets + * @property {StatsChunk[]=} chunks + * @property {StatsModule[]=} modules + * @property {number=} filteredModules + * @property {Record=} entrypoints + * @property {Record=} namedChunkGroups + * @property {StatsError[]=} errors + * @property {number=} errorsCount + * @property {StatsError[]=} warnings + * @property {number=} warningsCount + * @property {StatsCompilation[]=} children + * @property {Record=} logging + */ + +/** @typedef {Record & KnownStatsLogging} StatsLogging */ +/** + * @typedef {object} KnownStatsLogging + * @property {StatsLoggingEntry[]} entries + * @property {number} filteredEntries + * @property {boolean} debug + */ + +/** @typedef {Record & KnownStatsLoggingEntry} StatsLoggingEntry */ +/** + * @typedef {object} KnownStatsLoggingEntry + * @property {string} type + * @property {string=} message + * @property {string[]=} trace + * @property {StatsLoggingEntry[]=} children + * @property {any[]=} args + * @property {number=} time + */ + +/** @typedef {Record & KnownStatsAsset} StatsAsset */ +/** + * @typedef {object} KnownStatsAsset + * @property {string} type + * @property {string} name + * @property {AssetInfo} info + * @property {number} size + * @property {boolean} emitted + * @property {boolean} comparedForEmit + * @property {boolean} cached + * @property {StatsAsset[]=} related + * @property {(string|number)[]=} chunkNames + * @property {(string|number)[]=} chunkIdHints + * @property {(string|number)[]=} chunks + * @property {(string|number)[]=} auxiliaryChunkNames + * @property {(string|number)[]=} auxiliaryChunks + * @property {(string|number)[]=} auxiliaryChunkIdHints + * @property {number=} filteredRelated + * @property {boolean=} isOverSizeLimit + */ + +/** @typedef {Record & KnownStatsChunkGroup} StatsChunkGroup */ +/** + * @typedef {object} KnownStatsChunkGroup + * @property {(string | null)=} name + * @property {(string | number)[]=} chunks + * @property {({ name: string, size?: number })[]=} assets + * @property {number=} filteredAssets + * @property {number=} assetsSize + * @property {({ name: string, size?: number })[]=} auxiliaryAssets + * @property {number=} filteredAuxiliaryAssets + * @property {number=} auxiliaryAssetsSize + * @property {{ [x: string]: StatsChunkGroup[] }=} children + * @property {{ [x: string]: string[] }=} childAssets + * @property {boolean=} isOverSizeLimit + */ + +/** @typedef {Record & KnownStatsModule} StatsModule */ +/** + * @typedef {object} KnownStatsModule + * @property {string=} type + * @property {string=} moduleType + * @property {(string | null)=} layer + * @property {string=} identifier + * @property {string=} name + * @property {(string | null)=} nameForCondition + * @property {number=} index + * @property {number=} preOrderIndex + * @property {number=} index2 + * @property {number=} postOrderIndex + * @property {number=} size + * @property {{ [x: string]: number }=} sizes + * @property {boolean=} cacheable + * @property {boolean=} built + * @property {boolean=} codeGenerated + * @property {boolean=} buildTimeExecuted + * @property {boolean=} cached + * @property {boolean=} optional + * @property {boolean=} orphan + * @property {string | number=} id + * @property {string | number | null=} issuerId + * @property {(string | number)[]=} chunks + * @property {(string | number)[]=} assets + * @property {boolean=} dependent + * @property {(string | null)=} issuer + * @property {(string | null)=} issuerName + * @property {StatsModuleIssuer[]=} issuerPath + * @property {boolean=} failed + * @property {number=} errors + * @property {number=} warnings + * @property {StatsProfile=} profile + * @property {StatsModuleReason[]=} reasons + * @property {(boolean | null | string[])=} usedExports + * @property {(string[] | null)=} providedExports + * @property {string[]=} optimizationBailout + * @property {(number | null)=} depth + * @property {StatsModule[]=} modules + * @property {number=} filteredModules + * @property {ReturnType=} source + */ + +/** @typedef {Record & KnownStatsProfile} StatsProfile */ +/** + * @typedef {object} KnownStatsProfile + * @property {number} total + * @property {number} resolving + * @property {number} restoring + * @property {number} building + * @property {number} integration + * @property {number} storing + * @property {number} additionalResolving + * @property {number} additionalIntegration + * @property {number} factory + * @property {number} dependencies + */ + +/** @typedef {Record & KnownStatsModuleIssuer} StatsModuleIssuer */ +/** + * @typedef {object} KnownStatsModuleIssuer + * @property {string} identifier + * @property {string} name + * @property {(string|number)=} id + * @property {StatsProfile} profile + */ + +/** @typedef {Record & KnownStatsModuleReason} StatsModuleReason */ +/** + * @typedef {object} KnownStatsModuleReason + * @property {string | null} moduleIdentifier + * @property {string | null} module + * @property {string | null} moduleName + * @property {string | null} resolvedModuleIdentifier + * @property {string | null} resolvedModule + * @property {string | null} type + * @property {boolean} active + * @property {string | null} explanation + * @property {string | null} userRequest + * @property {(string | null)=} loc + * @property {(string | number | null)=} moduleId + * @property {(string | number | null)=} resolvedModuleId + */ + +/** @typedef {Record & KnownStatsChunk} StatsChunk */ +/** + * @typedef {object} KnownStatsChunk + * @property {boolean} rendered + * @property {boolean} initial + * @property {boolean} entry + * @property {boolean} recorded + * @property {string=} reason + * @property {number} size + * @property {Record} sizes + * @property {string[]} names + * @property {string[]} idHints + * @property {string[]=} runtime + * @property {string[]} files + * @property {string[]} auxiliaryFiles + * @property {string} hash + * @property {Record} childrenByOrder + * @property {(string|number)=} id + * @property {(string|number)[]=} siblings + * @property {(string|number)[]=} parents + * @property {(string|number)[]=} children + * @property {StatsModule[]=} modules + * @property {number=} filteredModules + * @property {StatsChunkOrigin[]=} origins + */ + +/** @typedef {Record & KnownStatsChunkOrigin} StatsChunkOrigin */ +/** + * @typedef {object} KnownStatsChunkOrigin + * @property {string} module + * @property {string} moduleIdentifier + * @property {string} moduleName + * @property {string} loc + * @property {string} request + * @property {(string | number)=} moduleId + */ + +/** @typedef { Record & KnownStatsModuleTraceItem} StatsModuleTraceItem */ +/** + * @typedef {object} KnownStatsModuleTraceItem + * @property {string=} originIdentifier + * @property {string=} originName + * @property {string=} moduleIdentifier + * @property {string=} moduleName + * @property {StatsModuleTraceDependency[]=} dependencies + * @property {(string|number)=} originId + * @property {(string|number)=} moduleId + */ + +/** @typedef {Record & KnownStatsModuleTraceDependency} StatsModuleTraceDependency */ +/** + * @typedef {object} KnownStatsModuleTraceDependency + * @property {string=} loc + */ + +/** @typedef {Record & KnownStatsError} StatsError */ +/** + * @typedef {object} KnownStatsError + * @property {string} message + * @property {string=} chunkName + * @property {boolean=} chunkEntry + * @property {boolean=} chunkInitial + * @property {string=} file + * @property {string=} moduleIdentifier + * @property {string=} moduleName + * @property {string=} loc + * @property {ChunkId=} chunkId + * @property {string|number=} moduleId + * @property {StatsModuleTraceItem[]=} moduleTrace + * @property {any=} details + * @property {string=} stack + */ + +/** @typedef {Asset & { type: string, related: PreprocessedAsset[] | undefined }} PreprocessedAsset */ + +/** + * @template T + * @template O + * @typedef {Record void>} ExtractorsByOption + */ + +/** + * @typedef {object} SimpleExtractors + * @property {ExtractorsByOption} compilation + * @property {ExtractorsByOption} asset + * @property {ExtractorsByOption} asset$visible + * @property {ExtractorsByOption<{ name: string, chunkGroup: ChunkGroup }, StatsChunkGroup>} chunkGroup + * @property {ExtractorsByOption} module + * @property {ExtractorsByOption} module$visible + * @property {ExtractorsByOption} moduleIssuer + * @property {ExtractorsByOption} profile + * @property {ExtractorsByOption} moduleReason + * @property {ExtractorsByOption} chunk + * @property {ExtractorsByOption} chunkOrigin + * @property {ExtractorsByOption} error + * @property {ExtractorsByOption} warning + * @property {ExtractorsByOption<{ origin: Module, module: Module }, StatsModuleTraceItem>} moduleTraceItem + * @property {ExtractorsByOption} moduleTraceDependency + */ + +/** + * @template T + * @template I + * @param {Iterable} items items to select from + * @param {function(T): Iterable} selector selector function to select values from item + * @returns {I[]} array of values + */ +const uniqueArray = (items, selector) => { + /** @type {Set} */ + const set = new Set(); + for (const item of items) { + for (const i of selector(item)) { + set.add(i); + } + } + return Array.from(set); +}; + +/** + * @template T + * @template I + * @param {Iterable} items items to select from + * @param {function(T): Iterable} selector selector function to select values from item + * @param {Comparator} comparator comparator function + * @returns {I[]} array of values + */ +const uniqueOrderedArray = (items, selector, comparator) => + uniqueArray(items, selector).sort(comparator); + +/** @template T @template R @typedef {{ [P in keyof T]: R }} MappedValues */ + +/** + * @template {object} T + * @template {object} R + * @param {T} obj object to be mapped + * @param {function(T[keyof T], keyof T): R} fn mapping function + * @returns {MappedValues} mapped object + */ +const mapObject = (obj, fn) => { + const newObj = Object.create(null); + for (const key of Object.keys(obj)) { + newObj[key] = fn( + obj[/** @type {keyof T} */ (key)], + /** @type {keyof T} */ (key) + ); + } + return newObj; +}; + +/** + * @param {Compilation} compilation the compilation + * @param {function(Compilation, string): any[]} getItems get items + * @returns {number} total number + */ +const countWithChildren = (compilation, getItems) => { + let count = getItems(compilation, "").length; + for (const child of compilation.children) { + count += countWithChildren(child, (c, type) => + getItems(c, `.children[].compilation${type}`) + ); + } + return count; +}; + +/** @type {ExtractorsByOption} */ +const EXTRACT_ERROR = { + _: (object, error, context, { requestShortener }) => { + // TODO webpack 6 disallow strings in the errors/warnings list + if (typeof error === "string") { + object.message = error; + } else { + if (error.chunk) { + object.chunkName = error.chunk.name; + object.chunkEntry = error.chunk.hasRuntime(); + object.chunkInitial = error.chunk.canBeInitial(); + } + if (error.file) { + object.file = error.file; + } + if (error.module) { + object.moduleIdentifier = error.module.identifier(); + object.moduleName = error.module.readableIdentifier(requestShortener); + } + if (error.loc) { + object.loc = formatLocation(error.loc); + } + object.message = error.message; + } + }, + ids: (object, error, { compilation: { chunkGraph } }) => { + if (typeof error !== "string") { + if (error.chunk) { + object.chunkId = /** @type {ChunkId} */ (error.chunk.id); + } + if (error.module) { + object.moduleId = + /** @type {ModuleId} */ + (chunkGraph.getModuleId(error.module)); + } + } + }, + moduleTrace: (object, error, context, options, factory) => { + if (typeof error !== "string" && error.module) { + const { + type, + compilation: { moduleGraph } + } = context; + /** @type {Set} */ + const visitedModules = new Set(); + const moduleTrace = []; + let current = error.module; + while (current) { + if (visitedModules.has(current)) break; // circular (technically impossible, but how knows) + visitedModules.add(current); + const origin = moduleGraph.getIssuer(current); + if (!origin) break; + moduleTrace.push({ origin, module: current }); + current = origin; + } + object.moduleTrace = factory.create( + `${type}.moduleTrace`, + moduleTrace, + context + ); + } + }, + errorDetails: ( + object, + error, + { type, compilation, cachedGetErrors, cachedGetWarnings }, + { errorDetails } + ) => { + if ( + typeof error !== "string" && + (errorDetails === true || + (type.endsWith(".error") && cachedGetErrors(compilation).length < 3)) + ) { + object.details = error.details; + } + }, + errorStack: (object, error) => { + if (typeof error !== "string") { + object.stack = error.stack; + } + } +}; + +/** @type {SimpleExtractors} */ +const SIMPLE_EXTRACTORS = { + compilation: { + _: (object, compilation, context, options) => { + if (!context.makePathsRelative) { + context.makePathsRelative = makePathsRelative.bindContextCache( + compilation.compiler.context, + compilation.compiler.root + ); + } + if (!context.cachedGetErrors) { + const map = new WeakMap(); + context.cachedGetErrors = compilation => + map.get(compilation) || + // eslint-disable-next-line no-sequences + (errors => (map.set(compilation, errors), errors))( + compilation.getErrors() + ); + } + if (!context.cachedGetWarnings) { + const map = new WeakMap(); + context.cachedGetWarnings = compilation => + map.get(compilation) || + // eslint-disable-next-line no-sequences + (warnings => (map.set(compilation, warnings), warnings))( + compilation.getWarnings() + ); + } + if (compilation.name) { + object.name = compilation.name; + } + if (compilation.needAdditionalPass) { + object.needAdditionalPass = true; + } + + const { logging, loggingDebug, loggingTrace } = options; + if (logging || (loggingDebug && loggingDebug.length > 0)) { + const util = require("util"); + object.logging = {}; + let acceptedTypes; + let collapsedGroups = false; + switch (logging) { + case "error": + acceptedTypes = new Set([LogType.error]); + break; + case "warn": + acceptedTypes = new Set([LogType.error, LogType.warn]); + break; + case "info": + acceptedTypes = new Set([ + LogType.error, + LogType.warn, + LogType.info + ]); + break; + case "log": + acceptedTypes = new Set([ + LogType.error, + LogType.warn, + LogType.info, + LogType.log, + LogType.group, + LogType.groupEnd, + LogType.groupCollapsed, + LogType.clear + ]); + break; + case "verbose": + acceptedTypes = new Set([ + LogType.error, + LogType.warn, + LogType.info, + LogType.log, + LogType.group, + LogType.groupEnd, + LogType.groupCollapsed, + LogType.profile, + LogType.profileEnd, + LogType.time, + LogType.status, + LogType.clear + ]); + collapsedGroups = true; + break; + default: + acceptedTypes = new Set(); + break; + } + const cachedMakePathsRelative = makePathsRelative.bindContextCache( + options.context, + compilation.compiler.root + ); + let depthInCollapsedGroup = 0; + for (const [origin, logEntries] of compilation.logging) { + const debugMode = loggingDebug.some(fn => fn(origin)); + if (logging === false && !debugMode) continue; + /** @type {KnownStatsLoggingEntry[]} */ + const groupStack = []; + /** @type {KnownStatsLoggingEntry[]} */ + const rootList = []; + let currentList = rootList; + let processedLogEntries = 0; + for (const entry of logEntries) { + let type = entry.type; + if (!debugMode && !acceptedTypes.has(type)) continue; + + // Expand groups in verbose and debug modes + if ( + type === LogType.groupCollapsed && + (debugMode || collapsedGroups) + ) + type = LogType.group; + + if (depthInCollapsedGroup === 0) { + processedLogEntries++; + } + + if (type === LogType.groupEnd) { + groupStack.pop(); + currentList = + groupStack.length > 0 + ? /** @type {KnownStatsLoggingEntry[]} */ ( + groupStack[groupStack.length - 1].children + ) + : rootList; + if (depthInCollapsedGroup > 0) depthInCollapsedGroup--; + continue; + } + let message; + if (entry.type === LogType.time) { + const [label, first, second] = + /** @type {[string, number, number]} */ + (entry.args); + message = `${label}: ${first * 1000 + second / 1000000} ms`; + } else if (entry.args && entry.args.length > 0) { + message = util.format(entry.args[0], ...entry.args.slice(1)); + } + /** @type {KnownStatsLoggingEntry} */ + const newEntry = { + ...entry, + type, + message, + trace: loggingTrace ? entry.trace : undefined, + children: + type === LogType.group || type === LogType.groupCollapsed + ? [] + : undefined + }; + currentList.push(newEntry); + if (newEntry.children) { + groupStack.push(newEntry); + currentList = newEntry.children; + if (depthInCollapsedGroup > 0) { + depthInCollapsedGroup++; + } else if (type === LogType.groupCollapsed) { + depthInCollapsedGroup = 1; + } + } + } + let name = cachedMakePathsRelative(origin).replace(/\|/g, " "); + if (name in object.logging) { + let i = 1; + while (`${name}#${i}` in object.logging) { + i++; + } + name = `${name}#${i}`; + } + object.logging[name] = { + entries: rootList, + filteredEntries: logEntries.length - processedLogEntries, + debug: debugMode + }; + } + } + }, + hash: (object, compilation) => { + object.hash = /** @type {string} */ (compilation.hash); + }, + version: object => { + object.version = require("../../package.json").version; + }, + env: (object, compilation, context, { _env }) => { + object.env = _env; + }, + timings: (object, compilation) => { + object.time = + /** @type {number} */ (compilation.endTime) - + /** @type {number} */ (compilation.startTime); + }, + builtAt: (object, compilation) => { + object.builtAt = /** @type {number} */ (compilation.endTime); + }, + publicPath: (object, compilation) => { + object.publicPath = compilation.getPath( + /** @type {TemplatePath} */ + (compilation.outputOptions.publicPath) + ); + }, + outputPath: (object, compilation) => { + object.outputPath = /** @type {string} */ ( + compilation.outputOptions.path + ); + }, + assets: (object, compilation, context, options, factory) => { + const { type } = context; + /** @type {Map} */ + const compilationFileToChunks = new Map(); + /** @type {Map} */ + const compilationAuxiliaryFileToChunks = new Map(); + for (const chunk of compilation.chunks) { + for (const file of chunk.files) { + let array = compilationFileToChunks.get(file); + if (array === undefined) { + array = []; + compilationFileToChunks.set(file, array); + } + array.push(chunk); + } + for (const file of chunk.auxiliaryFiles) { + let array = compilationAuxiliaryFileToChunks.get(file); + if (array === undefined) { + array = []; + compilationAuxiliaryFileToChunks.set(file, array); + } + array.push(chunk); + } + } + /** @type {Map} */ + const assetMap = new Map(); + /** @type {Set} */ + const assets = new Set(); + for (const asset of compilation.getAssets()) { + /** @type {PreprocessedAsset} */ + const item = { + ...asset, + type: "asset", + related: undefined + }; + assets.add(item); + assetMap.set(asset.name, item); + } + for (const item of assetMap.values()) { + const related = item.info.related; + if (!related) continue; + for (const type of Object.keys(related)) { + const relatedEntry = related[type]; + const deps = Array.isArray(relatedEntry) + ? relatedEntry + : [relatedEntry]; + for (const dep of deps) { + const depItem = assetMap.get(dep); + if (!depItem) continue; + assets.delete(depItem); + depItem.type = type; + item.related = item.related || []; + item.related.push(depItem); + } + } + } + + object.assetsByChunkName = {}; + for (const [file, chunks] of compilationFileToChunks) { + for (const chunk of chunks) { + const name = chunk.name; + if (!name) continue; + if ( + !Object.prototype.hasOwnProperty.call( + object.assetsByChunkName, + name + ) + ) { + object.assetsByChunkName[name] = []; + } + object.assetsByChunkName[name].push(file); + } + } + + const groupedAssets = factory.create( + `${type}.assets`, + Array.from(assets), + { + ...context, + compilationFileToChunks, + compilationAuxiliaryFileToChunks + } + ); + const limited = spaceLimited( + groupedAssets, + /** @type {number} */ (options.assetsSpace) + ); + object.assets = limited.children; + object.filteredAssets = limited.filteredChildren; + }, + chunks: (object, compilation, context, options, factory) => { + const { type } = context; + object.chunks = factory.create( + `${type}.chunks`, + Array.from(compilation.chunks), + context + ); + }, + modules: (object, compilation, context, options, factory) => { + const { type } = context; + const array = Array.from(compilation.modules); + const groupedModules = factory.create(`${type}.modules`, array, context); + const limited = spaceLimited(groupedModules, options.modulesSpace); + object.modules = limited.children; + object.filteredModules = limited.filteredChildren; + }, + entrypoints: ( + object, + compilation, + context, + { entrypoints, chunkGroups, chunkGroupAuxiliary, chunkGroupChildren }, + factory + ) => { + const { type } = context; + const array = Array.from(compilation.entrypoints, ([key, value]) => ({ + name: key, + chunkGroup: value + })); + if (entrypoints === "auto" && !chunkGroups) { + if (array.length > 5) return; + if ( + !chunkGroupChildren && + array.every(({ chunkGroup }) => { + if (chunkGroup.chunks.length !== 1) return false; + const chunk = chunkGroup.chunks[0]; + return ( + chunk.files.size === 1 && + (!chunkGroupAuxiliary || chunk.auxiliaryFiles.size === 0) + ); + }) + ) { + return; + } + } + object.entrypoints = factory.create( + `${type}.entrypoints`, + array, + context + ); + }, + chunkGroups: (object, compilation, context, options, factory) => { + const { type } = context; + const array = Array.from( + compilation.namedChunkGroups, + ([key, value]) => ({ + name: key, + chunkGroup: value + }) + ); + object.namedChunkGroups = factory.create( + `${type}.namedChunkGroups`, + array, + context + ); + }, + errors: (object, compilation, context, options, factory) => { + const { type, cachedGetErrors } = context; + const rawErrors = cachedGetErrors(compilation); + const factorizedErrors = factory.create( + `${type}.errors`, + cachedGetErrors(compilation), + context + ); + let filtered = 0; + if (options.errorDetails === "auto" && rawErrors.length >= 3) { + filtered = rawErrors + .map(e => typeof e !== "string" && e.details) + .filter(Boolean).length; + } + if ( + options.errorDetails === true || + !Number.isFinite(options.errorsSpace) + ) { + object.errors = factorizedErrors; + if (filtered) object.filteredErrorDetailsCount = filtered; + return; + } + const [errors, filteredBySpace] = errorsSpaceLimit( + factorizedErrors, + /** @type {number} */ + (options.errorsSpace) + ); + object.filteredErrorDetailsCount = filtered + filteredBySpace; + object.errors = errors; + }, + errorsCount: (object, compilation, { cachedGetErrors }) => { + object.errorsCount = countWithChildren(compilation, c => + cachedGetErrors(c) + ); + }, + warnings: (object, compilation, context, options, factory) => { + const { type, cachedGetWarnings } = context; + const rawWarnings = factory.create( + `${type}.warnings`, + cachedGetWarnings(compilation), + context + ); + let filtered = 0; + if (options.errorDetails === "auto") { + filtered = cachedGetWarnings(compilation) + .map(e => typeof e !== "string" && e.details) + .filter(Boolean).length; + } + if ( + options.errorDetails === true || + !Number.isFinite(options.warningsSpace) + ) { + object.warnings = rawWarnings; + if (filtered) object.filteredWarningDetailsCount = filtered; + return; + } + const [warnings, filteredBySpace] = errorsSpaceLimit( + rawWarnings, + /** @type {number} */ + (options.warningsSpace) + ); + object.filteredWarningDetailsCount = filtered + filteredBySpace; + object.warnings = warnings; + }, + warningsCount: ( + object, + compilation, + context, + { warningsFilter }, + factory + ) => { + const { type, cachedGetWarnings } = context; + object.warningsCount = countWithChildren(compilation, (c, childType) => { + if ( + !warningsFilter && + /** @type {((warning: StatsError, textValue: string) => boolean)[]} */ + (warningsFilter).length === 0 + ) + return cachedGetWarnings(c); + return factory + .create(`${type}${childType}.warnings`, cachedGetWarnings(c), context) + .filter( + /** + * @param {TODO} warning warning + * @returns {boolean} result + */ + warning => { + const warningString = Object.keys(warning) + .map( + key => + `${warning[/** @type {keyof KnownStatsError} */ (key)]}` + ) + .join("\n"); + return !warningsFilter.some(filter => + filter(warning, warningString) + ); + } + ); + }); + }, + children: (object, compilation, context, options, factory) => { + const { type } = context; + object.children = factory.create( + `${type}.children`, + compilation.children, + context + ); + } + }, + asset: { + _: (object, asset, context, options, factory) => { + const { compilation } = context; + object.type = asset.type; + object.name = asset.name; + object.size = asset.source.size(); + object.emitted = compilation.emittedAssets.has(asset.name); + object.comparedForEmit = compilation.comparedForEmitAssets.has( + asset.name + ); + const cached = !object.emitted && !object.comparedForEmit; + object.cached = cached; + object.info = asset.info; + if (!cached || options.cachedAssets) { + Object.assign( + object, + factory.create(`${context.type}$visible`, asset, context) + ); + } + } + }, + asset$visible: { + _: ( + object, + asset, + { compilation, compilationFileToChunks, compilationAuxiliaryFileToChunks } + ) => { + const chunks = compilationFileToChunks.get(asset.name) || []; + const auxiliaryChunks = + compilationAuxiliaryFileToChunks.get(asset.name) || []; + object.chunkNames = uniqueOrderedArray( + chunks, + c => (c.name ? [c.name] : []), + compareIds + ); + object.chunkIdHints = uniqueOrderedArray( + chunks, + c => Array.from(c.idNameHints), + compareIds + ); + object.auxiliaryChunkNames = uniqueOrderedArray( + auxiliaryChunks, + c => (c.name ? [c.name] : []), + compareIds + ); + object.auxiliaryChunkIdHints = uniqueOrderedArray( + auxiliaryChunks, + c => Array.from(c.idNameHints), + compareIds + ); + object.filteredRelated = asset.related ? asset.related.length : undefined; + }, + relatedAssets: (object, asset, context, options, factory) => { + const { type } = context; + object.related = factory.create( + `${type.slice(0, -8)}.related`, + asset.related || [], + context + ); + object.filteredRelated = asset.related + ? asset.related.length - + /** @type {StatsAsset[]} */ (object.related).length + : undefined; + }, + ids: ( + object, + asset, + { compilationFileToChunks, compilationAuxiliaryFileToChunks } + ) => { + const chunks = compilationFileToChunks.get(asset.name) || []; + const auxiliaryChunks = + compilationAuxiliaryFileToChunks.get(asset.name) || []; + object.chunks = uniqueOrderedArray( + chunks, + c => /** @type {ChunkId[]} */ (c.ids), + compareIds + ); + object.auxiliaryChunks = uniqueOrderedArray( + auxiliaryChunks, + c => /** @type {ChunkId[]} */ (c.ids), + compareIds + ); + }, + performance: (object, asset) => { + object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(asset.source); + } + }, + chunkGroup: { + _: ( + object, + { name, chunkGroup }, + { compilation, compilation: { moduleGraph, chunkGraph } }, + { ids, chunkGroupAuxiliary, chunkGroupChildren, chunkGroupMaxAssets } + ) => { + const children = + chunkGroupChildren && + chunkGroup.getChildrenByOrders(moduleGraph, chunkGraph); + /** + * @param {string} name Name + * @returns {{ name: string, size: number }} Asset object + */ + const toAsset = name => { + const asset = compilation.getAsset(name); + return { + name, + size: /** @type {number} */ (asset ? asset.info.size : -1) + }; + }; + /** @type {(total: number, asset: { size: number }) => number} */ + const sizeReducer = (total, { size }) => total + size; + const assets = uniqueArray(chunkGroup.chunks, c => c.files).map(toAsset); + const auxiliaryAssets = uniqueOrderedArray( + chunkGroup.chunks, + c => c.auxiliaryFiles, + compareIds + ).map(toAsset); + const assetsSize = assets.reduce(sizeReducer, 0); + const auxiliaryAssetsSize = auxiliaryAssets.reduce(sizeReducer, 0); + /** @type {KnownStatsChunkGroup} */ + const statsChunkGroup = { + name, + chunks: ids + ? /** @type {ChunkId[]} */ (chunkGroup.chunks.map(c => c.id)) + : undefined, + assets: assets.length <= chunkGroupMaxAssets ? assets : undefined, + filteredAssets: + assets.length <= chunkGroupMaxAssets ? 0 : assets.length, + assetsSize, + auxiliaryAssets: + chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets + ? auxiliaryAssets + : undefined, + filteredAuxiliaryAssets: + chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets + ? 0 + : auxiliaryAssets.length, + auxiliaryAssetsSize, + children: children + ? mapObject(children, groups => + groups.map(group => { + const assets = uniqueArray(group.chunks, c => c.files).map( + toAsset + ); + const auxiliaryAssets = uniqueOrderedArray( + group.chunks, + c => c.auxiliaryFiles, + compareIds + ).map(toAsset); + + /** @type {KnownStatsChunkGroup} */ + const childStatsChunkGroup = { + name: group.name, + chunks: ids + ? /** @type {ChunkId[]} */ + (group.chunks.map(c => c.id)) + : undefined, + assets: + assets.length <= chunkGroupMaxAssets ? assets : undefined, + filteredAssets: + assets.length <= chunkGroupMaxAssets ? 0 : assets.length, + auxiliaryAssets: + chunkGroupAuxiliary && + auxiliaryAssets.length <= chunkGroupMaxAssets + ? auxiliaryAssets + : undefined, + filteredAuxiliaryAssets: + chunkGroupAuxiliary && + auxiliaryAssets.length <= chunkGroupMaxAssets + ? 0 + : auxiliaryAssets.length + }; + + return childStatsChunkGroup; + }) + ) + : undefined, + childAssets: children + ? mapObject(children, groups => { + /** @type {Set} */ + const set = new Set(); + for (const group of groups) { + for (const chunk of group.chunks) { + for (const asset of chunk.files) { + set.add(asset); + } + } + } + return Array.from(set); + }) + : undefined + }; + Object.assign(object, statsChunkGroup); + }, + performance: (object, { chunkGroup }) => { + object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(chunkGroup); + } + }, + module: { + _: (object, module, context, options, factory) => { + const { type } = context; + const compilation = /** @type {Compilation} */ (context.compilation); + const built = compilation.builtModules.has(module); + const codeGenerated = compilation.codeGeneratedModules.has(module); + const buildTimeExecuted = + compilation.buildTimeExecutedModules.has(module); + /** @type {{[x: string]: number}} */ + const sizes = {}; + for (const sourceType of module.getSourceTypes()) { + sizes[sourceType] = module.size(sourceType); + } + /** @type {KnownStatsModule} */ + const statsModule = { + type: "module", + moduleType: module.type, + layer: module.layer, + size: module.size(), + sizes, + built, + codeGenerated, + buildTimeExecuted, + cached: !built && !codeGenerated + }; + Object.assign(object, statsModule); + + if (built || codeGenerated || options.cachedModules) { + Object.assign( + object, + factory.create(`${type}$visible`, module, context) + ); + } + } + }, + module$visible: { + _: (object, module, context, { requestShortener }, factory) => { + const { type, rootModules } = context; + const compilation = /** @type {Compilation} */ (context.compilation); + const { moduleGraph } = compilation; + /** @type {Module[]} */ + const path = []; + const issuer = moduleGraph.getIssuer(module); + let current = issuer; + while (current) { + path.push(current); + current = moduleGraph.getIssuer(current); + } + path.reverse(); + const profile = moduleGraph.getProfile(module); + const errors = module.getErrors(); + const errorsCount = errors !== undefined ? countIterable(errors) : 0; + const warnings = module.getWarnings(); + const warningsCount = + warnings !== undefined ? countIterable(warnings) : 0; + /** @type {KnownStatsModule} */ + const statsModule = { + identifier: module.identifier(), + name: module.readableIdentifier(requestShortener), + nameForCondition: module.nameForCondition(), + index: /** @type {number} */ (moduleGraph.getPreOrderIndex(module)), + preOrderIndex: /** @type {number} */ ( + moduleGraph.getPreOrderIndex(module) + ), + index2: /** @type {number} */ (moduleGraph.getPostOrderIndex(module)), + postOrderIndex: /** @type {number} */ ( + moduleGraph.getPostOrderIndex(module) + ), + cacheable: /** @type {BuildInfo} */ (module.buildInfo).cacheable, + optional: module.isOptional(moduleGraph), + orphan: + !type.endsWith("module.modules[].module$visible") && + compilation.chunkGraph.getNumberOfModuleChunks(module) === 0, + dependent: rootModules ? !rootModules.has(module) : undefined, + issuer: issuer && issuer.identifier(), + issuerName: issuer && issuer.readableIdentifier(requestShortener), + issuerPath: + issuer && + factory.create(`${type.slice(0, -8)}.issuerPath`, path, context), + failed: errorsCount > 0, + errors: errorsCount, + warnings: warningsCount + }; + Object.assign(object, statsModule); + if (profile) { + object.profile = factory.create( + `${type.slice(0, -8)}.profile`, + profile, + context + ); + } + }, + ids: (object, module, { compilation: { chunkGraph, moduleGraph } }) => { + object.id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + const issuer = moduleGraph.getIssuer(module); + object.issuerId = issuer && chunkGraph.getModuleId(issuer); + object.chunks = + /** @type {ChunkId[]} */ + ( + Array.from( + chunkGraph.getOrderedModuleChunksIterable( + module, + compareChunksById + ), + chunk => chunk.id + ) + ); + }, + moduleAssets: (object, module) => { + object.assets = /** @type {BuildInfo} */ (module.buildInfo).assets + ? Object.keys(/** @type {BuildInfo} */ (module.buildInfo).assets) + : []; + }, + reasons: (object, module, context, options, factory) => { + const { + type, + compilation: { moduleGraph } + } = context; + const groupsReasons = factory.create( + `${type.slice(0, -8)}.reasons`, + Array.from(moduleGraph.getIncomingConnections(module)), + context + ); + const limited = spaceLimited( + groupsReasons, + /** @type {number} */ + (options.reasonsSpace) + ); + object.reasons = limited.children; + object.filteredReasons = limited.filteredChildren; + }, + usedExports: ( + object, + module, + { runtime, compilation: { moduleGraph } } + ) => { + const usedExports = moduleGraph.getUsedExports(module, runtime); + if (usedExports === null) { + object.usedExports = null; + } else if (typeof usedExports === "boolean") { + object.usedExports = usedExports; + } else { + object.usedExports = Array.from(usedExports); + } + }, + providedExports: (object, module, { compilation: { moduleGraph } }) => { + const providedExports = moduleGraph.getProvidedExports(module); + object.providedExports = Array.isArray(providedExports) + ? providedExports + : null; + }, + optimizationBailout: ( + object, + module, + { compilation: { moduleGraph } }, + { requestShortener } + ) => { + object.optimizationBailout = moduleGraph + .getOptimizationBailout(module) + .map(item => { + if (typeof item === "function") return item(requestShortener); + return item; + }); + }, + depth: (object, module, { compilation: { moduleGraph } }) => { + object.depth = moduleGraph.getDepth(module); + }, + nestedModules: (object, module, context, options, factory) => { + const { type } = context; + const innerModules = /** @type {Module & { modules?: Module[] }} */ ( + module + ).modules; + if (Array.isArray(innerModules)) { + const groupedModules = factory.create( + `${type.slice(0, -8)}.modules`, + innerModules, + context + ); + const limited = spaceLimited( + groupedModules, + options.nestedModulesSpace + ); + object.modules = limited.children; + object.filteredModules = limited.filteredChildren; + } + }, + source: (object, module) => { + const originalSource = module.originalSource(); + if (originalSource) { + object.source = originalSource.source(); + } + } + }, + profile: { + _: (object, profile) => { + /** @type {KnownStatsProfile} */ + const statsProfile = { + total: + profile.factory + + profile.restoring + + profile.integration + + profile.building + + profile.storing, + resolving: profile.factory, + restoring: profile.restoring, + building: profile.building, + integration: profile.integration, + storing: profile.storing, + additionalResolving: profile.additionalFactories, + additionalIntegration: profile.additionalIntegration, + // TODO remove this in webpack 6 + factory: profile.factory, + // TODO remove this in webpack 6 + dependencies: profile.additionalFactories + }; + Object.assign(object, statsProfile); + } + }, + moduleIssuer: { + _: (object, module, context, { requestShortener }, factory) => { + const { type } = context; + const compilation = /** @type {Compilation} */ (context.compilation); + const { moduleGraph } = compilation; + const profile = moduleGraph.getProfile(module); + /** @type {Partial} */ + const statsModuleIssuer = { + identifier: module.identifier(), + name: module.readableIdentifier(requestShortener) + }; + Object.assign(object, statsModuleIssuer); + if (profile) { + object.profile = factory.create(`${type}.profile`, profile, context); + } + }, + ids: (object, module, { compilation: { chunkGraph } }) => { + object.id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); + } + }, + moduleReason: { + _: (object, reason, { runtime }, { requestShortener }) => { + const dep = reason.dependency; + const moduleDep = + dep && dep instanceof ModuleDependency ? dep : undefined; + /** @type {KnownStatsModuleReason} */ + const statsModuleReason = { + moduleIdentifier: reason.originModule + ? reason.originModule.identifier() + : null, + module: reason.originModule + ? reason.originModule.readableIdentifier(requestShortener) + : null, + moduleName: reason.originModule + ? reason.originModule.readableIdentifier(requestShortener) + : null, + resolvedModuleIdentifier: reason.resolvedOriginModule + ? reason.resolvedOriginModule.identifier() + : null, + resolvedModule: reason.resolvedOriginModule + ? reason.resolvedOriginModule.readableIdentifier(requestShortener) + : null, + type: reason.dependency ? reason.dependency.type : null, + active: reason.isActive(runtime), + explanation: reason.explanation, + userRequest: (moduleDep && moduleDep.userRequest) || null + }; + Object.assign(object, statsModuleReason); + if (reason.dependency) { + const locInfo = formatLocation(reason.dependency.loc); + if (locInfo) { + object.loc = locInfo; + } + } + }, + ids: (object, reason, { compilation: { chunkGraph } }) => { + object.moduleId = reason.originModule + ? chunkGraph.getModuleId(reason.originModule) + : null; + object.resolvedModuleId = reason.resolvedOriginModule + ? chunkGraph.getModuleId(reason.resolvedOriginModule) + : null; + } + }, + chunk: { + _: (object, chunk, { makePathsRelative, compilation: { chunkGraph } }) => { + const childIdByOrder = chunk.getChildIdsByOrders(chunkGraph); + + /** @type {KnownStatsChunk} */ + const statsChunk = { + rendered: chunk.rendered, + initial: chunk.canBeInitial(), + entry: chunk.hasRuntime(), + recorded: AggressiveSplittingPlugin.wasChunkRecorded(chunk), + reason: chunk.chunkReason, + size: chunkGraph.getChunkModulesSize(chunk), + sizes: chunkGraph.getChunkModulesSizes(chunk), + names: chunk.name ? [chunk.name] : [], + idHints: Array.from(chunk.idNameHints), + runtime: + chunk.runtime === undefined + ? undefined + : typeof chunk.runtime === "string" + ? [makePathsRelative(chunk.runtime)] + : Array.from(chunk.runtime.sort(), makePathsRelative), + files: Array.from(chunk.files), + auxiliaryFiles: Array.from(chunk.auxiliaryFiles).sort(compareIds), + hash: /** @type {string} */ (chunk.renderedHash), + childrenByOrder: childIdByOrder + }; + Object.assign(object, statsChunk); + }, + ids: (object, chunk) => { + object.id = /** @type {ChunkId} */ (chunk.id); + }, + chunkRelations: (object, chunk, { compilation: { chunkGraph } }) => { + /** @type {Set} */ + const parents = new Set(); + /** @type {Set} */ + const children = new Set(); + /** @type {Set} */ + const siblings = new Set(); + + for (const chunkGroup of chunk.groupsIterable) { + for (const parentGroup of chunkGroup.parentsIterable) { + for (const chunk of parentGroup.chunks) { + parents.add(/** @type {ChunkId} */ (chunk.id)); + } + } + for (const childGroup of chunkGroup.childrenIterable) { + for (const chunk of childGroup.chunks) { + children.add(/** @type {ChunkId} */ (chunk.id)); + } + } + for (const sibling of chunkGroup.chunks) { + if (sibling !== chunk) + siblings.add(/** @type {ChunkId} */ (sibling.id)); + } + } + object.siblings = Array.from(siblings).sort(compareIds); + object.parents = Array.from(parents).sort(compareIds); + object.children = Array.from(children).sort(compareIds); + }, + chunkModules: (object, chunk, context, options, factory) => { + const { + type, + compilation: { chunkGraph } + } = context; + const array = chunkGraph.getChunkModules(chunk); + const groupedModules = factory.create(`${type}.modules`, array, { + ...context, + runtime: chunk.runtime, + rootModules: new Set(chunkGraph.getChunkRootModules(chunk)) + }); + const limited = spaceLimited(groupedModules, options.chunkModulesSpace); + object.modules = limited.children; + object.filteredModules = limited.filteredChildren; + }, + chunkOrigins: (object, chunk, context, options, factory) => { + const { + type, + compilation: { chunkGraph } + } = context; + /** @type {Set} */ + const originsKeySet = new Set(); + const origins = []; + for (const g of chunk.groupsIterable) { + origins.push(...g.origins); + } + const array = origins.filter(origin => { + const key = [ + origin.module ? chunkGraph.getModuleId(origin.module) : undefined, + formatLocation(origin.loc), + origin.request + ].join(); + if (originsKeySet.has(key)) return false; + originsKeySet.add(key); + return true; + }); + object.origins = factory.create(`${type}.origins`, array, context); + } + }, + chunkOrigin: { + _: (object, origin, context, { requestShortener }) => { + /** @type {KnownStatsChunkOrigin} */ + const statsChunkOrigin = { + 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 + }; + Object.assign(object, statsChunkOrigin); + }, + ids: (object, origin, { compilation: { chunkGraph } }) => { + object.moduleId = origin.module + ? /** @type {ModuleId} */ (chunkGraph.getModuleId(origin.module)) + : undefined; + } + }, + error: EXTRACT_ERROR, + warning: EXTRACT_ERROR, + moduleTraceItem: { + _: (object, { origin, module }, context, { requestShortener }, factory) => { + const { + type, + compilation: { moduleGraph } + } = context; + object.originIdentifier = origin.identifier(); + object.originName = origin.readableIdentifier(requestShortener); + object.moduleIdentifier = module.identifier(); + object.moduleName = module.readableIdentifier(requestShortener); + const dependencies = Array.from( + moduleGraph.getIncomingConnections(module) + ) + .filter(c => c.resolvedOriginModule === origin && c.dependency) + .map(c => c.dependency); + object.dependencies = factory.create( + `${type}.dependencies`, + Array.from(new Set(dependencies)), + context + ); + }, + ids: (object, { origin, module }, { compilation: { chunkGraph } }) => { + object.originId = + /** @type {ModuleId} */ + (chunkGraph.getModuleId(origin)); + object.moduleId = + /** @type {ModuleId} */ + (chunkGraph.getModuleId(module)); + } + }, + moduleTraceDependency: { + _: (object, dependency) => { + object.loc = formatLocation(dependency.loc); + } + } +}; + +/** @type {Record boolean | undefined>>} */ +const FILTER = { + "module.reasons": { + "!orphanModules": (reason, { compilation: { chunkGraph } }) => { + if ( + reason.originModule && + chunkGraph.getNumberOfModuleChunks(reason.originModule) === 0 + ) { + return false; + } + } + } +}; + +/** @type {Record boolean | undefined>>} */ +const FILTER_RESULTS = { + "compilation.warnings": { + warningsFilter: util.deprecate( + (warning, context, { warningsFilter }) => { + const warningString = Object.keys(warning) + .map(key => `${warning[/** @type {keyof KnownStatsError} */ (key)]}`) + .join("\n"); + return !warningsFilter.some(filter => filter(warning, warningString)); + }, + "config.stats.warningsFilter is deprecated in favor of config.ignoreWarnings", + "DEP_WEBPACK_STATS_WARNINGS_FILTER" + ) + } +}; + +/** @type {Record void>} */ +const MODULES_SORTER = { + _: (comparators, { compilation: { moduleGraph } }) => { + comparators.push( + compareSelect( + /** + * @param {Module} m module + * @returns {number | null} depth + */ + m => moduleGraph.getDepth(m), + compareNumbers + ), + compareSelect( + /** + * @param {Module} m module + * @returns {number | null} index + */ + m => moduleGraph.getPreOrderIndex(m), + compareNumbers + ), + compareSelect( + /** + * @param {Module} m module + * @returns {string} identifier + */ + m => m.identifier(), + compareIds + ) + ); + } +}; + +/** @type {Record void>>} */ +const SORTERS = { + "compilation.chunks": { + _: comparators => { + comparators.push(compareSelect(c => c.id, compareIds)); + } + }, + "compilation.modules": MODULES_SORTER, + "chunk.rootModules": MODULES_SORTER, + "chunk.modules": MODULES_SORTER, + "module.modules": MODULES_SORTER, + "module.reasons": { + _: (comparators, { compilation: { chunkGraph } }) => { + comparators.push( + compareSelect(x => x.originModule, compareModulesByIdentifier) + ); + comparators.push( + compareSelect(x => x.resolvedOriginModule, compareModulesByIdentifier) + ); + comparators.push( + compareSelect( + x => x.dependency, + concatComparators( + compareSelect( + /** + * @param {Dependency} x dependency + * @returns {DependencyLocation} location + */ + x => x.loc, + compareLocations + ), + compareSelect(x => x.type, compareIds) + ) + ) + ); + } + }, + "chunk.origins": { + _: (comparators, { compilation: { chunkGraph } }) => { + comparators.push( + compareSelect( + origin => + origin.module ? chunkGraph.getModuleId(origin.module) : undefined, + compareIds + ), + compareSelect(origin => formatLocation(origin.loc), compareIds), + compareSelect(origin => origin.request, compareIds) + ); + } + } +}; + +/** + * @template T + * @typedef {T & { children: Children[] | undefined, filteredChildren?: number }} Children + */ + +/** + * @template T + * @param {Children} item item + * @returns {number} item size + */ +const getItemSize = item => + // Each item takes 1 line + // + the size of the children + // + 1 extra line when it has children and filteredChildren + !item.children + ? 1 + : item.filteredChildren + ? 2 + getTotalSize(item.children) + : 1 + getTotalSize(item.children); + +/** + * @template T + * @param {Children[]} children children + * @returns {number} total size + */ +const getTotalSize = children => { + let size = 0; + for (const child of children) { + size += getItemSize(child); + } + return size; +}; + +/** + * @template T + * @param {Children[]} children children + * @returns {number} total items + */ +const getTotalItems = children => { + let count = 0; + for (const child of children) { + if (!child.children && !child.filteredChildren) { + count++; + } else { + if (child.children) count += getTotalItems(child.children); + if (child.filteredChildren) count += child.filteredChildren; + } + } + return count; +}; + +/** + * @template T + * @param {Children[]} children children + * @returns {Children[]} collapsed children + */ +const collapse = children => { + // After collapse each child must take exactly one line + const newChildren = []; + for (const child of children) { + if (child.children) { + let filteredChildren = child.filteredChildren || 0; + filteredChildren += getTotalItems(child.children); + newChildren.push({ + ...child, + children: undefined, + filteredChildren + }); + } else { + newChildren.push(child); + } + } + return newChildren; +}; + +/** + * @template T + * @param {Children[]} itemsAndGroups item and groups + * @param {number} max max + * @param {boolean=} filteredChildrenLineReserved filtered children line reserved + * @returns {Children} result + */ +const spaceLimited = ( + itemsAndGroups, + max, + filteredChildrenLineReserved = false +) => { + if (max < 1) { + return /** @type {Children} */ ({ + children: undefined, + filteredChildren: getTotalItems(itemsAndGroups) + }); + } + /** @type {Children[] | undefined} */ + let children; + /** @type {number | undefined} */ + let filteredChildren; + // This are the groups, which take 1+ lines each + /** @type {Children[] | undefined} */ + const groups = []; + // The sizes of the groups are stored in groupSizes + /** @type {number[]} */ + const groupSizes = []; + // This are the items, which take 1 line each + const items = []; + // The total of group sizes + let groupsSize = 0; + + for (const itemOrGroup of itemsAndGroups) { + // is item + if (!itemOrGroup.children && !itemOrGroup.filteredChildren) { + items.push(itemOrGroup); + } else { + groups.push(itemOrGroup); + const size = getItemSize(itemOrGroup); + groupSizes.push(size); + groupsSize += size; + } + } + + if (groupsSize + items.length <= max) { + // The total size in the current state fits into the max + // keep all + children = groups.length > 0 ? groups.concat(items) : items; + } else if (groups.length === 0) { + // slice items to max + // inner space marks that lines for filteredChildren already reserved + const limit = max - (filteredChildrenLineReserved ? 0 : 1); + filteredChildren = items.length - limit; + items.length = limit; + children = items; + } else { + // limit is the size when all groups are collapsed + const limit = + groups.length + + (filteredChildrenLineReserved || items.length === 0 ? 0 : 1); + if (limit < max) { + // calculate how much we are over the size limit + // this allows to approach the limit faster + let oversize; + // If each group would take 1 line the total would be below the maximum + // collapse some groups, keep items + while ( + (oversize = + groupsSize + + items.length + + (filteredChildren && !filteredChildrenLineReserved ? 1 : 0) - + max) > 0 + ) { + // Find the maximum group and process only this one + const maxGroupSize = Math.max(...groupSizes); + if (maxGroupSize < items.length) { + filteredChildren = items.length; + items.length = 0; + continue; + } + for (let i = 0; i < groups.length; i++) { + if (groupSizes[i] === maxGroupSize) { + const group = groups[i]; + // run this algorithm recursively and limit the size of the children to + // current size - oversize / number of groups + // So it should always end up being smaller + const headerSize = group.filteredChildren ? 2 : 1; + const limited = spaceLimited( + /** @type {Children} */ (group.children), + maxGroupSize - + // we should use ceil to always feet in max + Math.ceil(oversize / groups.length) - + // we substitute size of group head + headerSize, + headerSize === 2 + ); + groups[i] = { + ...group, + children: limited.children, + filteredChildren: limited.filteredChildren + ? (group.filteredChildren || 0) + limited.filteredChildren + : group.filteredChildren + }; + const newSize = getItemSize(groups[i]); + groupsSize -= maxGroupSize - newSize; + groupSizes[i] = newSize; + break; + } + } + } + children = groups.concat(items); + } else if (limit === max) { + // If we have only enough space to show one line per group and one line for the filtered items + // collapse all groups and items + children = collapse(groups); + filteredChildren = items.length; + } else { + // If we have no space + // collapse complete group + filteredChildren = getTotalItems(itemsAndGroups); + } + } + + return /** @type {Children} */ ({ children, filteredChildren }); +}; + +/** + * @param {StatsError[]} errors errors + * @param {number} max max + * @returns {[StatsError[], number]} error space limit + */ +const errorsSpaceLimit = (errors, max) => { + let filtered = 0; + // Can not fit into limit + // print only messages + if (errors.length + 1 >= max) + return [ + errors.map(error => { + if (typeof error === "string" || !error.details) return error; + filtered++; + return { ...error, details: "" }; + }), + filtered + ]; + let fullLength = errors.length; + let result = errors; + + let i = 0; + for (; i < errors.length; i++) { + const error = errors[i]; + if (typeof error !== "string" && error.details) { + const splitted = error.details.split("\n"); + const len = splitted.length; + fullLength += len; + if (fullLength > max) { + result = i > 0 ? errors.slice(0, i) : []; + const overLimit = fullLength - max + 1; + const error = errors[i++]; + result.push({ + ...error, + details: error.details.split("\n").slice(0, -overLimit).join("\n"), + filteredDetails: overLimit + }); + filtered = errors.length - i; + for (; i < errors.length; i++) { + const error = errors[i]; + if (typeof error === "string" || !error.details) result.push(error); + result.push({ ...error, details: "" }); + } + break; + } else if (fullLength === max) { + result = errors.slice(0, ++i); + filtered = errors.length - i; + for (; i < errors.length; i++) { + const error = errors[i]; + if (typeof error === "string" || !error.details) result.push(error); + result.push({ ...error, details: "" }); + } + break; + } + } + } + + return [result, filtered]; +}; + +/** + * @template {{ size: number }} T + * @template {{ size: number }} R + * @param {(R | T)[]} children children + * @param {T[]} assets assets + * @returns {{ size: number }} asset size + */ +const assetGroup = (children, assets) => { + let size = 0; + for (const asset of children) { + size += asset.size; + } + return { size }; +}; + +/** + * @template {{ size: number, sizes: Record }} T + * @param {Children[]} children children + * @param {KnownStatsModule[]} modules modules + * @returns {{ size: number, sizes: Record}} size and sizes + */ +const moduleGroup = (children, modules) => { + let size = 0; + /** @type {Record} */ + const sizes = {}; + for (const module of children) { + size += module.size; + for (const key of Object.keys(module.sizes)) { + sizes[key] = (sizes[key] || 0) + module.sizes[key]; + } + } + return { + size, + sizes + }; +}; + +/** + * @template {{ active: boolean }} T + * @param {Children[]} children children + * @param {KnownStatsModuleReason[]} reasons reasons + * @returns {{ active: boolean }} reason group + */ +const reasonGroup = (children, reasons) => { + let active = false; + for (const reason of children) { + active = active || reason.active; + } + return { + active + }; +}; + +const GROUP_EXTENSION_REGEXP = /(\.[^.]+?)(?:\?|(?: \+ \d+ modules?)?$)/; +const GROUP_PATH_REGEXP = /(.+)[/\\][^/\\]+?(?:\?|(?: \+ \d+ modules?)?$)/; + +/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} AssetsGroupers */ + +/** @type {AssetsGroupers} */ +const ASSETS_GROUPERS = { + _: (groupConfigs, context, options) => { + /** + * @param {keyof KnownStatsAsset} name name + * @param {boolean=} exclude need exclude? + */ + const groupByFlag = (name, exclude) => { + groupConfigs.push({ + getKeys: asset => (asset[name] ? ["1"] : undefined), + getOptions: () => ({ + groupChildren: !exclude, + force: exclude + }), + createGroup: (key, children, assets) => + exclude + ? { + type: "assets by status", + [name]: Boolean(key), + filteredChildren: assets.length, + ...assetGroup(children, assets) + } + : { + type: "assets by status", + [name]: Boolean(key), + children, + ...assetGroup(children, assets) + } + }); + }; + const { + groupAssetsByEmitStatus, + groupAssetsByPath, + groupAssetsByExtension + } = options; + if (groupAssetsByEmitStatus) { + groupByFlag("emitted"); + groupByFlag("comparedForEmit"); + groupByFlag("isOverSizeLimit"); + } + if (groupAssetsByEmitStatus || !options.cachedAssets) { + groupByFlag("cached", !options.cachedAssets); + } + if (groupAssetsByPath || groupAssetsByExtension) { + groupConfigs.push({ + getKeys: asset => { + const extensionMatch = + groupAssetsByExtension && GROUP_EXTENSION_REGEXP.exec(asset.name); + const extension = extensionMatch ? extensionMatch[1] : ""; + const pathMatch = + groupAssetsByPath && GROUP_PATH_REGEXP.exec(asset.name); + const path = pathMatch ? pathMatch[1].split(/[/\\]/) : []; + const keys = []; + if (groupAssetsByPath) { + keys.push("."); + if (extension) + keys.push( + path.length + ? `${path.join("/")}/*${extension}` + : `*${extension}` + ); + while (path.length > 0) { + keys.push(`${path.join("/")}/`); + path.pop(); + } + } else if (extension) { + keys.push(`*${extension}`); + } + return keys; + }, + createGroup: (key, children, assets) => ({ + type: groupAssetsByPath ? "assets by path" : "assets by extension", + name: key, + children, + ...assetGroup(children, assets) + }) + }); + } + }, + groupAssetsByInfo: (groupConfigs, context, options) => { + /** + * @param {string} name name + */ + const groupByAssetInfoFlag = name => { + groupConfigs.push({ + getKeys: asset => (asset.info && asset.info[name] ? ["1"] : undefined), + createGroup: (key, children, assets) => ({ + type: "assets by info", + info: { + [name]: Boolean(key) + }, + children, + ...assetGroup(children, assets) + }) + }); + }; + groupByAssetInfoFlag("immutable"); + groupByAssetInfoFlag("development"); + groupByAssetInfoFlag("hotModuleReplacement"); + }, + groupAssetsByChunk: (groupConfigs, context, options) => { + /** + * @param {keyof KnownStatsAsset} name name + */ + const groupByNames = name => { + groupConfigs.push({ + getKeys: asset => /** @type {string[]} */ (asset[name]), + createGroup: (key, children, assets) => ({ + type: "assets by chunk", + [name]: [key], + children, + ...assetGroup(children, assets) + }) + }); + }; + groupByNames("chunkNames"); + groupByNames("auxiliaryChunkNames"); + groupByNames("chunkIdHints"); + groupByNames("auxiliaryChunkIdHints"); + }, + excludeAssets: (groupConfigs, context, { excludeAssets }) => { + groupConfigs.push({ + getKeys: asset => { + const ident = asset.name; + const excluded = excludeAssets.some(fn => fn(ident, asset)); + if (excluded) return ["excluded"]; + }, + getOptions: () => ({ + groupChildren: false, + force: true + }), + createGroup: (key, children, assets) => ({ + type: "hidden assets", + filteredChildren: assets.length, + ...assetGroup(children, assets) + }) + }); + } +}; + +/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} ModulesGroupers */ + +/** @type {function("module" | "chunk" | "root-of-chunk" | "nested"): ModulesGroupers} */ +const MODULES_GROUPERS = type => ({ + _: (groupConfigs, context, options) => { + /** + * @param {keyof KnownStatsModule} name name + * @param {string} type type + * @param {boolean=} exclude need exclude? + */ + const groupByFlag = (name, type, exclude) => { + groupConfigs.push({ + getKeys: module => (module[name] ? ["1"] : undefined), + getOptions: () => ({ + groupChildren: !exclude, + force: exclude + }), + createGroup: (key, children, modules) => ({ + type, + [name]: Boolean(key), + ...(exclude ? { filteredChildren: modules.length } : { children }), + ...moduleGroup(children, modules) + }) + }); + }; + const { + groupModulesByCacheStatus, + groupModulesByLayer, + groupModulesByAttributes, + groupModulesByType, + groupModulesByPath, + groupModulesByExtension + } = options; + if (groupModulesByAttributes) { + groupByFlag("errors", "modules with errors"); + groupByFlag("warnings", "modules with warnings"); + groupByFlag("assets", "modules with assets"); + groupByFlag("optional", "optional modules"); + } + if (groupModulesByCacheStatus) { + groupByFlag("cacheable", "cacheable modules"); + groupByFlag("built", "built modules"); + groupByFlag("codeGenerated", "code generated modules"); + } + if (groupModulesByCacheStatus || !options.cachedModules) { + groupByFlag("cached", "cached modules", !options.cachedModules); + } + if (groupModulesByAttributes || !options.orphanModules) { + groupByFlag("orphan", "orphan modules", !options.orphanModules); + } + if (groupModulesByAttributes || !options.dependentModules) { + groupByFlag("dependent", "dependent modules", !options.dependentModules); + } + if (groupModulesByType || !options.runtimeModules) { + groupConfigs.push({ + getKeys: module => { + if (!module.moduleType) return; + if (groupModulesByType) { + return [module.moduleType.split("/", 1)[0]]; + } else if (module.moduleType === WEBPACK_MODULE_TYPE_RUNTIME) { + return [WEBPACK_MODULE_TYPE_RUNTIME]; + } + }, + getOptions: key => { + const exclude = + key === WEBPACK_MODULE_TYPE_RUNTIME && !options.runtimeModules; + return { + groupChildren: !exclude, + force: exclude + }; + }, + createGroup: (key, children, modules) => { + const exclude = + key === WEBPACK_MODULE_TYPE_RUNTIME && !options.runtimeModules; + return { + type: `${key} modules`, + moduleType: key, + ...(exclude ? { filteredChildren: modules.length } : { children }), + ...moduleGroup(children, modules) + }; + } + }); + } + if (groupModulesByLayer) { + groupConfigs.push({ + getKeys: module => /** @type {string[]} */ ([module.layer]), + createGroup: (key, children, modules) => ({ + type: "modules by layer", + layer: key, + children, + ...moduleGroup(children, modules) + }) + }); + } + if (groupModulesByPath || groupModulesByExtension) { + groupConfigs.push({ + getKeys: module => { + if (!module.name) return; + const resource = parseResource( + /** @type {string} */ (module.name.split("!").pop()) + ).path; + const dataUrl = /^data:[^,;]+/.exec(resource); + if (dataUrl) return [dataUrl[0]]; + const extensionMatch = + groupModulesByExtension && GROUP_EXTENSION_REGEXP.exec(resource); + const extension = extensionMatch ? extensionMatch[1] : ""; + const pathMatch = + groupModulesByPath && GROUP_PATH_REGEXP.exec(resource); + const path = pathMatch ? pathMatch[1].split(/[/\\]/) : []; + const keys = []; + if (groupModulesByPath) { + if (extension) + keys.push( + path.length + ? `${path.join("/")}/*${extension}` + : `*${extension}` + ); + while (path.length > 0) { + keys.push(`${path.join("/")}/`); + path.pop(); + } + } else if (extension) { + keys.push(`*${extension}`); + } + return keys; + }, + createGroup: (key, children, modules) => { + const isDataUrl = key.startsWith("data:"); + return { + type: isDataUrl + ? "modules by mime type" + : groupModulesByPath + ? "modules by path" + : "modules by extension", + name: isDataUrl ? key.slice(/* 'data:'.length */ 5) : key, + children, + ...moduleGroup(children, modules) + }; + } + }); + } + }, + excludeModules: (groupConfigs, context, { excludeModules }) => { + groupConfigs.push({ + getKeys: module => { + const name = module.name; + if (name) { + const excluded = excludeModules.some(fn => fn(name, module, type)); + if (excluded) return ["1"]; + } + }, + getOptions: () => ({ + groupChildren: false, + force: true + }), + createGroup: (key, children, modules) => ({ + type: "hidden modules", + filteredChildren: children.length, + ...moduleGroup(children, modules) + }) + }); + } +}); + +/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} ModuleReasonsGroupers */ + +/** @type {ModuleReasonsGroupers} */ +const MODULE_REASONS_GROUPERS = { + groupReasonsByOrigin: groupConfigs => { + groupConfigs.push({ + getKeys: reason => /** @type {string[]} */ ([reason.module]), + createGroup: (key, children, reasons) => ({ + type: "from origin", + module: key, + children, + ...reasonGroup(children, reasons) + }) + }); + } +}; + +/** @type {Record} */ +const RESULT_GROUPERS = { + "compilation.assets": ASSETS_GROUPERS, + "asset.related": ASSETS_GROUPERS, + "compilation.modules": MODULES_GROUPERS("module"), + "chunk.modules": MODULES_GROUPERS("chunk"), + "chunk.rootModules": MODULES_GROUPERS("root-of-chunk"), + "module.modules": MODULES_GROUPERS("nested"), + "module.reasons": MODULE_REASONS_GROUPERS +}; + +// remove a prefixed "!" that can be specified to reverse sort order +/** + * @param {string} field a field name + * @returns {field} normalized field + */ +const normalizeFieldKey = field => { + if (field[0] === "!") { + return field.slice(1); + } + return field; +}; + +// if a field is prefixed by a "!" reverse sort order +/** + * @param {string} field a field name + * @returns {boolean} result + */ +const sortOrderRegular = field => { + if (field[0] === "!") { + return false; + } + return true; +}; + +/** + * @template T + * @param {string} field field name + * @returns {function(T, T): 0 | 1 | -1} comparators + */ +const sortByField = field => { + if (!field) { + /** + * @param {any} a first + * @param {any} b second + * @returns {-1|0|1} zero + */ + const noSort = (a, b) => 0; + return noSort; + } + + const fieldKey = normalizeFieldKey(field); + + let sortFn = compareSelect(m => m[fieldKey], compareIds); + + // if a field is prefixed with a "!" the sort is reversed! + const sortIsRegular = sortOrderRegular(field); + + if (!sortIsRegular) { + const oldSortFn = sortFn; + sortFn = (a, b) => oldSortFn(b, a); + } + + return sortFn; +}; + +/** @type {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} */ +const ASSET_SORTERS = { + assetsSort: (comparators, context, { assetsSort }) => { + comparators.push(sortByField(assetsSort)); + }, + _: comparators => { + comparators.push(compareSelect(a => a.name, compareIds)); + } +}; + +/** @type {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>>} */ +const RESULT_SORTERS = { + "compilation.chunks": { + chunksSort: (comparators, context, { chunksSort }) => { + comparators.push(sortByField(chunksSort)); + } + }, + "compilation.modules": { + modulesSort: (comparators, context, { modulesSort }) => { + comparators.push(sortByField(modulesSort)); + } + }, + "chunk.modules": { + chunkModulesSort: (comparators, context, { chunkModulesSort }) => { + comparators.push(sortByField(chunkModulesSort)); + } + }, + "module.modules": { + nestedModulesSort: (comparators, context, { nestedModulesSort }) => { + comparators.push(sortByField(nestedModulesSort)); + } + }, + "compilation.assets": ASSET_SORTERS, + "asset.related": ASSET_SORTERS +}; + +/** + * @param {Record>} config the config see above + * @param {NormalizedStatsOptions} options stats options + * @param {function(string, Function): void} fn handler function called for every active line in config + * @returns {void} + */ +const iterateConfig = (config, options, fn) => { + for (const hookFor of Object.keys(config)) { + const subConfig = config[hookFor]; + for (const option of Object.keys(subConfig)) { + if (option !== "_") { + if (option.startsWith("!")) { + if (options[option.slice(1)]) continue; + } else { + const value = options[option]; + if ( + value === false || + value === undefined || + (Array.isArray(value) && value.length === 0) + ) + continue; + } + } + fn(hookFor, subConfig[option]); + } + } +}; + +/** @type {Record} */ +const ITEM_NAMES = { + "compilation.children[]": "compilation", + "compilation.modules[]": "module", + "compilation.entrypoints[]": "chunkGroup", + "compilation.namedChunkGroups[]": "chunkGroup", + "compilation.errors[]": "error", + "compilation.warnings[]": "warning", + "chunk.modules[]": "module", + "chunk.rootModules[]": "module", + "chunk.origins[]": "chunkOrigin", + "compilation.chunks[]": "chunk", + "compilation.assets[]": "asset", + "asset.related[]": "asset", + "module.issuerPath[]": "moduleIssuer", + "module.reasons[]": "moduleReason", + "module.modules[]": "module", + "module.children[]": "module", + "moduleTrace[]": "moduleTraceItem", + "moduleTraceItem.dependencies[]": "moduleTraceDependency" +}; + +/** + * @template T + * @typedef {{ name: T }} NamedObject + */ + +/** + * @template {{ name: string }} T + * @param {T[]} items items to be merged + * @returns {NamedObject} an object + */ +const mergeToObject = items => { + const obj = Object.create(null); + for (const item of items) { + obj[item.name] = item; + } + return obj; +}; + +/** + * @template {{ name: string }} T + * @type {Record NamedObject>} + */ +const MERGER = { + "compilation.entrypoints": mergeToObject, + "compilation.namedChunkGroups": mergeToObject +}; + +class DefaultStatsFactoryPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => { + compilation.hooks.statsFactory.tap( + "DefaultStatsFactoryPlugin", + /** + * @param {StatsFactory} stats stats factory + * @param {NormalizedStatsOptions} options stats options + */ + (stats, options) => { + iterateConfig(SIMPLE_EXTRACTORS, options, (hookFor, fn) => { + stats.hooks.extract + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (obj, data, ctx) => + fn(obj, data, ctx, options, stats) + ); + }); + iterateConfig(FILTER, options, (hookFor, fn) => { + stats.hooks.filter + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) => + fn(item, ctx, options, idx, i) + ); + }); + iterateConfig(FILTER_RESULTS, options, (hookFor, fn) => { + stats.hooks.filterResults + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) => + fn(item, ctx, options, idx, i) + ); + }); + iterateConfig(SORTERS, options, (hookFor, fn) => { + stats.hooks.sort + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (comparators, ctx) => + fn(comparators, ctx, options) + ); + }); + iterateConfig(RESULT_SORTERS, options, (hookFor, fn) => { + stats.hooks.sortResults + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (comparators, ctx) => + fn(comparators, ctx, options) + ); + }); + iterateConfig(RESULT_GROUPERS, options, (hookFor, fn) => { + stats.hooks.groupResults + .for(hookFor) + .tap("DefaultStatsFactoryPlugin", (groupConfigs, ctx) => + fn(groupConfigs, ctx, options) + ); + }); + for (const key of Object.keys(ITEM_NAMES)) { + const itemName = ITEM_NAMES[key]; + stats.hooks.getItemName + .for(key) + .tap("DefaultStatsFactoryPlugin", () => itemName); + } + for (const key of Object.keys(MERGER)) { + const merger = MERGER[key]; + stats.hooks.merge.for(key).tap("DefaultStatsFactoryPlugin", merger); + } + if (options.children) { + if (Array.isArray(options.children)) { + stats.hooks.getItemFactory + .for("compilation.children[].compilation") + .tap( + "DefaultStatsFactoryPlugin", + /** + * @param {Compilation} comp compilation + * @param {StatsFactoryContext} options options + * @returns {StatsFactory | undefined} stats factory + */ + (comp, { _index: idx }) => { + const children = + /** @type {TODO} */ + (options.children); + if (idx < children.length) { + return compilation.createStatsFactory( + compilation.createStatsOptions(children[idx]) + ); + } + } + ); + } else if (options.children !== true) { + const childFactory = compilation.createStatsFactory( + compilation.createStatsOptions(options.children) + ); + stats.hooks.getItemFactory + .for("compilation.children[].compilation") + .tap("DefaultStatsFactoryPlugin", () => childFactory); + } + } + } + ); + }); + } +} +module.exports = DefaultStatsFactoryPlugin; diff --git a/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js b/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js new file mode 100644 index 00000000000..70e56b8cb3e --- /dev/null +++ b/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js @@ -0,0 +1,386 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RequestShortener = require("../RequestShortener"); + +/** @typedef {import("../../declarations/WebpackOptions").StatsOptions} StatsOptions */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").CreateStatsOptionsContext} CreateStatsOptionsContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */ + +/** + * @param {StatsOptions} options options + * @param {StatsOptions} defaults default options + */ +const applyDefaults = (options, defaults) => { + for (const _k of Object.keys(defaults)) { + const key = /** @type {keyof StatsOptions} */ (_k); + if (typeof options[key] === "undefined") { + /** @type {TODO} */ + (options)[key] = defaults[key]; + } + } +}; + +/** @typedef {Record} NamedPresets */ +/** @type {NamedPresets} */ +const NAMED_PRESETS = { + verbose: { + hash: true, + builtAt: true, + relatedAssets: true, + entrypoints: true, + chunkGroups: true, + ids: true, + modules: false, + chunks: true, + chunkRelations: true, + chunkModules: true, + dependentModules: true, + chunkOrigins: true, + depth: true, + env: true, + reasons: true, + usedExports: true, + providedExports: true, + optimizationBailout: true, + errorDetails: true, + errorStack: true, + publicPath: true, + logging: "verbose", + orphanModules: true, + runtimeModules: true, + exclude: false, + errorsSpace: Infinity, + warningsSpace: Infinity, + modulesSpace: Infinity, + chunkModulesSpace: Infinity, + assetsSpace: Infinity, + reasonsSpace: Infinity, + children: true + }, + detailed: { + hash: true, + builtAt: true, + relatedAssets: true, + entrypoints: true, + chunkGroups: true, + ids: true, + chunks: true, + chunkRelations: true, + chunkModules: false, + chunkOrigins: true, + depth: true, + usedExports: true, + providedExports: true, + optimizationBailout: true, + errorDetails: true, + publicPath: true, + logging: true, + runtimeModules: true, + exclude: false, + errorsSpace: 1000, + warningsSpace: 1000, + modulesSpace: 1000, + assetsSpace: 1000, + reasonsSpace: 1000 + }, + minimal: { + all: false, + version: true, + timings: true, + modules: true, + errorsSpace: 0, + warningsSpace: 0, + modulesSpace: 0, + assets: true, + assetsSpace: 0, + errors: true, + errorsCount: true, + warnings: true, + warningsCount: true, + logging: "warn" + }, + "errors-only": { + all: false, + errors: true, + errorsCount: true, + errorsSpace: Infinity, + moduleTrace: true, + logging: "error" + }, + "errors-warnings": { + all: false, + errors: true, + errorsCount: true, + errorsSpace: Infinity, + warnings: true, + warningsCount: true, + warningsSpace: Infinity, + logging: "warn" + }, + summary: { + all: false, + version: true, + errorsCount: true, + warningsCount: true + }, + none: { + all: false + } +}; + +/** + * @param {StatsOptions} all stats option + * @returns {boolean} true when enabled, otherwise false + */ +const NORMAL_ON = ({ all }) => all !== false; +/** + * @param {StatsOptions} all stats option + * @returns {boolean} true when enabled, otherwise false + */ +const NORMAL_OFF = ({ all }) => all === true; +/** + * @param {StatsOptions} all stats option + * @param {CreateStatsOptionsContext} forToString stats options context + * @returns {boolean} true when enabled, otherwise false + */ +const ON_FOR_TO_STRING = ({ all }, { forToString }) => + forToString ? all !== false : all === true; +/** + * @param {StatsOptions} all stats option + * @param {CreateStatsOptionsContext} forToString stats options context + * @returns {boolean} true when enabled, otherwise false + */ +const OFF_FOR_TO_STRING = ({ all }, { forToString }) => + forToString ? all === true : all !== false; +/** + * @param {StatsOptions} all stats option + * @param {CreateStatsOptionsContext} forToString stats options context + * @returns {boolean | "auto"} true when enabled, otherwise false + */ +const AUTO_FOR_TO_STRING = ({ all }, { forToString }) => { + if (all === false) return false; + if (all === true) return true; + if (forToString) return "auto"; + return true; +}; + +/** @typedef {Record StatsOptions[keyof StatsOptions] | RequestShortener>} Defaults */ + +/** @type {Defaults} */ +const DEFAULTS = { + context: (options, context, compilation) => compilation.compiler.context, + requestShortener: (options, context, compilation) => + compilation.compiler.context === options.context + ? compilation.requestShortener + : new RequestShortener( + /** @type {string} */ + (options.context), + compilation.compiler.root + ), + performance: NORMAL_ON, + hash: OFF_FOR_TO_STRING, + env: NORMAL_OFF, + version: NORMAL_ON, + timings: NORMAL_ON, + builtAt: OFF_FOR_TO_STRING, + assets: NORMAL_ON, + entrypoints: AUTO_FOR_TO_STRING, + chunkGroups: OFF_FOR_TO_STRING, + chunkGroupAuxiliary: OFF_FOR_TO_STRING, + chunkGroupChildren: OFF_FOR_TO_STRING, + chunkGroupMaxAssets: (o, { forToString }) => (forToString ? 5 : Infinity), + chunks: OFF_FOR_TO_STRING, + chunkRelations: OFF_FOR_TO_STRING, + chunkModules: ({ all, modules }) => { + if (all === false) return false; + if (all === true) return true; + if (modules) return false; + return true; + }, + dependentModules: OFF_FOR_TO_STRING, + chunkOrigins: OFF_FOR_TO_STRING, + ids: OFF_FOR_TO_STRING, + modules: ({ all, chunks, chunkModules }, { forToString }) => { + if (all === false) return false; + if (all === true) return true; + if (forToString && chunks && chunkModules) return false; + return true; + }, + nestedModules: OFF_FOR_TO_STRING, + groupModulesByType: ON_FOR_TO_STRING, + groupModulesByCacheStatus: ON_FOR_TO_STRING, + groupModulesByLayer: ON_FOR_TO_STRING, + groupModulesByAttributes: ON_FOR_TO_STRING, + groupModulesByPath: ON_FOR_TO_STRING, + groupModulesByExtension: ON_FOR_TO_STRING, + modulesSpace: (o, { forToString }) => (forToString ? 15 : Infinity), + chunkModulesSpace: (o, { forToString }) => (forToString ? 10 : Infinity), + nestedModulesSpace: (o, { forToString }) => (forToString ? 10 : Infinity), + relatedAssets: OFF_FOR_TO_STRING, + groupAssetsByEmitStatus: ON_FOR_TO_STRING, + groupAssetsByInfo: ON_FOR_TO_STRING, + groupAssetsByPath: ON_FOR_TO_STRING, + groupAssetsByExtension: ON_FOR_TO_STRING, + groupAssetsByChunk: ON_FOR_TO_STRING, + assetsSpace: (o, { forToString }) => (forToString ? 15 : Infinity), + orphanModules: OFF_FOR_TO_STRING, + runtimeModules: ({ all, runtime }, { forToString }) => + runtime !== undefined + ? runtime + : forToString + ? all === true + : all !== false, + cachedModules: ({ all, cached }, { forToString }) => + cached !== undefined ? cached : forToString ? all === true : all !== false, + moduleAssets: OFF_FOR_TO_STRING, + depth: OFF_FOR_TO_STRING, + cachedAssets: OFF_FOR_TO_STRING, + reasons: OFF_FOR_TO_STRING, + reasonsSpace: (o, { forToString }) => (forToString ? 15 : Infinity), + groupReasonsByOrigin: ON_FOR_TO_STRING, + usedExports: OFF_FOR_TO_STRING, + providedExports: OFF_FOR_TO_STRING, + optimizationBailout: OFF_FOR_TO_STRING, + children: OFF_FOR_TO_STRING, + source: NORMAL_OFF, + moduleTrace: NORMAL_ON, + errors: NORMAL_ON, + errorsCount: NORMAL_ON, + errorDetails: AUTO_FOR_TO_STRING, + errorStack: OFF_FOR_TO_STRING, + warnings: NORMAL_ON, + warningsCount: NORMAL_ON, + publicPath: OFF_FOR_TO_STRING, + logging: ({ all }, { forToString }) => + forToString && all !== false ? "info" : false, + loggingDebug: () => [], + loggingTrace: OFF_FOR_TO_STRING, + excludeModules: () => [], + excludeAssets: () => [], + modulesSort: () => "depth", + chunkModulesSort: () => "name", + nestedModulesSort: () => false, + chunksSort: () => false, + assetsSort: () => "!size", + outputPath: OFF_FOR_TO_STRING, + colors: () => false +}; + +/** + * @param {string | ({ test: function(string): boolean }) | (function(string): boolean) | boolean} item item to normalize + * @returns {(function(string): boolean) | undefined} normalize fn + */ +const normalizeFilter = item => { + if (typeof item === "string") { + const regExp = new RegExp( + `[\\\\/]${item.replace(/[-[\]{}()*+?.\\^$|]/g, "\\$&")}([\\\\/]|$|!|\\?)` + ); + 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; + } +}; + +/** @type {Record} */ +const NORMALIZER = { + excludeModules: value => { + if (!Array.isArray(value)) { + value = value ? [value] : []; + } + return value.map(normalizeFilter); + }, + excludeAssets: value => { + if (!Array.isArray(value)) { + value = value ? [value] : []; + } + return value.map(normalizeFilter); + }, + warningsFilter: value => { + if (!Array.isArray(value)) { + value = value ? [value] : []; + } + /** + * @callback WarningFilterFn + * @param {StatsError} warning warning + * @param {string} warningString warning string + * @returns {boolean} result + */ + return value.map( + /** + * @param {StatsOptions["warningsFilter"]} filter a warning filter + * @returns {WarningFilterFn} result + */ + filter => { + if (typeof filter === "string") { + return (warning, warningString) => warningString.includes(filter); + } + if (filter instanceof RegExp) { + return (warning, warningString) => filter.test(warningString); + } + if (typeof filter === "function") { + return filter; + } + throw new Error( + `Can only filter warnings with Strings or RegExps. (Given: ${filter})` + ); + } + ); + }, + logging: value => { + if (value === true) value = "log"; + return value; + }, + loggingDebug: value => { + if (!Array.isArray(value)) { + value = value ? [value] : []; + } + return value.map(normalizeFilter); + } +}; + +class DefaultStatsPresetPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("DefaultStatsPresetPlugin", compilation => { + for (const key of Object.keys(NAMED_PRESETS)) { + const defaults = NAMED_PRESETS[/** @type {keyof NamedPresets} */ (key)]; + compilation.hooks.statsPreset + .for(key) + .tap("DefaultStatsPresetPlugin", (options, context) => { + applyDefaults(options, defaults); + }); + } + compilation.hooks.statsNormalize.tap( + "DefaultStatsPresetPlugin", + (options, context) => { + for (const key of Object.keys(DEFAULTS)) { + if (options[key] === undefined) + options[key] = DEFAULTS[key](options, context, compilation); + } + for (const key of Object.keys(NORMALIZER)) { + options[key] = NORMALIZER[key](options[key]); + } + } + ); + }); + } +} +module.exports = DefaultStatsPresetPlugin; diff --git a/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js b/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js new file mode 100644 index 00000000000..419311a72a5 --- /dev/null +++ b/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js @@ -0,0 +1,1695 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("./DefaultStatsFactoryPlugin").KnownStatsChunkGroup} KnownStatsChunkGroup */ +/** @typedef {import("./StatsPrinter")} StatsPrinter */ +/** @typedef {import("./StatsPrinter").KnownStatsPrinterColorFn} KnownStatsPrinterColorFn */ +/** @typedef {import("./StatsPrinter").KnownStatsPrinterFormaters} KnownStatsPrinterFormaters */ +/** @typedef {import("./StatsPrinter").StatsPrinterContext} StatsPrinterContext */ + +const DATA_URI_CONTENT_LENGTH = 16; +const MAX_MODULE_IDENTIFIER_LENGTH = 80; + +/** + * @param {number} n a number + * @param {string} singular singular + * @param {string} plural plural + * @returns {string} if n is 1, singular, else plural + */ +const plural = (n, singular, plural) => (n === 1 ? singular : plural); + +/** + * @param {Record} sizes sizes by source type + * @param {StatsPrinterContext} options options + * @returns {string | undefined} text + */ +const printSizes = (sizes, { formatSize = n => `${n}` }) => { + const keys = Object.keys(sizes); + if (keys.length > 1) { + return keys.map(key => `${formatSize(sizes[key])} (${key})`).join(" "); + } else if (keys.length === 1) { + return formatSize(sizes[keys[0]]); + } +}; + +/** + * @param {string} resource resource + * @returns {string} resource name for display + */ +const getResourceName = resource => { + const dataUrl = /^data:[^,]+,/.exec(resource); + if (!dataUrl) return resource; + + const len = dataUrl[0].length + DATA_URI_CONTENT_LENGTH; + if (resource.length < len) return resource; + return `${resource.slice( + 0, + Math.min(resource.length - /* '..'.length */ 2, len) + )}..`; +}; + +/** + * @param {string} name module name + * @returns {[string,string]} prefix and module name + */ +const getModuleName = name => { + const [, prefix, resource] = + /** @type {[any, string, string]} */ + (/** @type {unknown} */ (/^(.*!)?([^!]*)$/.exec(name))); + + if (resource.length > MAX_MODULE_IDENTIFIER_LENGTH) { + const truncatedResource = `${resource.slice( + 0, + Math.min( + resource.length - /* '...(truncated)'.length */ 14, + MAX_MODULE_IDENTIFIER_LENGTH + ) + )}...(truncated)`; + + return [prefix, getResourceName(truncatedResource)]; + } + + return [prefix, getResourceName(resource)]; +}; + +/** + * @param {string} str string + * @param {function(string): string} fn function to apply to each line + * @returns {string} joined string + */ +const mapLines = (str, fn) => str.split("\n").map(fn).join("\n"); + +/** + * @param {number} n a number + * @returns {string} number as two digit string, leading 0 + */ +const twoDigit = n => (n >= 10 ? `${n}` : `0${n}`); + +/** + * @param {string | number} id an id + * @returns {boolean | string} is i + */ +const isValidId = id => typeof id === "number" || id; + +/** + * @template T + * @param {Array | undefined} list of items + * @param {number} count number of items to show + * @returns {string} string representation of list + */ +const moreCount = (list, count) => + list && list.length > 0 ? `+ ${count}` : `${count}`; + +/** + * @template T + * @template {keyof T} K + * @typedef {{ [P in K]-?: T[P] }} WithRequired + */ + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const COMPILATION_SIMPLE_PRINTERS = { + "compilation.summary!": ( + _, + { + type, + bold, + green, + red, + yellow, + formatDateTime, + formatTime, + compilation: { + name, + hash, + version, + time, + builtAt, + errorsCount, + warningsCount + } + } + ) => { + const root = type === "compilation.summary!"; + const warningsMessage = + /** @type {number} */ (warningsCount) > 0 + ? yellow( + `${warningsCount} ${plural(/** @type {number} */ (warningsCount), "warning", "warnings")}` + ) + : ""; + const errorsMessage = + /** @type {number} */ (errorsCount) > 0 + ? red( + `${errorsCount} ${plural(/** @type {number} */ (errorsCount), "error", "errors")}` + ) + : ""; + const timeMessage = root && time ? ` in ${formatTime(time)}` : ""; + const hashMessage = hash ? ` (${hash})` : ""; + const builtAtMessage = + root && builtAt ? `${formatDateTime(builtAt)}: ` : ""; + const versionMessage = root && version ? `webpack ${version}` : ""; + const nameMessage = + root && name + ? bold(name) + : name + ? `Child ${bold(name)}` + : root + ? "" + : "Child"; + const subjectMessage = + nameMessage && versionMessage + ? `${nameMessage} (${versionMessage})` + : versionMessage || nameMessage || "webpack"; + let statusMessage; + if (errorsMessage && warningsMessage) { + statusMessage = `compiled with ${errorsMessage} and ${warningsMessage}`; + } else if (errorsMessage) { + statusMessage = `compiled with ${errorsMessage}`; + } else if (warningsMessage) { + statusMessage = `compiled with ${warningsMessage}`; + } else if (errorsCount === 0 && warningsCount === 0) { + statusMessage = `compiled ${green("successfully")}`; + } else { + statusMessage = "compiled"; + } + if ( + builtAtMessage || + versionMessage || + errorsMessage || + warningsMessage || + (errorsCount === 0 && warningsCount === 0) || + timeMessage || + hashMessage + ) + return `${builtAtMessage}${subjectMessage} ${statusMessage}${timeMessage}${hashMessage}`; + }, + "compilation.filteredWarningDetailsCount": count => + count + ? `${count} ${plural( + count, + "warning has", + "warnings have" + )} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.` + : undefined, + "compilation.filteredErrorDetailsCount": (count, { yellow }) => + count + ? yellow( + `${count} ${plural( + count, + "error has", + "errors have" + )} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.` + ) + : undefined, + "compilation.env": (env, { bold }) => + env + ? `Environment (--env): ${bold(JSON.stringify(env, null, 2))}` + : undefined, + "compilation.publicPath": (publicPath, { bold }) => + `PublicPath: ${bold(publicPath || "(none)")}`, + "compilation.entrypoints": (entrypoints, context, printer) => + Array.isArray(entrypoints) + ? undefined + : printer.print(context.type, Object.values(entrypoints), { + ...context, + chunkGroupKind: "Entrypoint" + }), + "compilation.namedChunkGroups": (namedChunkGroups, context, printer) => { + if (!Array.isArray(namedChunkGroups)) { + const { + compilation: { entrypoints } + } = context; + let chunkGroups = Object.values(namedChunkGroups); + if (entrypoints) { + chunkGroups = chunkGroups.filter( + group => + !Object.prototype.hasOwnProperty.call(entrypoints, group.name) + ); + } + return printer.print(context.type, chunkGroups, { + ...context, + chunkGroupKind: "Chunk Group" + }); + } + }, + "compilation.assetsByChunkName": () => "", + + "compilation.filteredModules": ( + filteredModules, + { compilation: { modules } } + ) => + filteredModules > 0 + ? `${moreCount(modules, filteredModules)} ${plural( + filteredModules, + "module", + "modules" + )}` + : undefined, + "compilation.filteredAssets": ( + filteredAssets, + { compilation: { assets } } + ) => + filteredAssets > 0 + ? `${moreCount(assets, filteredAssets)} ${plural( + filteredAssets, + "asset", + "assets" + )}` + : undefined, + "compilation.logging": (logging, context, printer) => + Array.isArray(logging) + ? undefined + : printer.print( + context.type, + Object.entries(logging).map(([name, value]) => ({ ...value, name })), + context + ), + "compilation.warningsInChildren!": (_, { yellow, compilation }) => { + if ( + !compilation.children && + /** @type {number} */ (compilation.warningsCount) > 0 && + compilation.warnings + ) { + const childWarnings = + /** @type {number} */ (compilation.warningsCount) - + compilation.warnings.length; + if (childWarnings > 0) { + return yellow( + `${childWarnings} ${plural( + childWarnings, + "WARNING", + "WARNINGS" + )} in child compilations${ + compilation.children + ? "" + : " (Use 'stats.children: true' resp. '--stats-children' for more details)" + }` + ); + } + } + }, + "compilation.errorsInChildren!": (_, { red, compilation }) => { + if ( + !compilation.children && + /** @type {number} */ (compilation.errorsCount) > 0 && + compilation.errors + ) { + const childErrors = + /** @type {number} */ (compilation.errorsCount) - + compilation.errors.length; + if (childErrors > 0) { + return red( + `${childErrors} ${plural( + childErrors, + "ERROR", + "ERRORS" + )} in child compilations${ + compilation.children + ? "" + : " (Use 'stats.children: true' resp. '--stats-children' for more details)" + }` + ); + } + } + } +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const ASSET_SIMPLE_PRINTERS = { + "asset.type": type => type, + "asset.name": (name, { formatFilename, asset: { isOverSizeLimit } }) => + formatFilename(name, isOverSizeLimit), + "asset.size": (size, { asset: { isOverSizeLimit }, yellow, formatSize }) => + isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size), + "asset.emitted": (emitted, { green, formatFlag }) => + emitted ? green(formatFlag("emitted")) : undefined, + "asset.comparedForEmit": (comparedForEmit, { yellow, formatFlag }) => + comparedForEmit ? yellow(formatFlag("compared for emit")) : undefined, + "asset.cached": (cached, { green, formatFlag }) => + cached ? green(formatFlag("cached")) : undefined, + "asset.isOverSizeLimit": (isOverSizeLimit, { yellow, formatFlag }) => + isOverSizeLimit ? yellow(formatFlag("big")) : undefined, + + "asset.info.immutable": (immutable, { green, formatFlag }) => + immutable ? green(formatFlag("immutable")) : undefined, + "asset.info.javascriptModule": (javascriptModule, { formatFlag }) => + javascriptModule ? formatFlag("javascript module") : undefined, + "asset.info.sourceFilename": (sourceFilename, { formatFlag }) => + sourceFilename + ? formatFlag( + sourceFilename === true + ? "from source file" + : `from: ${sourceFilename}` + ) + : undefined, + "asset.info.development": (development, { green, formatFlag }) => + development ? green(formatFlag("dev")) : undefined, + "asset.info.hotModuleReplacement": ( + hotModuleReplacement, + { green, formatFlag } + ) => (hotModuleReplacement ? green(formatFlag("hmr")) : undefined), + "asset.separator!": () => "\n", + "asset.filteredRelated": (filteredRelated, { asset: { related } }) => + filteredRelated > 0 + ? `${moreCount(related, filteredRelated)} related ${plural( + filteredRelated, + "asset", + "assets" + )}` + : undefined, + "asset.filteredChildren": (filteredChildren, { asset: { children } }) => + filteredChildren > 0 + ? `${moreCount(children, filteredChildren)} ${plural( + filteredChildren, + "asset", + "assets" + )}` + : undefined, + + assetChunk: (id, { formatChunkId }) => formatChunkId(id), + + assetChunkName: name => name, + assetChunkIdHint: name => name +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_SIMPLE_PRINTERS = { + "module.type": type => (type !== "module" ? type : undefined), + "module.id": (id, { formatModuleId }) => + isValidId(id) ? formatModuleId(id) : undefined, + "module.name": (name, { bold }) => { + const [prefix, resource] = getModuleName(name); + return `${prefix || ""}${bold(resource || "")}`; + }, + "module.identifier": identifier => undefined, + "module.layer": (layer, { formatLayer }) => + layer ? formatLayer(layer) : undefined, + "module.sizes": printSizes, + "module.chunks[]": (id, { formatChunkId }) => formatChunkId(id), + "module.depth": (depth, { formatFlag }) => + depth !== null ? formatFlag(`depth ${depth}`) : undefined, + "module.cacheable": (cacheable, { formatFlag, red }) => + cacheable === false ? red(formatFlag("not cacheable")) : undefined, + "module.orphan": (orphan, { formatFlag, yellow }) => + orphan ? yellow(formatFlag("orphan")) : undefined, + "module.runtime": (runtime, { formatFlag, yellow }) => + runtime ? yellow(formatFlag("runtime")) : undefined, + "module.optional": (optional, { formatFlag, yellow }) => + optional ? yellow(formatFlag("optional")) : undefined, + "module.dependent": (dependent, { formatFlag, cyan }) => + dependent ? cyan(formatFlag("dependent")) : undefined, + "module.built": (built, { formatFlag, yellow }) => + built ? yellow(formatFlag("built")) : undefined, + "module.codeGenerated": (codeGenerated, { formatFlag, yellow }) => + codeGenerated ? yellow(formatFlag("code generated")) : undefined, + "module.buildTimeExecuted": (buildTimeExecuted, { formatFlag, green }) => + buildTimeExecuted ? green(formatFlag("build time executed")) : undefined, + "module.cached": (cached, { formatFlag, green }) => + cached ? green(formatFlag("cached")) : undefined, + "module.assets": (assets, { formatFlag, magenta }) => + assets && assets.length + ? magenta( + formatFlag( + `${assets.length} ${plural(assets.length, "asset", "assets")}` + ) + ) + : undefined, + "module.warnings": (warnings, { formatFlag, yellow }) => + warnings === true + ? yellow(formatFlag("warnings")) + : warnings + ? yellow( + formatFlag(`${warnings} ${plural(warnings, "warning", "warnings")}`) + ) + : undefined, + "module.errors": (errors, { formatFlag, red }) => + errors === true + ? red(formatFlag("errors")) + : errors + ? red(formatFlag(`${errors} ${plural(errors, "error", "errors")}`)) + : undefined, + "module.providedExports": (providedExports, { formatFlag, cyan }) => { + if (Array.isArray(providedExports)) { + if (providedExports.length === 0) return cyan(formatFlag("no exports")); + return cyan(formatFlag(`exports: ${providedExports.join(", ")}`)); + } + }, + "module.usedExports": (usedExports, { formatFlag, cyan, module }) => { + if (usedExports !== true) { + if (usedExports === null) return cyan(formatFlag("used exports unknown")); + if (usedExports === false) return cyan(formatFlag("module unused")); + if (Array.isArray(usedExports)) { + if (usedExports.length === 0) + return cyan(formatFlag("no exports used")); + const providedExportsCount = Array.isArray(module.providedExports) + ? module.providedExports.length + : null; + if ( + providedExportsCount !== null && + providedExportsCount === usedExports.length + ) { + return cyan(formatFlag("all exports used")); + } + + return cyan( + formatFlag(`only some exports used: ${usedExports.join(", ")}`) + ); + } + } + }, + "module.optimizationBailout[]": (optimizationBailout, { yellow }) => + yellow(optimizationBailout), + "module.issuerPath": (issuerPath, { module }) => + module.profile ? undefined : "", + "module.profile": profile => undefined, + "module.filteredModules": (filteredModules, { module: { modules } }) => + filteredModules > 0 + ? `${moreCount(modules, filteredModules)} nested ${plural( + filteredModules, + "module", + "modules" + )}` + : undefined, + "module.filteredReasons": (filteredReasons, { module: { reasons } }) => + filteredReasons > 0 + ? `${moreCount(reasons, filteredReasons)} ${plural( + filteredReasons, + "reason", + "reasons" + )}` + : undefined, + "module.filteredChildren": (filteredChildren, { module: { children } }) => + filteredChildren > 0 + ? `${moreCount(children, filteredChildren)} ${plural( + filteredChildren, + "module", + "modules" + )}` + : undefined, + "module.separator!": () => "\n" +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_ISSUER_PRINTERS = { + "moduleIssuer.id": (id, { formatModuleId }) => formatModuleId(id), + "moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value) +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_REASON_PRINTERS = { + "moduleReason.type": type => type, + "moduleReason.userRequest": (userRequest, { cyan }) => + cyan(getResourceName(userRequest)), + "moduleReason.moduleId": (moduleId, { formatModuleId }) => + isValidId(moduleId) ? formatModuleId(moduleId) : undefined, + "moduleReason.module": (module, { magenta }) => magenta(module), + "moduleReason.loc": loc => loc, + "moduleReason.explanation": (explanation, { cyan }) => cyan(explanation), + "moduleReason.active": (active, { formatFlag }) => + active ? undefined : formatFlag("inactive"), + "moduleReason.resolvedModule": (module, { magenta }) => magenta(module), + "moduleReason.filteredChildren": ( + filteredChildren, + { moduleReason: { children } } + ) => + filteredChildren > 0 + ? `${moreCount(children, filteredChildren)} ${plural( + filteredChildren, + "reason", + "reasons" + )}` + : undefined +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_PROFILE_PRINTERS = { + "module.profile.total": (value, { formatTime }) => formatTime(value), + "module.profile.resolving": (value, { formatTime }) => + `resolving: ${formatTime(value)}`, + "module.profile.restoring": (value, { formatTime }) => + `restoring: ${formatTime(value)}`, + "module.profile.integration": (value, { formatTime }) => + `integration: ${formatTime(value)}`, + "module.profile.building": (value, { formatTime }) => + `building: ${formatTime(value)}`, + "module.profile.storing": (value, { formatTime }) => + `storing: ${formatTime(value)}`, + "module.profile.additionalResolving": (value, { formatTime }) => + value ? `additional resolving: ${formatTime(value)}` : undefined, + "module.profile.additionalIntegration": (value, { formatTime }) => + value ? `additional integration: ${formatTime(value)}` : undefined +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const CHUNK_GROUP_PRINTERS = { + "chunkGroup.kind!": (_, { chunkGroupKind }) => chunkGroupKind, + "chunkGroup.separator!": () => "\n", + "chunkGroup.name": (name, { bold }) => bold(name), + "chunkGroup.isOverSizeLimit": (isOverSizeLimit, { formatFlag, yellow }) => + isOverSizeLimit ? yellow(formatFlag("big")) : undefined, + "chunkGroup.assetsSize": (size, { formatSize }) => + size ? formatSize(size) : undefined, + "chunkGroup.auxiliaryAssetsSize": (size, { formatSize }) => + size ? `(${formatSize(size)})` : undefined, + "chunkGroup.filteredAssets": (n, { chunkGroup: { assets } }) => + n > 0 + ? `${moreCount(assets, n)} ${plural(n, "asset", "assets")}` + : undefined, + "chunkGroup.filteredAuxiliaryAssets": ( + n, + { chunkGroup: { auxiliaryAssets } } + ) => + n > 0 + ? `${moreCount(auxiliaryAssets, n)} auxiliary ${plural( + n, + "asset", + "assets" + )}` + : undefined, + "chunkGroup.is!": () => "=", + "chunkGroupAsset.name": (asset, { green }) => green(asset), + "chunkGroupAsset.size": (size, { formatSize, chunkGroup }) => + chunkGroup.assets && + (chunkGroup.assets.length > 1 || + (chunkGroup.auxiliaryAssets && chunkGroup.auxiliaryAssets.length > 0) + ? formatSize(size) + : undefined), + "chunkGroup.children": (children, context, printer) => + Array.isArray(children) + ? undefined + : printer.print( + context.type, + Object.keys(children).map(key => ({ + type: key, + children: children[key] + })), + context + ), + "chunkGroupChildGroup.type": type => `${type}:`, + "chunkGroupChild.assets[]": (file, { formatFilename }) => + formatFilename(file), + "chunkGroupChild.chunks[]": (id, { formatChunkId }) => formatChunkId(id), + "chunkGroupChild.name": name => (name ? `(name: ${name})` : undefined) +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const CHUNK_PRINTERS = { + "chunk.id": (id, { formatChunkId }) => formatChunkId(id), + "chunk.files[]": (file, { formatFilename }) => formatFilename(file), + "chunk.names[]": name => name, + "chunk.idHints[]": name => name, + "chunk.runtime[]": name => name, + "chunk.sizes": (sizes, context) => printSizes(sizes, context), + "chunk.parents[]": (parents, context) => + context.formatChunkId(parents, "parent"), + "chunk.siblings[]": (siblings, context) => + context.formatChunkId(siblings, "sibling"), + "chunk.children[]": (children, context) => + context.formatChunkId(children, "child"), + "chunk.childrenByOrder": (childrenByOrder, context, printer) => + Array.isArray(childrenByOrder) + ? undefined + : printer.print( + context.type, + Object.keys(childrenByOrder).map(key => ({ + type: key, + children: childrenByOrder[key] + })), + context + ), + "chunk.childrenByOrder[].type": type => `${type}:`, + "chunk.childrenByOrder[].children[]": (id, { formatChunkId }) => + isValidId(id) ? formatChunkId(id) : undefined, + "chunk.entry": (entry, { formatFlag, yellow }) => + entry ? yellow(formatFlag("entry")) : undefined, + "chunk.initial": (initial, { formatFlag, yellow }) => + initial ? yellow(formatFlag("initial")) : undefined, + "chunk.rendered": (rendered, { formatFlag, green }) => + rendered ? green(formatFlag("rendered")) : undefined, + "chunk.recorded": (recorded, { formatFlag, green }) => + recorded ? green(formatFlag("recorded")) : undefined, + "chunk.reason": (reason, { yellow }) => (reason ? yellow(reason) : undefined), + "chunk.filteredModules": (filteredModules, { chunk: { modules } }) => + filteredModules > 0 + ? `${moreCount(modules, filteredModules)} chunk ${plural( + filteredModules, + "module", + "modules" + )}` + : undefined, + "chunk.separator!": () => "\n", + + "chunkOrigin.request": request => request, + "chunkOrigin.moduleId": (moduleId, { formatModuleId }) => + isValidId(moduleId) ? formatModuleId(moduleId) : undefined, + "chunkOrigin.moduleName": (moduleName, { bold }) => bold(moduleName), + "chunkOrigin.loc": loc => loc +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const ERROR_PRINTERS = { + "error.compilerPath": (compilerPath, { bold }) => + compilerPath ? bold(`(${compilerPath})`) : undefined, + "error.chunkId": (chunkId, { formatChunkId }) => + isValidId(chunkId) ? formatChunkId(chunkId) : undefined, + "error.chunkEntry": (chunkEntry, { formatFlag }) => + chunkEntry ? formatFlag("entry") : undefined, + "error.chunkInitial": (chunkInitial, { formatFlag }) => + chunkInitial ? formatFlag("initial") : undefined, + "error.file": (file, { bold }) => bold(file), + "error.moduleName": (moduleName, { bold }) => + moduleName.includes("!") + ? `${bold(moduleName.replace(/^(\s|\S)*!/, ""))} (${moduleName})` + : `${bold(moduleName)}`, + "error.loc": (loc, { green }) => green(loc), + "error.message": (message, { bold, formatError }) => + message.includes("\u001B[") ? message : bold(formatError(message)), + "error.details": (details, { formatError }) => formatError(details), + "error.filteredDetails": filteredDetails => + filteredDetails ? `+ ${filteredDetails} hidden lines` : undefined, + "error.stack": stack => stack, + "error.moduleTrace": moduleTrace => undefined, + "error.separator!": () => "\n" +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const LOG_ENTRY_PRINTERS = { + "loggingEntry(error).loggingEntry.message": (message, { red }) => + mapLines(message, x => ` ${red(x)}`), + "loggingEntry(warn).loggingEntry.message": (message, { yellow }) => + mapLines(message, x => ` ${yellow(x)}`), + "loggingEntry(info).loggingEntry.message": (message, { green }) => + mapLines(message, x => ` ${green(x)}`), + "loggingEntry(log).loggingEntry.message": (message, { bold }) => + mapLines(message, x => ` ${bold(x)}`), + "loggingEntry(debug).loggingEntry.message": message => + mapLines(message, x => ` ${x}`), + "loggingEntry(trace).loggingEntry.message": message => + mapLines(message, x => ` ${x}`), + "loggingEntry(status).loggingEntry.message": (message, { magenta }) => + mapLines(message, x => ` ${magenta(x)}`), + "loggingEntry(profile).loggingEntry.message": (message, { magenta }) => + mapLines(message, x => `

${magenta(x)}`), + "loggingEntry(profileEnd).loggingEntry.message": (message, { magenta }) => + mapLines(message, x => `

${magenta(x)}`), + "loggingEntry(time).loggingEntry.message": (message, { magenta }) => + mapLines(message, x => ` ${magenta(x)}`), + "loggingEntry(group).loggingEntry.message": (message, { cyan }) => + mapLines(message, x => `<-> ${cyan(x)}`), + "loggingEntry(groupCollapsed).loggingEntry.message": (message, { cyan }) => + mapLines(message, x => `<+> ${cyan(x)}`), + "loggingEntry(clear).loggingEntry": () => " -------", + "loggingEntry(groupCollapsed).loggingEntry.children": () => "", + "loggingEntry.trace[]": trace => + trace ? mapLines(trace, x => `| ${x}`) : undefined, + + loggingGroup: loggingGroup => + loggingGroup.entries.length === 0 ? "" : undefined, + "loggingGroup.debug": (flag, { red }) => (flag ? red("DEBUG") : undefined), + "loggingGroup.name": (name, { bold }) => bold(`LOG from ${name}`), + "loggingGroup.separator!": () => "\n", + "loggingGroup.filteredEntries": filteredEntries => + filteredEntries > 0 ? `+ ${filteredEntries} hidden lines` : undefined +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_TRACE_ITEM_PRINTERS = { + "moduleTraceItem.originName": originName => originName +}; + +/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ +const MODULE_TRACE_DEPENDENCY_PRINTERS = { + "moduleTraceDependency.loc": loc => loc +}; + +/** @type {Record} */ +const ITEM_NAMES = { + "compilation.assets[]": "asset", + "compilation.modules[]": "module", + "compilation.chunks[]": "chunk", + "compilation.entrypoints[]": "chunkGroup", + "compilation.namedChunkGroups[]": "chunkGroup", + "compilation.errors[]": "error", + "compilation.warnings[]": "error", + "compilation.logging[]": "loggingGroup", + "compilation.children[]": "compilation", + "asset.related[]": "asset", + "asset.children[]": "asset", + "asset.chunks[]": "assetChunk", + "asset.auxiliaryChunks[]": "assetChunk", + "asset.chunkNames[]": "assetChunkName", + "asset.chunkIdHints[]": "assetChunkIdHint", + "asset.auxiliaryChunkNames[]": "assetChunkName", + "asset.auxiliaryChunkIdHints[]": "assetChunkIdHint", + "chunkGroup.assets[]": "chunkGroupAsset", + "chunkGroup.auxiliaryAssets[]": "chunkGroupAsset", + "chunkGroupChild.assets[]": "chunkGroupAsset", + "chunkGroupChild.auxiliaryAssets[]": "chunkGroupAsset", + "chunkGroup.children[]": "chunkGroupChildGroup", + "chunkGroupChildGroup.children[]": "chunkGroupChild", + "module.modules[]": "module", + "module.children[]": "module", + "module.reasons[]": "moduleReason", + "moduleReason.children[]": "moduleReason", + "module.issuerPath[]": "moduleIssuer", + "chunk.origins[]": "chunkOrigin", + "chunk.modules[]": "module", + "loggingGroup.entries[]": logEntry => + `loggingEntry(${logEntry.type}).loggingEntry`, + "loggingEntry.children[]": logEntry => + `loggingEntry(${logEntry.type}).loggingEntry`, + "error.moduleTrace[]": "moduleTraceItem", + "moduleTraceItem.dependencies[]": "moduleTraceDependency" +}; + +const ERROR_PREFERRED_ORDER = [ + "compilerPath", + "chunkId", + "chunkEntry", + "chunkInitial", + "file", + "separator!", + "moduleName", + "loc", + "separator!", + "message", + "separator!", + "details", + "separator!", + "filteredDetails", + "separator!", + "stack", + "separator!", + "missing", + "separator!", + "moduleTrace" +]; + +/** @type {Record} */ +const PREFERRED_ORDERS = { + compilation: [ + "name", + "hash", + "version", + "time", + "builtAt", + "env", + "publicPath", + "assets", + "filteredAssets", + "entrypoints", + "namedChunkGroups", + "chunks", + "modules", + "filteredModules", + "children", + "logging", + "warnings", + "warningsInChildren!", + "filteredWarningDetailsCount", + "errors", + "errorsInChildren!", + "filteredErrorDetailsCount", + "summary!", + "needAdditionalPass" + ], + asset: [ + "type", + "name", + "size", + "chunks", + "auxiliaryChunks", + "emitted", + "comparedForEmit", + "cached", + "info", + "isOverSizeLimit", + "chunkNames", + "auxiliaryChunkNames", + "chunkIdHints", + "auxiliaryChunkIdHints", + "related", + "filteredRelated", + "children", + "filteredChildren" + ], + "asset.info": [ + "immutable", + "sourceFilename", + "javascriptModule", + "development", + "hotModuleReplacement" + ], + chunkGroup: [ + "kind!", + "name", + "isOverSizeLimit", + "assetsSize", + "auxiliaryAssetsSize", + "is!", + "assets", + "filteredAssets", + "auxiliaryAssets", + "filteredAuxiliaryAssets", + "separator!", + "children" + ], + chunkGroupAsset: ["name", "size"], + chunkGroupChildGroup: ["type", "children"], + chunkGroupChild: ["assets", "chunks", "name"], + module: [ + "type", + "name", + "identifier", + "id", + "layer", + "sizes", + "chunks", + "depth", + "cacheable", + "orphan", + "runtime", + "optional", + "dependent", + "built", + "codeGenerated", + "cached", + "assets", + "failed", + "warnings", + "errors", + "children", + "filteredChildren", + "providedExports", + "usedExports", + "optimizationBailout", + "reasons", + "filteredReasons", + "issuerPath", + "profile", + "modules", + "filteredModules" + ], + moduleReason: [ + "active", + "type", + "userRequest", + "moduleId", + "module", + "resolvedModule", + "loc", + "explanation", + "children", + "filteredChildren" + ], + "module.profile": [ + "total", + "separator!", + "resolving", + "restoring", + "integration", + "building", + "storing", + "additionalResolving", + "additionalIntegration" + ], + chunk: [ + "id", + "runtime", + "files", + "names", + "idHints", + "sizes", + "parents", + "siblings", + "children", + "childrenByOrder", + "entry", + "initial", + "rendered", + "recorded", + "reason", + "separator!", + "origins", + "separator!", + "modules", + "separator!", + "filteredModules" + ], + chunkOrigin: ["request", "moduleId", "moduleName", "loc"], + error: ERROR_PREFERRED_ORDER, + warning: ERROR_PREFERRED_ORDER, + "chunk.childrenByOrder[]": ["type", "children"], + loggingGroup: [ + "debug", + "name", + "separator!", + "entries", + "separator!", + "filteredEntries" + ], + loggingEntry: ["message", "trace", "children"] +}; + +/** @typedef {(items: string[]) => string | undefined} SimpleItemsJoiner */ + +/** @type {SimpleItemsJoiner} */ +const itemsJoinOneLine = items => items.filter(Boolean).join(" "); +/** @type {SimpleItemsJoiner} */ +const itemsJoinOneLineBrackets = items => + items.length > 0 ? `(${items.filter(Boolean).join(" ")})` : undefined; +/** @type {SimpleItemsJoiner} */ +const itemsJoinMoreSpacing = items => items.filter(Boolean).join("\n\n"); +/** @type {SimpleItemsJoiner} */ +const itemsJoinComma = items => items.filter(Boolean).join(", "); +/** @type {SimpleItemsJoiner} */ +const itemsJoinCommaBrackets = items => + items.length > 0 ? `(${items.filter(Boolean).join(", ")})` : undefined; +/** @type {function(string): SimpleItemsJoiner} */ +const itemsJoinCommaBracketsWithName = name => items => + items.length > 0 + ? `(${name}: ${items.filter(Boolean).join(", ")})` + : undefined; + +/** @type {Record} */ +const SIMPLE_ITEMS_JOINER = { + "chunk.parents": itemsJoinOneLine, + "chunk.siblings": itemsJoinOneLine, + "chunk.children": itemsJoinOneLine, + "chunk.names": itemsJoinCommaBrackets, + "chunk.idHints": itemsJoinCommaBracketsWithName("id hint"), + "chunk.runtime": itemsJoinCommaBracketsWithName("runtime"), + "chunk.files": itemsJoinComma, + "chunk.childrenByOrder": itemsJoinOneLine, + "chunk.childrenByOrder[].children": itemsJoinOneLine, + "chunkGroup.assets": itemsJoinOneLine, + "chunkGroup.auxiliaryAssets": itemsJoinOneLineBrackets, + "chunkGroupChildGroup.children": itemsJoinComma, + "chunkGroupChild.assets": itemsJoinOneLine, + "chunkGroupChild.auxiliaryAssets": itemsJoinOneLineBrackets, + "asset.chunks": itemsJoinComma, + "asset.auxiliaryChunks": itemsJoinCommaBrackets, + "asset.chunkNames": itemsJoinCommaBracketsWithName("name"), + "asset.auxiliaryChunkNames": itemsJoinCommaBracketsWithName("auxiliary name"), + "asset.chunkIdHints": itemsJoinCommaBracketsWithName("id hint"), + "asset.auxiliaryChunkIdHints": + itemsJoinCommaBracketsWithName("auxiliary id hint"), + "module.chunks": itemsJoinOneLine, + "module.issuerPath": items => + items + .filter(Boolean) + .map(item => `${item} ->`) + .join(" "), + "compilation.errors": itemsJoinMoreSpacing, + "compilation.warnings": itemsJoinMoreSpacing, + "compilation.logging": itemsJoinMoreSpacing, + "compilation.children": items => + indent(/** @type {string} */ (itemsJoinMoreSpacing(items)), " "), + "moduleTraceItem.dependencies": itemsJoinOneLine, + "loggingEntry.children": items => + indent(items.filter(Boolean).join("\n"), " ", false) +}; + +/** + * @param {Item[]} items items + * @returns {string} result + */ +const joinOneLine = items => + items + .map(item => item.content) + .filter(Boolean) + .join(" "); + +/** + * @param {Item[]} items items + * @returns {string} result + */ +const joinInBrackets = items => { + const res = []; + let mode = 0; + for (const item of items) { + if (item.element === "separator!") { + switch (mode) { + case 0: + case 1: + mode += 2; + break; + case 4: + res.push(")"); + mode = 3; + break; + } + } + if (!item.content) continue; + switch (mode) { + case 0: + mode = 1; + break; + case 1: + res.push(" "); + break; + case 2: + res.push("("); + mode = 4; + break; + case 3: + res.push(" ("); + mode = 4; + break; + case 4: + res.push(", "); + break; + } + res.push(item.content); + } + if (mode === 4) res.push(")"); + return res.join(""); +}; + +/** + * @param {string} str a string + * @param {string} prefix prefix + * @param {boolean=} noPrefixInFirstLine need prefix in the first line? + * @returns {string} result + */ +const indent = (str, prefix, noPrefixInFirstLine) => { + const rem = str.replace(/\n([^\n])/g, `\n${prefix}$1`); + if (noPrefixInFirstLine) return rem; + const ind = str[0] === "\n" ? "" : prefix; + return ind + rem; +}; + +/** + * @param {(false | Item)[]} items items + * @param {string} indenter indenter + * @returns {string} result + */ +const joinExplicitNewLine = (items, indenter) => { + let firstInLine = true; + let first = true; + return items + .map(item => { + if (!item || !item.content) return; + let content = indent(item.content, first ? "" : indenter, !firstInLine); + if (firstInLine) { + content = content.replace(/^\n+/, ""); + } + if (!content) return; + first = false; + const noJoiner = firstInLine || content.startsWith("\n"); + firstInLine = content.endsWith("\n"); + return noJoiner ? content : ` ${content}`; + }) + .filter(Boolean) + .join("") + .trim(); +}; + +/** + * @param {boolean} error is an error + * @returns {SimpleElementJoiner} joiner + */ +const joinError = + error => + /** + * @param {Item[]} items items + * @param {Required} ctx context + * @returns {string} result + */ + (items, { red, yellow }) => + `${error ? red("ERROR") : yellow("WARNING")} in ${joinExplicitNewLine( + items, + "" + )}`; + +/** @typedef {{ element: string, content: string }} Item */ +/** @typedef {(items: Item[], context: Required) => string} SimpleElementJoiner */ + +/** @type {Record} */ +const SIMPLE_ELEMENT_JOINERS = { + compilation: items => { + const result = []; + let lastNeedMore = false; + for (const item of items) { + if (!item.content) continue; + const needMoreSpace = + item.element === "warnings" || + item.element === "filteredWarningDetailsCount" || + item.element === "errors" || + item.element === "filteredErrorDetailsCount" || + item.element === "logging"; + if (result.length !== 0) { + result.push(needMoreSpace || lastNeedMore ? "\n\n" : "\n"); + } + result.push(item.content); + lastNeedMore = needMoreSpace; + } + if (lastNeedMore) result.push("\n"); + return result.join(""); + }, + asset: items => + joinExplicitNewLine( + items.map(item => { + if ( + (item.element === "related" || item.element === "children") && + item.content + ) { + return { + ...item, + content: `\n${item.content}\n` + }; + } + return item; + }), + " " + ), + "asset.info": joinOneLine, + module: (items, { module }) => { + let hasName = false; + return joinExplicitNewLine( + items.map(item => { + switch (item.element) { + case "id": + if (module.id === module.name) { + if (hasName) return false; + if (item.content) hasName = true; + } + break; + case "name": + if (hasName) return false; + if (item.content) hasName = true; + break; + case "providedExports": + case "usedExports": + case "optimizationBailout": + case "reasons": + case "issuerPath": + case "profile": + case "children": + case "modules": + if (item.content) { + return { + ...item, + content: `\n${item.content}\n` + }; + } + break; + } + return item; + }), + " " + ); + }, + chunk: items => { + let hasEntry = false; + return `chunk ${joinExplicitNewLine( + items.filter(item => { + switch (item.element) { + case "entry": + if (item.content) hasEntry = true; + break; + case "initial": + if (hasEntry) return false; + break; + } + return true; + }), + " " + )}`; + }, + "chunk.childrenByOrder[]": items => `(${joinOneLine(items)})`, + chunkGroup: items => joinExplicitNewLine(items, " "), + chunkGroupAsset: joinOneLine, + chunkGroupChildGroup: joinOneLine, + chunkGroupChild: joinOneLine, + // moduleReason: (items, { moduleReason }) => { + // let hasName = false; + // return joinOneLine( + // items.filter(item => { + // switch (item.element) { + // case "moduleId": + // if (moduleReason.moduleId === moduleReason.module && item.content) + // hasName = true; + // break; + // case "module": + // if (hasName) return false; + // break; + // case "resolvedModule": + // return ( + // moduleReason.module !== moduleReason.resolvedModule && + // item.content + // ); + // } + // return true; + // }) + // ); + // }, + moduleReason: (items, { moduleReason }) => { + let hasName = false; + return joinExplicitNewLine( + items.map(item => { + switch (item.element) { + case "moduleId": + if (moduleReason.moduleId === moduleReason.module && item.content) + hasName = true; + break; + case "module": + if (hasName) return false; + break; + case "resolvedModule": + if (moduleReason.module === moduleReason.resolvedModule) + return false; + break; + case "children": + if (item.content) { + return { + ...item, + content: `\n${item.content}\n` + }; + } + break; + } + return item; + }), + " " + ); + }, + "module.profile": joinInBrackets, + moduleIssuer: joinOneLine, + chunkOrigin: items => `> ${joinOneLine(items)}`, + "errors[].error": joinError(true), + "warnings[].error": joinError(false), + loggingGroup: items => joinExplicitNewLine(items, "").trimEnd(), + moduleTraceItem: items => ` @ ${joinOneLine(items)}`, + moduleTraceDependency: joinOneLine +}; + +/** @typedef {"bold" | "yellow" | "red" | "green" | "cyan" | "magenta"} ColorNames */ + +/** @type {Record} */ +const AVAILABLE_COLORS = { + 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" +}; + +/** @type {Record & StatsPrinterContext, ...any): string>} */ +const AVAILABLE_FORMATS = { + formatChunkId: (id, { yellow }, direction) => { + switch (direction) { + case "parent": + return `<{${yellow(id)}}>`; + case "sibling": + return `={${yellow(id)}}=`; + case "child": + return `>{${yellow(id)}}<`; + default: + return `{${yellow(id)}}`; + } + }, + formatModuleId: id => `[${id}]`, + formatFilename: (filename, { green, yellow }, oversize) => + (oversize ? yellow : green)(filename), + formatFlag: flag => `[${flag}]`, + formatLayer: layer => `(in ${layer})`, + formatSize: require("../SizeFormatHelpers").formatSize, + formatDateTime: (dateTime, { bold }) => { + const d = new Date(dateTime); + const x = twoDigit; + const date = `${d.getFullYear()}-${x(d.getMonth() + 1)}-${x(d.getDate())}`; + const time = `${x(d.getHours())}:${x(d.getMinutes())}:${x(d.getSeconds())}`; + return `${date} ${bold(time)}`; + }, + formatTime: ( + time, + { timeReference, bold, green, yellow, red }, + boldQuantity + ) => { + const unit = " ms"; + if (timeReference && time !== timeReference) { + const times = [ + timeReference / 2, + timeReference / 4, + timeReference / 8, + timeReference / 16 + ]; + if (time < times[3]) return `${time}${unit}`; + else if (time < times[2]) return bold(`${time}${unit}`); + else if (time < times[1]) return green(`${time}${unit}`); + else if (time < times[0]) return yellow(`${time}${unit}`); + return red(`${time}${unit}`); + } + return `${boldQuantity ? bold(time) : time}${unit}`; + }, + formatError: (message, { green, yellow, red }) => { + if (message.includes("\u001B[")) return message; + const highlights = [ + { regExp: /(Did you mean .+)/g, format: green }, + { + regExp: /(Set 'mode' option to 'development' or 'production')/g, + format: green + }, + { regExp: /(\(module has no exports\))/g, format: red }, + { regExp: /\(possible exports: (.+)\)/g, format: green }, + { regExp: /(?:^|\n)(.* doesn't exist)/g, format: red }, + { regExp: /('\w+' option has not been set)/g, format: red }, + { + regExp: /(Emitted value instead of an instance of Error)/g, + format: yellow + }, + { regExp: /(Used? .+ instead)/gi, format: yellow }, + { regExp: /\b(deprecated|must|required)\b/g, format: yellow }, + { + regExp: /\b(BREAKING CHANGE)\b/gi, + format: red + }, + { + regExp: + /\b(error|failed|unexpected|invalid|not found|not supported|not available|not possible|not implemented|doesn't support|conflict|conflicting|not existing|duplicate)\b/gi, + format: red + } + ]; + for (const { regExp, format } of highlights) { + message = message.replace( + regExp, + /** + * @param {string} match match + * @param {string} content content + * @returns {string} result + */ + (match, content) => match.replace(content, format(content)) + ); + } + return message; + } +}; + +/** @typedef {function(string): string} ResultModifierFn */ +/** @type {Record} */ +const RESULT_MODIFIER = { + "module.modules": result => indent(result, "| ") +}; + +/** + * @param {string[]} array array + * @param {string[]} preferredOrder preferred order + * @returns {string[]} result + */ +const createOrder = (array, preferredOrder) => { + const originalArray = array.slice(); + /** @type {Set} */ + const set = new Set(array); + /** @type {Set} */ + const usedSet = new Set(); + array.length = 0; + for (const element of preferredOrder) { + if (element.endsWith("!") || set.has(element)) { + array.push(element); + usedSet.add(element); + } + } + for (const element of originalArray) { + if (!usedSet.has(element)) { + array.push(element); + } + } + return array; +}; + +class DefaultStatsPrinterPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("DefaultStatsPrinterPlugin", compilation => { + compilation.hooks.statsPrinter.tap( + "DefaultStatsPrinterPlugin", + (stats, options) => { + // Put colors into context + stats.hooks.print + .for("compilation") + .tap("DefaultStatsPrinterPlugin", (compilation, context) => { + for (const color of Object.keys(AVAILABLE_COLORS)) { + const name = /** @type {ColorNames} */ (color); + /** @type {string | undefined} */ + let start; + if (options.colors) { + if ( + typeof options.colors === "object" && + typeof options.colors[name] === "string" + ) { + start = options.colors[name]; + } else { + start = AVAILABLE_COLORS[name]; + } + } + if (start) { + /** + * @param {string} str string + * @returns {string} string with color + */ + context[color] = str => + `${start}${ + typeof str === "string" + ? str.replace( + /((\u001B\[39m|\u001B\[22m|\u001B\[0m)+)/g, + `$1${start}` + ) + : str + }\u001B[39m\u001B[22m`; + } else { + /** + * @param {string} str string + * @returns {string} str string + */ + context[color] = str => str; + } + } + for (const format of Object.keys(AVAILABLE_FORMATS)) { + context[format] = + /** + * @param {string | number} content content + * @param {...TODO} args args + * @returns {string} result + */ + (content, ...args) => + AVAILABLE_FORMATS[format]( + content, + /** @type {Required & StatsPrinterContext} */ + (context), + ...args + ); + } + context.timeReference = compilation.time; + }); + + for (const key of Object.keys(COMPILATION_SIMPLE_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + COMPILATION_SIMPLE_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(ASSET_SIMPLE_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + ASSET_SIMPLE_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_SIMPLE_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_SIMPLE_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_ISSUER_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_ISSUER_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_REASON_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_REASON_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_PROFILE_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_PROFILE_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(CHUNK_GROUP_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + CHUNK_GROUP_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(CHUNK_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + CHUNK_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(ERROR_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + ERROR_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(LOG_ENTRY_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + LOG_ENTRY_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_TRACE_DEPENDENCY_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_TRACE_DEPENDENCY_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(MODULE_TRACE_ITEM_PRINTERS)) { + stats.hooks.print + .for(key) + .tap("DefaultStatsPrinterPlugin", (obj, ctx) => + MODULE_TRACE_ITEM_PRINTERS[key]( + obj, + /** @type {Required & Required & WithRequired} */ + (ctx), + stats + ) + ); + } + + for (const key of Object.keys(PREFERRED_ORDERS)) { + const preferredOrder = PREFERRED_ORDERS[key]; + stats.hooks.sortElements + .for(key) + .tap("DefaultStatsPrinterPlugin", (elements, context) => { + createOrder(elements, preferredOrder); + }); + } + + for (const key of Object.keys(ITEM_NAMES)) { + const itemName = ITEM_NAMES[key]; + stats.hooks.getItemName + .for(key) + .tap( + "DefaultStatsPrinterPlugin", + typeof itemName === "string" ? () => itemName : itemName + ); + } + + for (const key of Object.keys(SIMPLE_ITEMS_JOINER)) { + const joiner = SIMPLE_ITEMS_JOINER[key]; + stats.hooks.printItems + .for(key) + .tap("DefaultStatsPrinterPlugin", joiner); + } + + for (const key of Object.keys(SIMPLE_ELEMENT_JOINERS)) { + const joiner = SIMPLE_ELEMENT_JOINERS[key]; + stats.hooks.printElements + .for(key) + .tap("DefaultStatsPrinterPlugin", /** @type {TODO} */ (joiner)); + } + + for (const key of Object.keys(RESULT_MODIFIER)) { + const modifier = RESULT_MODIFIER[key]; + stats.hooks.result + .for(key) + .tap("DefaultStatsPrinterPlugin", modifier); + } + } + ); + }); + } +} +module.exports = DefaultStatsPrinterPlugin; diff --git a/webpack-lib/lib/stats/StatsFactory.js b/webpack-lib/lib/stats/StatsFactory.js new file mode 100644 index 00000000000..b668369ea1d --- /dev/null +++ b/webpack-lib/lib/stats/StatsFactory.js @@ -0,0 +1,363 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable"); +const { concatComparators, keepOriginalOrder } = require("../util/comparators"); +const smartGrouping = require("../util/smartGrouping"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../WebpackError")} WebpackError */ +/** @typedef {import("../util/comparators").Comparator} Comparator */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("../util/smartGrouping").GroupConfig} GroupConfig */ + +/** + * @typedef {object} KnownStatsFactoryContext + * @property {string} type + * @property {function(string): string} makePathsRelative + * @property {Compilation} compilation + * @property {Set} rootModules + * @property {Map} compilationFileToChunks + * @property {Map} compilationAuxiliaryFileToChunks + * @property {RuntimeSpec} runtime + * @property {function(Compilation): WebpackError[]} cachedGetErrors + * @property {function(Compilation): WebpackError[]} cachedGetWarnings + */ + +/** @typedef {Record & KnownStatsFactoryContext} StatsFactoryContext */ + +/** @typedef {any} CreatedObject */ +/** @typedef {any} FactoryData */ +/** @typedef {any} FactoryDataItem */ +/** @typedef {any} Result */ +/** @typedef {Record} ObjectForExtract */ + +/** + * @typedef {object} StatsFactoryHooks + * @property {HookMap>} extract + * @property {HookMap>} filter + * @property {HookMap>} sort + * @property {HookMap>} filterSorted + * @property {HookMap>} groupResults + * @property {HookMap>} sortResults + * @property {HookMap>} filterResults + * @property {HookMap>} merge + * @property {HookMap>} result + * @property {HookMap>} getItemName + * @property {HookMap>} getItemFactory + */ + +/** + * @template T + * @typedef {Map} Caches + */ + +class StatsFactory { + constructor() { + /** @type {StatsFactoryHooks} */ + this.hooks = Object.freeze({ + extract: new HookMap( + () => new SyncBailHook(["object", "data", "context"]) + ), + filter: new HookMap( + () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) + ), + sort: new HookMap(() => new SyncBailHook(["comparators", "context"])), + filterSorted: new HookMap( + () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) + ), + groupResults: new HookMap( + () => new SyncBailHook(["groupConfigs", "context"]) + ), + sortResults: new HookMap( + () => new SyncBailHook(["comparators", "context"]) + ), + filterResults: new HookMap( + () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) + ), + merge: new HookMap(() => new SyncBailHook(["items", "context"])), + result: new HookMap(() => new SyncWaterfallHook(["result", "context"])), + getItemName: new HookMap(() => new SyncBailHook(["item", "context"])), + getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"])) + }); + const hooks = this.hooks; + this._caches = /** @type {TODO} */ ({}); + for (const key of Object.keys(hooks)) { + this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map(); + } + this._inCreate = false; + } + + /** + * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @param {HM} hookMap hook map + * @param {Caches} cache cache + * @param {string} type type + * @returns {H[]} hooks + * @private + */ + _getAllLevelHooks(hookMap, cache, type) { + const cacheEntry = cache.get(type); + if (cacheEntry !== undefined) { + return cacheEntry; + } + const hooks = /** @type {H[]} */ ([]); + const typeParts = type.split("."); + for (let i = 0; i < typeParts.length; i++) { + const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join("."))); + if (hook) { + hooks.push(hook); + } + } + cache.set(type, hooks); + return hooks; + } + + /** + * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @template {H extends import("tapable").Hook ? R : never} R + * @param {HM} hookMap hook map + * @param {Caches} cache cache + * @param {string} type type + * @param {function(H): R | void} fn fn + * @returns {R | void} hook + * @private + */ + _forEachLevel(hookMap, cache, type, fn) { + for (const hook of this._getAllLevelHooks(hookMap, cache, type)) { + const result = fn(/** @type {H} */ (hook)); + if (result !== undefined) return result; + } + } + + /** + * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @param {HM} hookMap hook map + * @param {Caches} cache cache + * @param {string} type type + * @param {FactoryData} data data + * @param {function(H, FactoryData): FactoryData} fn fn + * @returns {FactoryData} data + * @private + */ + _forEachLevelWaterfall(hookMap, cache, type, data, fn) { + for (const hook of this._getAllLevelHooks(hookMap, cache, type)) { + data = fn(/** @type {H} */ (hook), data); + } + return data; + } + + /** + * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} T + * @template {T extends HookMap ? H : never} H + * @template {H extends import("tapable").Hook ? R : never} R + * @param {T} hookMap hook map + * @param {Caches} cache cache + * @param {string} type type + * @param {Array} items items + * @param {function(H, R, number, number): R | undefined} fn fn + * @param {boolean} forceClone force clone + * @returns {R[]} result for each level + * @private + */ + _forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) { + const hooks = this._getAllLevelHooks(hookMap, cache, type); + if (hooks.length === 0) return forceClone ? items.slice() : items; + let i = 0; + return items.filter((item, idx) => { + for (const hook of hooks) { + const r = fn(/** @type {H} */ (hook), item, idx, i); + if (r !== undefined) { + if (r) i++; + return r; + } + } + i++; + return true; + }); + } + + /** + * @param {string} type type + * @param {FactoryData} data factory data + * @param {Omit} baseContext context used as base + * @returns {CreatedObject} created object + */ + create(type, data, baseContext) { + if (this._inCreate) { + return this._create(type, data, baseContext); + } + try { + this._inCreate = true; + return this._create(type, data, baseContext); + } finally { + for (const key of Object.keys(this._caches)) + this._caches[/** @type {keyof StatsFactoryHooks} */ (key)].clear(); + this._inCreate = false; + } + } + + /** + * @param {string} type type + * @param {FactoryData} data factory data + * @param {Omit} baseContext context used as base + * @returns {CreatedObject} created object + * @private + */ + _create(type, data, baseContext) { + const context = /** @type {StatsFactoryContext} */ ({ + ...baseContext, + type, + [type]: data + }); + if (Array.isArray(data)) { + // run filter on unsorted items + const items = this._forEachLevelFilter( + this.hooks.filter, + this._caches.filter, + type, + data, + (h, r, idx, i) => h.call(r, context, idx, i), + true + ); + + // sort items + /** @type {Comparator[]} */ + const comparators = []; + this._forEachLevel(this.hooks.sort, this._caches.sort, type, h => + h.call(comparators, context) + ); + if (comparators.length > 0) { + items.sort( + // @ts-expect-error number of arguments is correct + concatComparators(...comparators, keepOriginalOrder(items)) + ); + } + + // run filter on sorted items + const items2 = this._forEachLevelFilter( + this.hooks.filterSorted, + this._caches.filterSorted, + type, + items, + (h, r, idx, i) => h.call(r, context, idx, i), + false + ); + + // for each item + let resultItems = items2.map((item, i) => { + /** @type {StatsFactoryContext} */ + const itemContext = { + ...context, + _index: i + }; + + // run getItemName + const itemName = this._forEachLevel( + this.hooks.getItemName, + this._caches.getItemName, + `${type}[]`, + h => h.call(item, itemContext) + ); + if (itemName) itemContext[itemName] = item; + const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`; + + // run getItemFactory + const itemFactory = + this._forEachLevel( + this.hooks.getItemFactory, + this._caches.getItemFactory, + innerType, + h => h.call(item, itemContext) + ) || this; + + // run item factory + return itemFactory.create(innerType, item, itemContext); + }); + + // sort result items + /** @type {Comparator[]} */ + const comparators2 = []; + this._forEachLevel( + this.hooks.sortResults, + this._caches.sortResults, + type, + h => h.call(comparators2, context) + ); + if (comparators2.length > 0) { + resultItems.sort( + // @ts-expect-error number of arguments is correct + concatComparators(...comparators2, keepOriginalOrder(resultItems)) + ); + } + + // group result items + /** @type {GroupConfig[]} */ + const groupConfigs = []; + this._forEachLevel( + this.hooks.groupResults, + this._caches.groupResults, + type, + h => h.call(groupConfigs, context) + ); + if (groupConfigs.length > 0) { + resultItems = smartGrouping(resultItems, groupConfigs); + } + + // run filter on sorted result items + const finalResultItems = this._forEachLevelFilter( + this.hooks.filterResults, + this._caches.filterResults, + type, + resultItems, + (h, r, idx, i) => h.call(r, context, idx, i), + false + ); + + // run merge on mapped items + let result = this._forEachLevel( + this.hooks.merge, + this._caches.merge, + type, + h => h.call(finalResultItems, context) + ); + if (result === undefined) result = finalResultItems; + + // run result on merged items + return this._forEachLevelWaterfall( + this.hooks.result, + this._caches.result, + type, + result, + (h, r) => h.call(r, context) + ); + } + /** @type {ObjectForExtract} */ + const object = {}; + + // run extract on value + this._forEachLevel(this.hooks.extract, this._caches.extract, type, h => + h.call(object, data, context) + ); + + // run result on extracted object + return this._forEachLevelWaterfall( + this.hooks.result, + this._caches.result, + type, + object, + (h, r) => h.call(r, context) + ); + } +} +module.exports = StatsFactory; diff --git a/webpack-lib/lib/stats/StatsPrinter.js b/webpack-lib/lib/stats/StatsPrinter.js new file mode 100644 index 00000000000..99270618389 --- /dev/null +++ b/webpack-lib/lib/stats/StatsPrinter.js @@ -0,0 +1,280 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable"); + +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsLogging} StatsLogging */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */ +/** @typedef {import("./DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */ + +/** + * @typedef {object} PrintedElement + * @property {string} element + * @property {string} content + */ + +/** + * @typedef {object} KnownStatsPrinterContext + * @property {string=} type + * @property {StatsCompilation=} compilation + * @property {StatsChunkGroup=} chunkGroup + * @property {string=} chunkGroupKind + * @property {StatsAsset=} asset + * @property {StatsModule=} module + * @property {StatsChunk=} chunk + * @property {StatsModuleReason=} moduleReason + * @property {StatsModuleIssuer=} moduleIssuer + * @property {StatsError=} error + * @property {StatsProfile=} profile + * @property {StatsLogging=} logging + * @property {StatsModuleTraceItem=} moduleTraceItem + * @property {StatsModuleTraceDependency=} moduleTraceDependency + */ + +/** + * @typedef {object} KnownStatsPrinterColorFn + * @property {(str: string) => string=} bold + * @property {(str: string) => string=} yellow + * @property {(str: string) => string=} red + * @property {(str: string) => string=} green + * @property {(str: string) => string=} magenta + * @property {(str: string) => string=} cyan + */ + +/** + * @typedef {object} KnownStatsPrinterFormaters + * @property {(file: string, oversize?: boolean) => string=} formatFilename + * @property {(id: string) => string=} formatModuleId + * @property {(id: string, direction?: "parent"|"child"|"sibling") => string=} formatChunkId + * @property {(size: number) => string=} formatSize + * @property {(size: string) => string=} formatLayer + * @property {(dateTime: number) => string=} formatDateTime + * @property {(flag: string) => string=} formatFlag + * @property {(time: number, boldQuantity?: boolean) => string=} formatTime + * @property {(message: string) => string=} formatError + */ + +/** @typedef {Record & KnownStatsPrinterColorFn & KnownStatsPrinterFormaters & KnownStatsPrinterContext} StatsPrinterContext */ +/** @typedef {any} PrintObject */ + +/** + * @typedef {object} StatsPrintHooks + * @property {HookMap>} sortElements + * @property {HookMap>} printElements + * @property {HookMap>} sortItems + * @property {HookMap>} getItemName + * @property {HookMap>} printItems + * @property {HookMap>} print + * @property {HookMap>} result + */ + +class StatsPrinter { + constructor() { + /** @type {StatsPrintHooks} */ + this.hooks = Object.freeze({ + sortElements: new HookMap( + () => new SyncBailHook(["elements", "context"]) + ), + printElements: new HookMap( + () => new SyncBailHook(["printedElements", "context"]) + ), + sortItems: new HookMap(() => new SyncBailHook(["items", "context"])), + getItemName: new HookMap(() => new SyncBailHook(["item", "context"])), + printItems: new HookMap( + () => new SyncBailHook(["printedItems", "context"]) + ), + print: new HookMap(() => new SyncBailHook(["object", "context"])), + /** @type {HookMap>} */ + result: new HookMap(() => new SyncWaterfallHook(["result", "context"])) + }); + /** + * @type {TODO} + */ + this._levelHookCache = new Map(); + this._inPrint = false; + } + + /** + * get all level hooks + * @private + * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @param {HM} hookMap hook map + * @param {string} type type + * @returns {H[]} hooks + */ + _getAllLevelHooks(hookMap, type) { + let cache = this._levelHookCache.get(hookMap); + if (cache === undefined) { + cache = new Map(); + this._levelHookCache.set(hookMap, cache); + } + const cacheEntry = cache.get(type); + if (cacheEntry !== undefined) { + return cacheEntry; + } + /** @type {H[]} */ + const hooks = []; + const typeParts = type.split("."); + for (let i = 0; i < typeParts.length; i++) { + const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join("."))); + if (hook) { + hooks.push(hook); + } + } + cache.set(type, hooks); + return hooks; + } + + /** + * Run `fn` for each level + * @private + * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @template {H extends import("tapable").Hook ? R : never} R + * @param {HM} hookMap hook map + * @param {string} type type + * @param {function(H): R | void} fn fn + * @returns {R | void} hook + */ + _forEachLevel(hookMap, type, fn) { + for (const hook of this._getAllLevelHooks(hookMap, type)) { + const result = fn(/** @type {H} */ (hook)); + if (result !== undefined) return result; + } + } + + /** + * Run `fn` for each level + * @private + * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM + * @template {HM extends HookMap ? H : never} H + * @param {HM} hookMap hook map + * @param {string} type type + * @param {string} data data + * @param {function(H, string): string} fn fn + * @returns {string} result of `fn` + */ + _forEachLevelWaterfall(hookMap, type, data, fn) { + for (const hook of this._getAllLevelHooks(hookMap, type)) { + data = fn(/** @type {H} */ (hook), data); + } + return data; + } + + /** + * @param {string} type The type + * @param {PrintObject} object Object to print + * @param {StatsPrinterContext=} baseContext The base context + * @returns {string} printed result + */ + print(type, object, baseContext) { + if (this._inPrint) { + return this._print(type, object, baseContext); + } + try { + this._inPrint = true; + return this._print(type, object, baseContext); + } finally { + this._levelHookCache.clear(); + this._inPrint = false; + } + } + + /** + * @private + * @param {string} type type + * @param {PrintObject} object object + * @param {StatsPrinterContext=} baseContext context + * @returns {string} printed result + */ + _print(type, object, baseContext) { + /** @type {StatsPrinterContext} */ + const context = { + ...baseContext, + type, + [type]: object + }; + + let printResult = this._forEachLevel(this.hooks.print, type, hook => + hook.call(object, context) + ); + if (printResult === undefined) { + if (Array.isArray(object)) { + const sortedItems = object.slice(); + this._forEachLevel(this.hooks.sortItems, type, h => + h.call(sortedItems, context) + ); + const printedItems = sortedItems.map((item, i) => { + /** @type {StatsPrinterContext} */ + const itemContext = { + ...context, + _index: i + }; + const itemName = this._forEachLevel( + this.hooks.getItemName, + `${type}[]`, + h => h.call(item, itemContext) + ); + if (itemName) itemContext[itemName] = item; + return this.print( + itemName ? `${type}[].${itemName}` : `${type}[]`, + item, + itemContext + ); + }); + printResult = this._forEachLevel(this.hooks.printItems, type, h => + h.call(printedItems, context) + ); + if (printResult === undefined) { + const result = printedItems.filter(Boolean); + if (result.length > 0) printResult = result.join("\n"); + } + } else if (object !== null && typeof object === "object") { + const elements = Object.keys(object).filter( + key => object[key] !== undefined + ); + this._forEachLevel(this.hooks.sortElements, type, h => + h.call(elements, context) + ); + const printedElements = elements.map(element => { + const content = this.print(`${type}.${element}`, object[element], { + ...context, + _parent: object, + _element: element, + [element]: object[element] + }); + return { element, content }; + }); + printResult = this._forEachLevel(this.hooks.printElements, type, h => + h.call(printedElements, context) + ); + if (printResult === undefined) { + const result = printedElements.map(e => e.content).filter(Boolean); + if (result.length > 0) printResult = result.join("\n"); + } + } + } + + return this._forEachLevelWaterfall( + this.hooks.result, + type, + /** @type {string} */ (printResult), + (h, r) => h.call(r, context) + ); + } +} +module.exports = StatsPrinter; diff --git a/webpack-lib/lib/util/ArrayHelpers.js b/webpack-lib/lib/util/ArrayHelpers.js new file mode 100644 index 00000000000..ac32ce9f7a3 --- /dev/null +++ b/webpack-lib/lib/util/ArrayHelpers.js @@ -0,0 +1,48 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * Compare two arrays or strings by performing strict equality check for each value. + * @template T [T=any] + * @param {ArrayLike} a Array of values to be compared + * @param {ArrayLike} b Array of values to be compared + * @returns {boolean} returns true if all the elements of passed arrays are strictly equal. + */ + +module.exports.equals = (a, b) => { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +}; + +/** + * Partition an array by calling a predicate function on each value. + * @template T [T=any] + * @param {Array} arr Array of values to be partitioned + * @param {(value: T) => boolean} fn Partition function which partitions based on truthiness of result. + * @returns {[Array, Array]} returns the values of `arr` partitioned into two new arrays based on fn predicate. + */ + +module.exports.groupBy = ( + // eslint-disable-next-line default-param-last + arr = [], + fn +) => + arr.reduce( + /** + * @param {[Array, Array]} groups An accumulator storing already partitioned values returned from previous call. + * @param {T} value The value of the current element + * @returns {[Array, Array]} returns an array of partitioned groups accumulator resulting from calling a predicate on the current value. + */ + (groups, value) => { + groups[fn(value) ? 0 : 1].push(value); + return groups; + }, + [[], []] + ); diff --git a/webpack-lib/lib/util/ArrayQueue.js b/webpack-lib/lib/util/ArrayQueue.js new file mode 100644 index 00000000000..522abf93de2 --- /dev/null +++ b/webpack-lib/lib/util/ArrayQueue.js @@ -0,0 +1,104 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template T + */ +class ArrayQueue { + /** + * @param {Iterable=} items The initial elements. + */ + constructor(items) { + /** + * @private + * @type {T[]} + */ + this._list = items ? Array.from(items) : []; + /** + * @private + * @type {T[]} + */ + this._listReversed = []; + } + + /** + * Returns the number of elements in this queue. + * @returns {number} The number of elements in this queue. + */ + get length() { + return this._list.length + this._listReversed.length; + } + + /** + * Empties the queue. + */ + clear() { + this._list.length = 0; + this._listReversed.length = 0; + } + + /** + * Appends the specified element to this queue. + * @param {T} item The element to add. + * @returns {void} + */ + enqueue(item) { + this._list.push(item); + } + + /** + * Retrieves and removes the head of this queue. + * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. + */ + dequeue() { + if (this._listReversed.length === 0) { + if (this._list.length === 0) return; + if (this._list.length === 1) return this._list.pop(); + if (this._list.length < 16) return this._list.shift(); + const temp = this._listReversed; + this._listReversed = this._list; + this._listReversed.reverse(); + this._list = temp; + } + return this._listReversed.pop(); + } + + /** + * Finds and removes an item + * @param {T} item the item + * @returns {void} + */ + delete(item) { + const i = this._list.indexOf(item); + if (i >= 0) { + this._list.splice(i, 1); + } else { + const i = this._listReversed.indexOf(item); + if (i >= 0) this._listReversed.splice(i, 1); + } + } + + [Symbol.iterator]() { + return { + next: () => { + const item = this.dequeue(); + if (item) { + return { + done: false, + value: item + }; + } + return { + done: true, + value: undefined + }; + } + }; + } +} + +module.exports = ArrayQueue; diff --git a/webpack-lib/lib/util/AsyncQueue.js b/webpack-lib/lib/util/AsyncQueue.js new file mode 100644 index 00000000000..fb01d49e91d --- /dev/null +++ b/webpack-lib/lib/util/AsyncQueue.js @@ -0,0 +1,410 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncHook, AsyncSeriesHook } = require("tapable"); +const { makeWebpackError } = require("../HookWebpackError"); +const WebpackError = require("../WebpackError"); +const ArrayQueue = require("./ArrayQueue"); + +const QUEUED_STATE = 0; +const PROCESSING_STATE = 1; +const DONE_STATE = 2; + +let inHandleResult = 0; + +/** + * @template T + * @callback Callback + * @param {(WebpackError | null)=} err + * @param {(T | null)=} result + */ + +/** + * @template T + * @template K + * @template R + */ +class AsyncQueueEntry { + /** + * @param {T} item the item + * @param {Callback} callback the callback + */ + constructor(item, callback) { + this.item = item; + /** @type {typeof QUEUED_STATE | typeof PROCESSING_STATE | typeof DONE_STATE} */ + this.state = QUEUED_STATE; + /** @type {Callback | undefined} */ + this.callback = callback; + /** @type {Callback[] | undefined} */ + this.callbacks = undefined; + /** @type {R | null | undefined} */ + this.result = undefined; + /** @type {WebpackError | null | undefined} */ + this.error = undefined; + } +} + +/** + * @template T, K + * @typedef {function(T): K} getKey + */ + +/** + * @template T, R + * @typedef {function(T, Callback): void} Processor + */ + +/** + * @template T + * @template K + * @template R + */ +class AsyncQueue { + /** + * @param {object} options options object + * @param {string=} options.name name of the queue + * @param {number=} options.parallelism how many items should be processed at once + * @param {string=} options.context context of execution + * @param {AsyncQueue=} options.parent parent queue, which will have priority over this queue and with shared parallelism + * @param {getKey=} options.getKey extract key from item + * @param {Processor} options.processor async function to process items + */ + constructor({ name, context, parallelism, parent, processor, getKey }) { + this._name = name; + this._context = context || "normal"; + this._parallelism = parallelism || 1; + this._processor = processor; + this._getKey = + getKey || + /** @type {getKey} */ (item => /** @type {T & K} */ (item)); + /** @type {Map>} */ + this._entries = new Map(); + /** @type {ArrayQueue>} */ + this._queued = new ArrayQueue(); + /** @type {AsyncQueue[] | undefined} */ + this._children = undefined; + this._activeTasks = 0; + this._willEnsureProcessing = false; + this._needProcessing = false; + this._stopped = false; + /** @type {AsyncQueue} */ + this._root = parent ? parent._root : this; + if (parent) { + if (this._root._children === undefined) { + this._root._children = [this]; + } else { + this._root._children.push(this); + } + } + + this.hooks = { + /** @type {AsyncSeriesHook<[T]>} */ + beforeAdd: new AsyncSeriesHook(["item"]), + /** @type {SyncHook<[T]>} */ + added: new SyncHook(["item"]), + /** @type {AsyncSeriesHook<[T]>} */ + beforeStart: new AsyncSeriesHook(["item"]), + /** @type {SyncHook<[T]>} */ + started: new SyncHook(["item"]), + /** @type {SyncHook<[T, WebpackError | null | undefined, R | null | undefined]>} */ + result: new SyncHook(["item", "error", "result"]) + }; + + this._ensureProcessing = this._ensureProcessing.bind(this); + } + + /** + * @returns {string} context of execution + */ + getContext() { + return this._context; + } + + /** + * @param {string} value context of execution + */ + setContext(value) { + this._context = value; + } + + /** + * @param {T} item an item + * @param {Callback} callback callback function + * @returns {void} + */ + add(item, callback) { + if (this._stopped) return callback(new WebpackError("Queue was stopped")); + this.hooks.beforeAdd.callAsync(item, err => { + if (err) { + callback( + makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`) + ); + return; + } + const key = this._getKey(item); + const entry = this._entries.get(key); + if (entry !== undefined) { + if (entry.state === DONE_STATE) { + if (inHandleResult++ > 3) { + process.nextTick(() => callback(entry.error, entry.result)); + } else { + callback(entry.error, entry.result); + } + inHandleResult--; + } else if (entry.callbacks === undefined) { + entry.callbacks = [callback]; + } else { + entry.callbacks.push(callback); + } + return; + } + const newEntry = new AsyncQueueEntry(item, callback); + if (this._stopped) { + this.hooks.added.call(item); + this._root._activeTasks++; + process.nextTick(() => + this._handleResult(newEntry, new WebpackError("Queue was stopped")) + ); + } else { + this._entries.set(key, newEntry); + this._queued.enqueue(newEntry); + const root = this._root; + root._needProcessing = true; + if (root._willEnsureProcessing === false) { + root._willEnsureProcessing = true; + setImmediate(root._ensureProcessing); + } + this.hooks.added.call(item); + } + }); + } + + /** + * @param {T} item an item + * @returns {void} + */ + invalidate(item) { + const key = this._getKey(item); + const entry = + /** @type {AsyncQueueEntry} */ + (this._entries.get(key)); + this._entries.delete(key); + if (entry.state === QUEUED_STATE) { + this._queued.delete(entry); + } + } + + /** + * Waits for an already started item + * @param {T} item an item + * @param {Callback} callback callback function + * @returns {void} + */ + waitFor(item, callback) { + const key = this._getKey(item); + const entry = this._entries.get(key); + if (entry === undefined) { + return callback( + new WebpackError( + "waitFor can only be called for an already started item" + ) + ); + } + if (entry.state === DONE_STATE) { + process.nextTick(() => callback(entry.error, entry.result)); + } else if (entry.callbacks === undefined) { + entry.callbacks = [callback]; + } else { + entry.callbacks.push(callback); + } + } + + /** + * @returns {void} + */ + stop() { + this._stopped = true; + const queue = this._queued; + this._queued = new ArrayQueue(); + const root = this._root; + for (const entry of queue) { + this._entries.delete( + this._getKey(/** @type {AsyncQueueEntry} */ (entry).item) + ); + root._activeTasks++; + this._handleResult( + /** @type {AsyncQueueEntry} */ (entry), + new WebpackError("Queue was stopped") + ); + } + } + + /** + * @returns {void} + */ + increaseParallelism() { + const root = this._root; + root._parallelism++; + /* istanbul ignore next */ + if (root._willEnsureProcessing === false && root._needProcessing) { + root._willEnsureProcessing = true; + setImmediate(root._ensureProcessing); + } + } + + /** + * @returns {void} + */ + decreaseParallelism() { + const root = this._root; + root._parallelism--; + } + + /** + * @param {T} item an item + * @returns {boolean} true, if the item is currently being processed + */ + isProcessing(item) { + const key = this._getKey(item); + const entry = this._entries.get(key); + return entry !== undefined && entry.state === PROCESSING_STATE; + } + + /** + * @param {T} item an item + * @returns {boolean} true, if the item is currently queued + */ + isQueued(item) { + const key = this._getKey(item); + const entry = this._entries.get(key); + return entry !== undefined && entry.state === QUEUED_STATE; + } + + /** + * @param {T} item an item + * @returns {boolean} true, if the item is currently queued + */ + isDone(item) { + const key = this._getKey(item); + const entry = this._entries.get(key); + return entry !== undefined && entry.state === DONE_STATE; + } + + /** + * @returns {void} + */ + _ensureProcessing() { + while (this._activeTasks < this._parallelism) { + const entry = this._queued.dequeue(); + if (entry === undefined) break; + this._activeTasks++; + entry.state = PROCESSING_STATE; + this._startProcessing(entry); + } + this._willEnsureProcessing = false; + if (this._queued.length > 0) return; + if (this._children !== undefined) { + for (const child of this._children) { + while (this._activeTasks < this._parallelism) { + const entry = child._queued.dequeue(); + if (entry === undefined) break; + this._activeTasks++; + entry.state = PROCESSING_STATE; + child._startProcessing(entry); + } + if (child._queued.length > 0) return; + } + } + if (!this._willEnsureProcessing) this._needProcessing = false; + } + + /** + * @param {AsyncQueueEntry} entry the entry + * @returns {void} + */ + _startProcessing(entry) { + this.hooks.beforeStart.callAsync(entry.item, err => { + if (err) { + this._handleResult( + entry, + makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`) + ); + return; + } + let inCallback = false; + try { + this._processor(entry.item, (e, r) => { + inCallback = true; + this._handleResult(entry, e, r); + }); + } catch (err) { + if (inCallback) throw err; + this._handleResult(entry, /** @type {WebpackError} */ (err), null); + } + this.hooks.started.call(entry.item); + }); + } + + /** + * @param {AsyncQueueEntry} entry the entry + * @param {(WebpackError | null)=} err error, if any + * @param {(R | null)=} result result, if any + * @returns {void} + */ + _handleResult(entry, err, result) { + this.hooks.result.callAsync(entry.item, err, result, hookError => { + const error = hookError + ? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`) + : err; + + const callback = /** @type {Callback} */ (entry.callback); + const callbacks = entry.callbacks; + entry.state = DONE_STATE; + entry.callback = undefined; + entry.callbacks = undefined; + entry.result = result; + entry.error = error; + + const root = this._root; + root._activeTasks--; + if (root._willEnsureProcessing === false && root._needProcessing) { + root._willEnsureProcessing = true; + setImmediate(root._ensureProcessing); + } + + if (inHandleResult++ > 3) { + process.nextTick(() => { + callback(error, result); + if (callbacks !== undefined) { + for (const callback of callbacks) { + callback(error, result); + } + } + }); + } else { + callback(error, result); + if (callbacks !== undefined) { + for (const callback of callbacks) { + callback(error, result); + } + } + } + inHandleResult--; + }); + } + + clear() { + this._entries.clear(); + this._queued.clear(); + this._activeTasks = 0; + this._willEnsureProcessing = false; + this._needProcessing = false; + this._stopped = false; + } +} + +module.exports = AsyncQueue; diff --git a/webpack-lib/lib/util/Hash.js b/webpack-lib/lib/util/Hash.js new file mode 100644 index 00000000000..a0078275327 --- /dev/null +++ b/webpack-lib/lib/util/Hash.js @@ -0,0 +1,35 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +class Hash { + /* istanbul ignore next */ + /** + * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} + * @abstract + * @param {string|Buffer} data data + * @param {string=} inputEncoding data encoding + * @returns {this} updated hash + */ + update(data, inputEncoding) { + const AbstractMethodError = require("../AbstractMethodError"); + throw new AbstractMethodError(); + } + + /* istanbul ignore next */ + /** + * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} + * @abstract + * @param {string=} encoding encoding of the return value + * @returns {string|Buffer} digest + */ + digest(encoding) { + const AbstractMethodError = require("../AbstractMethodError"); + throw new AbstractMethodError(); + } +} + +module.exports = Hash; diff --git a/webpack-lib/lib/util/IterableHelpers.js b/webpack-lib/lib/util/IterableHelpers.js new file mode 100644 index 00000000000..73d02c66727 --- /dev/null +++ b/webpack-lib/lib/util/IterableHelpers.js @@ -0,0 +1,45 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template T + * @param {Iterable} set a set + * @returns {T | undefined} last item + */ +const last = set => { + let last; + for (const item of set) last = item; + return last; +}; + +/** + * @template T + * @param {Iterable} iterable iterable + * @param {function(T): boolean | null | undefined} filter predicate + * @returns {boolean} true, if some items match the filter predicate + */ +const someInIterable = (iterable, filter) => { + for (const item of iterable) { + if (filter(item)) return true; + } + return false; +}; + +/** + * @template T + * @param {Iterable} iterable an iterable + * @returns {number} count of items + */ +const countIterable = iterable => { + let i = 0; + for (const _ of iterable) i++; + return i; +}; + +module.exports.last = last; +module.exports.someInIterable = someInIterable; +module.exports.countIterable = countIterable; diff --git a/webpack-lib/lib/util/LazyBucketSortedSet.js b/webpack-lib/lib/util/LazyBucketSortedSet.js new file mode 100644 index 00000000000..5469010893d --- /dev/null +++ b/webpack-lib/lib/util/LazyBucketSortedSet.js @@ -0,0 +1,252 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { first } = require("./SetHelpers"); +const SortableSet = require("./SortableSet"); + +/** + * @template T + * @typedef {LazyBucketSortedSet | SortableSet} Entry + */ + +/** + * @template T + * @typedef {(function(T): any) | (function(any, any): number)} Arg + */ + +/** + * Multi layer bucket sorted set: + * Supports adding non-existing items (DO NOT ADD ITEM TWICE), + * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET), + * Supports popping the first items according to defined order, + * Supports iterating all items without order, + * Supports updating an item in an efficient way, + * Supports size property, which is the number of items, + * Items are lazy partially sorted when needed + * @template T + * @template K + */ +class LazyBucketSortedSet { + /** + * @param {function(T): K} getKey function to get key from item + * @param {function(K, K): number} comparator comparator to sort keys + * @param {...Arg} args more pairs of getKey and comparator plus optional final comparator for the last layer + */ + constructor(getKey, comparator, ...args) { + this._getKey = getKey; + /** @type {Arg[]} */ + this._innerArgs = args; + this._leaf = args.length <= 1; + this._keys = new SortableSet(undefined, comparator); + /** @type {Map>} */ + this._map = new Map(); + this._unsortedItems = new Set(); + this.size = 0; + } + + /** + * @param {T} item an item + * @returns {void} + */ + add(item) { + this.size++; + this._unsortedItems.add(item); + } + + /** + * @param {K} key key of item + * @param {T} item the item + * @returns {void} + */ + _addInternal(key, item) { + let entry = this._map.get(key); + if (entry === undefined) { + entry = + /** @type {Entry} */ + ( + this._leaf + ? new SortableSet(undefined, this._innerArgs[0]) + : new /** @type {TODO} */ (LazyBucketSortedSet)(...this._innerArgs) + ); + this._keys.add(key); + this._map.set(key, entry); + } + /** @type {Entry} */ + (entry).add(item); + } + + /** + * @param {T} item an item + * @returns {void} + */ + delete(item) { + this.size--; + if (this._unsortedItems.has(item)) { + this._unsortedItems.delete(item); + return; + } + const key = this._getKey(item); + const entry = /** @type {Entry} */ (this._map.get(key)); + entry.delete(item); + if (entry.size === 0) { + this._deleteKey(key); + } + } + + /** + * @param {K} key key to be removed + * @returns {void} + */ + _deleteKey(key) { + this._keys.delete(key); + this._map.delete(key); + } + + /** + * @returns {T | undefined} an item + */ + popFirst() { + if (this.size === 0) return; + this.size--; + if (this._unsortedItems.size > 0) { + for (const item of this._unsortedItems) { + const key = this._getKey(item); + this._addInternal(key, item); + } + this._unsortedItems.clear(); + } + this._keys.sort(); + const key = /** @type {K} */ (first(this._keys)); + const entry = this._map.get(key); + if (this._leaf) { + const leafEntry = /** @type {SortableSet} */ (entry); + leafEntry.sort(); + const item = /** @type {T} */ (first(leafEntry)); + leafEntry.delete(item); + if (leafEntry.size === 0) { + this._deleteKey(key); + } + return item; + } + const nodeEntry = /** @type {LazyBucketSortedSet} */ (entry); + const item = nodeEntry.popFirst(); + if (nodeEntry.size === 0) { + this._deleteKey(key); + } + return item; + } + + /** + * @param {T} item to be updated item + * @returns {function(true=): void} finish update + */ + startUpdate(item) { + if (this._unsortedItems.has(item)) { + return remove => { + if (remove) { + this._unsortedItems.delete(item); + this.size--; + } + }; + } + const key = this._getKey(item); + if (this._leaf) { + const oldEntry = /** @type {SortableSet} */ (this._map.get(key)); + return remove => { + if (remove) { + this.size--; + oldEntry.delete(item); + if (oldEntry.size === 0) { + this._deleteKey(key); + } + return; + } + const newKey = this._getKey(item); + if (key === newKey) { + // This flags the sortable set as unordered + oldEntry.add(item); + } else { + oldEntry.delete(item); + if (oldEntry.size === 0) { + this._deleteKey(key); + } + this._addInternal(newKey, item); + } + }; + } + const oldEntry = /** @type {LazyBucketSortedSet} */ ( + this._map.get(key) + ); + const finishUpdate = oldEntry.startUpdate(item); + return remove => { + if (remove) { + this.size--; + finishUpdate(true); + if (oldEntry.size === 0) { + this._deleteKey(key); + } + return; + } + const newKey = this._getKey(item); + if (key === newKey) { + finishUpdate(); + } else { + finishUpdate(true); + if (oldEntry.size === 0) { + this._deleteKey(key); + } + this._addInternal(newKey, item); + } + }; + } + + /** + * @param {Iterator[]} iterators list of iterators to append to + * @returns {void} + */ + _appendIterators(iterators) { + if (this._unsortedItems.size > 0) + iterators.push(this._unsortedItems[Symbol.iterator]()); + for (const key of this._keys) { + const entry = this._map.get(key); + if (this._leaf) { + const leafEntry = /** @type {SortableSet} */ (entry); + const iterator = leafEntry[Symbol.iterator](); + iterators.push(iterator); + } else { + const nodeEntry = /** @type {LazyBucketSortedSet} */ (entry); + nodeEntry._appendIterators(iterators); + } + } + } + + /** + * @returns {Iterator} the iterator + */ + [Symbol.iterator]() { + /** @type {Iterator[]} */ + const iterators = []; + this._appendIterators(iterators); + iterators.reverse(); + let currentIterator = + /** @type {Iterator} */ + (iterators.pop()); + return { + next: () => { + const res = currentIterator.next(); + if (res.done) { + if (iterators.length === 0) return res; + currentIterator = /** @type {Iterator} */ (iterators.pop()); + return currentIterator.next(); + } + return res; + } + }; + } +} + +module.exports = LazyBucketSortedSet; diff --git a/webpack-lib/lib/util/LazySet.js b/webpack-lib/lib/util/LazySet.js new file mode 100644 index 00000000000..623c1c5a329 --- /dev/null +++ b/webpack-lib/lib/util/LazySet.js @@ -0,0 +1,229 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const makeSerializable = require("./makeSerializable.js"); + +/** + * @template T + * @param {Set} targetSet set where items should be added + * @param {Set>} toMerge iterables to be merged + * @returns {void} + */ +const merge = (targetSet, toMerge) => { + for (const set of toMerge) { + for (const item of set) { + targetSet.add(item); + } + } +}; + +/** + * @template T + * @param {Set>} targetSet set where iterables should be added + * @param {Array>} toDeepMerge lazy sets to be flattened + * @returns {void} + */ +const flatten = (targetSet, toDeepMerge) => { + for (const set of toDeepMerge) { + if (set._set.size > 0) targetSet.add(set._set); + if (set._needMerge) { + for (const mergedSet of set._toMerge) { + targetSet.add(mergedSet); + } + flatten(targetSet, set._toDeepMerge); + } + } +}; + +/** + * Like Set but with an addAll method to eventually add items from another iterable. + * Access methods make sure that all delayed operations are executed. + * Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration). + * @template T + */ +class LazySet { + /** + * @param {Iterable=} iterable init iterable + */ + constructor(iterable) { + /** @type {Set} */ + this._set = new Set(iterable); + /** @type {Set>} */ + this._toMerge = new Set(); + /** @type {Array>} */ + this._toDeepMerge = []; + this._needMerge = false; + this._deopt = false; + } + + _flatten() { + flatten(this._toMerge, this._toDeepMerge); + this._toDeepMerge.length = 0; + } + + _merge() { + this._flatten(); + merge(this._set, this._toMerge); + this._toMerge.clear(); + this._needMerge = false; + } + + _isEmpty() { + return ( + this._set.size === 0 && + this._toMerge.size === 0 && + this._toDeepMerge.length === 0 + ); + } + + get size() { + if (this._needMerge) this._merge(); + return this._set.size; + } + + /** + * @param {T} item an item + * @returns {LazySet} itself + */ + add(item) { + this._set.add(item); + return this; + } + + /** + * @param {Iterable | LazySet} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set + * @returns {LazySet} itself + */ + addAll(iterable) { + if (this._deopt) { + const _set = this._set; + for (const item of iterable) { + _set.add(item); + } + } else { + if (iterable instanceof LazySet) { + if (iterable._isEmpty()) return this; + this._toDeepMerge.push(iterable); + this._needMerge = true; + if (this._toDeepMerge.length > 100000) { + this._flatten(); + } + } else { + this._toMerge.add(iterable); + this._needMerge = true; + } + if (this._toMerge.size > 100000) this._merge(); + } + return this; + } + + clear() { + this._set.clear(); + this._toMerge.clear(); + this._toDeepMerge.length = 0; + this._needMerge = false; + this._deopt = false; + } + + /** + * @param {T} value an item + * @returns {boolean} true, if the value was in the Set before + */ + delete(value) { + if (this._needMerge) this._merge(); + return this._set.delete(value); + } + + /** + * @returns {IterableIterator<[T, T]>} entries + */ + entries() { + this._deopt = true; + if (this._needMerge) this._merge(); + return this._set.entries(); + } + + /** + * @param {function(T, T, Set): void} callbackFn function called for each entry + * @param {any} thisArg this argument for the callbackFn + * @returns {void} + */ + forEach(callbackFn, thisArg) { + this._deopt = true; + if (this._needMerge) this._merge(); + // eslint-disable-next-line unicorn/no-array-for-each + this._set.forEach(callbackFn, thisArg); + } + + /** + * @param {T} item an item + * @returns {boolean} true, when the item is in the Set + */ + has(item) { + if (this._needMerge) this._merge(); + return this._set.has(item); + } + + /** + * @returns {IterableIterator} keys + */ + keys() { + this._deopt = true; + if (this._needMerge) this._merge(); + return this._set.keys(); + } + + /** + * @returns {IterableIterator} values + */ + values() { + this._deopt = true; + if (this._needMerge) this._merge(); + return this._set.values(); + } + + /** + * @returns {IterableIterator} iterable iterator + */ + [Symbol.iterator]() { + this._deopt = true; + if (this._needMerge) this._merge(); + return this._set[Symbol.iterator](); + } + + /* istanbul ignore next */ + get [Symbol.toStringTag]() { + return "LazySet"; + } + + /** + * @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext} context context + */ + serialize({ write }) { + if (this._needMerge) this._merge(); + write(this._set.size); + for (const item of this._set) write(item); + } + + /** + * @template T + * @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} context context + * @returns {LazySet} lazy set + */ + static deserialize({ read }) { + const count = read(); + const items = []; + for (let i = 0; i < count; i++) { + items.push(read()); + } + return new LazySet(items); + } +} + +makeSerializable(LazySet, "webpack/lib/util/LazySet"); + +module.exports = LazySet; diff --git a/webpack-lib/lib/util/MapHelpers.js b/webpack-lib/lib/util/MapHelpers.js new file mode 100644 index 00000000000..259f621d8e0 --- /dev/null +++ b/webpack-lib/lib/util/MapHelpers.js @@ -0,0 +1,34 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * getOrInsert is a helper function for maps that allows you to get a value + * from a map if it exists, or insert a new value if it doesn't. If it value doesn't + * exist, it will be computed by the provided function. + * @template K + * @template V + * @param {Map} map The map object to check + * @param {K} key The key to check + * @param {function(): V} computer function which will compute the value if it doesn't exist + * @returns {V} The value from the map, or the computed value + * @example + * ```js + * const map = new Map(); + * const value = getOrInsert(map, "key", () => "value"); + * console.log(value); // "value" + * ``` + */ +module.exports.getOrInsert = (map, key, computer) => { + // Grab key from map + const value = map.get(key); + // If the value already exists, return it + if (value !== undefined) return value; + // Otherwise compute the value, set it in the map, and return it + const newValue = computer(); + map.set(key, newValue); + return newValue; +}; diff --git a/webpack-lib/lib/util/ParallelismFactorCalculator.js b/webpack-lib/lib/util/ParallelismFactorCalculator.js new file mode 100644 index 00000000000..d7725b7bfd4 --- /dev/null +++ b/webpack-lib/lib/util/ParallelismFactorCalculator.js @@ -0,0 +1,69 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const binarySearchBounds = require("./binarySearchBounds"); + +/** @typedef {function(number): void} Callback */ + +class ParallelismFactorCalculator { + constructor() { + /** @type {number[]} */ + this._rangePoints = []; + /** @type {Callback[]} */ + this._rangeCallbacks = []; + } + + /** + * @param {number} start range start + * @param {number} end range end + * @param {Callback} callback callback + * @returns {void} + */ + range(start, end, callback) { + if (start === end) return callback(1); + this._rangePoints.push(start); + this._rangePoints.push(end); + this._rangeCallbacks.push(callback); + } + + calculate() { + const segments = Array.from(new Set(this._rangePoints)).sort((a, b) => + a < b ? -1 : 1 + ); + const parallelism = segments.map(() => 0); + const rangeStartIndices = []; + for (let i = 0; i < this._rangePoints.length; i += 2) { + const start = this._rangePoints[i]; + const end = this._rangePoints[i + 1]; + let idx = binarySearchBounds.eq(segments, start); + rangeStartIndices.push(idx); + do { + parallelism[idx]++; + idx++; + } while (segments[idx] < end); + } + for (let i = 0; i < this._rangeCallbacks.length; i++) { + const start = this._rangePoints[i * 2]; + const end = this._rangePoints[i * 2 + 1]; + let idx = rangeStartIndices[i]; + let sum = 0; + let totalDuration = 0; + let current = start; + do { + const p = parallelism[idx]; + idx++; + const duration = segments[idx] - current; + totalDuration += duration; + current = segments[idx]; + sum += p * duration; + } while (current < end); + this._rangeCallbacks[i](sum / totalDuration); + } + } +} + +module.exports = ParallelismFactorCalculator; diff --git a/webpack-lib/lib/util/Queue.js b/webpack-lib/lib/util/Queue.js new file mode 100644 index 00000000000..3820770655a --- /dev/null +++ b/webpack-lib/lib/util/Queue.js @@ -0,0 +1,52 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template T + */ +class Queue { + /** + * @param {Iterable=} items The initial elements. + */ + constructor(items) { + /** + * @private + * @type {Set} + */ + this._set = new Set(items); + } + + /** + * Returns the number of elements in this queue. + * @returns {number} The number of elements in this queue. + */ + get length() { + return this._set.size; + } + + /** + * Appends the specified element to this queue. + * @param {T} item The element to add. + * @returns {void} + */ + enqueue(item) { + this._set.add(item); + } + + /** + * Retrieves and removes the head of this queue. + * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. + */ + dequeue() { + const result = this._set[Symbol.iterator]().next(); + if (result.done) return; + this._set.delete(result.value); + return result.value; + } +} + +module.exports = Queue; diff --git a/webpack-lib/lib/util/Semaphore.js b/webpack-lib/lib/util/Semaphore.js new file mode 100644 index 00000000000..5277fedb6c6 --- /dev/null +++ b/webpack-lib/lib/util/Semaphore.js @@ -0,0 +1,51 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +class Semaphore { + /** + * Creates an instance of Semaphore. + * @param {number} available the amount available number of "tasks" + * in the Semaphore + */ + constructor(available) { + this.available = available; + /** @type {(function(): void)[]} */ + this.waiters = []; + /** @private */ + this._continue = this._continue.bind(this); + } + + /** + * @param {function(): void} callback function block to capture and run + * @returns {void} + */ + acquire(callback) { + if (this.available > 0) { + this.available--; + callback(); + } else { + this.waiters.push(callback); + } + } + + release() { + this.available++; + if (this.waiters.length > 0) { + process.nextTick(this._continue); + } + } + + _continue() { + if (this.available > 0 && this.waiters.length > 0) { + this.available--; + const callback = /** @type {(function(): void)} */ (this.waiters.pop()); + callback(); + } + } +} + +module.exports = Semaphore; diff --git a/webpack-lib/lib/util/SetHelpers.js b/webpack-lib/lib/util/SetHelpers.js new file mode 100644 index 00000000000..5908cce9339 --- /dev/null +++ b/webpack-lib/lib/util/SetHelpers.js @@ -0,0 +1,94 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * intersect creates Set containing the intersection of elements between all sets + * @template T + * @param {Set[]} sets an array of sets being checked for shared elements + * @returns {Set} returns a new Set containing the intersecting items + */ +const intersect = sets => { + if (sets.length === 0) return new Set(); + if (sets.length === 1) return new Set(sets[0]); + let minSize = Infinity; + let minIndex = -1; + for (let i = 0; i < sets.length; i++) { + const size = sets[i].size; + if (size < minSize) { + minIndex = i; + minSize = size; + } + } + const current = new Set(sets[minIndex]); + for (let i = 0; i < sets.length; i++) { + if (i === minIndex) continue; + const set = sets[i]; + for (const item of current) { + if (!set.has(item)) { + current.delete(item); + } + } + } + return current; +}; + +/** + * Checks if a set is the subset of another set + * @template T + * @param {Set} bigSet a Set which contains the original elements to compare against + * @param {Set} smallSet the set whose elements might be contained inside of bigSet + * @returns {boolean} returns true if smallSet contains all elements inside of the bigSet + */ +const isSubset = (bigSet, smallSet) => { + if (bigSet.size < smallSet.size) return false; + for (const item of smallSet) { + if (!bigSet.has(item)) return false; + } + return true; +}; + +/** + * @template T + * @param {Set} set a set + * @param {function(T): boolean} fn selector function + * @returns {T | undefined} found item + */ +const find = (set, fn) => { + for (const item of set) { + if (fn(item)) return item; + } +}; + +/** + * @template T + * @param {Set | ReadonlySet} set a set + * @returns {T | undefined} first item + */ +const first = set => { + const entry = set.values().next(); + return entry.done ? undefined : entry.value; +}; + +/** + * @template T + * @param {Set} a first + * @param {Set} b second + * @returns {Set} combined set, may be identical to a or b + */ +const combine = (a, b) => { + if (b.size === 0) return a; + if (a.size === 0) return b; + const set = new Set(a); + for (const item of b) set.add(item); + return set; +}; + +module.exports.intersect = intersect; +module.exports.isSubset = isSubset; +module.exports.find = find; +module.exports.first = first; +module.exports.combine = combine; diff --git a/webpack-lib/lib/util/SortableSet.js b/webpack-lib/lib/util/SortableSet.js new file mode 100644 index 00000000000..9260c163a0f --- /dev/null +++ b/webpack-lib/lib/util/SortableSet.js @@ -0,0 +1,173 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NONE = Symbol("not sorted"); + +/** + * A subset of Set that offers sorting functionality + * @template T item type in set + * @extends {Set} + */ +class SortableSet extends Set { + /** + * Create a new sortable set + * @template T + * @param {Iterable=} initialIterable The initial iterable value + * @typedef {function(T, T): number} SortFunction + * @param {SortFunction=} defaultSort Default sorting function + */ + constructor(initialIterable, defaultSort) { + super(initialIterable); + /** + * @private + * @type {undefined | SortFunction} + */ + this._sortFn = defaultSort; + /** + * @private + * @type {typeof NONE | undefined | function(T, T): number}} + */ + this._lastActiveSortFn = NONE; + /** + * @private + * @type {Map | undefined} + */ + this._cache = undefined; + /** + * @private + * @type {Map | undefined} + */ + this._cacheOrderIndependent = undefined; + } + + /** + * @param {T} value value to add to set + * @returns {this} returns itself + */ + add(value) { + this._lastActiveSortFn = NONE; + this._invalidateCache(); + this._invalidateOrderedCache(); + super.add(value); + return this; + } + + /** + * @param {T} value value to delete + * @returns {boolean} true if value existed in set, false otherwise + */ + delete(value) { + this._invalidateCache(); + this._invalidateOrderedCache(); + return super.delete(value); + } + + /** + * @returns {void} + */ + clear() { + this._invalidateCache(); + this._invalidateOrderedCache(); + return super.clear(); + } + + /** + * Sort with a comparer function + * @param {SortFunction | undefined} sortFn Sorting comparer function + * @returns {void} + */ + sortWith(sortFn) { + if (this.size <= 1 || sortFn === this._lastActiveSortFn) { + // already sorted - nothing to do + return; + } + + const sortedArray = Array.from(this).sort(sortFn); + super.clear(); + for (let i = 0; i < sortedArray.length; i += 1) { + super.add(sortedArray[i]); + } + this._lastActiveSortFn = sortFn; + this._invalidateCache(); + } + + sort() { + this.sortWith(this._sortFn); + return this; + } + + /** + * Get data from cache + * @template R + * @param {function(SortableSet): R} fn function to calculate value + * @returns {R} returns result of fn(this), cached until set changes + */ + getFromCache(fn) { + if (this._cache === undefined) { + this._cache = new Map(); + } else { + const result = this._cache.get(fn); + const data = /** @type {R} */ (result); + if (data !== undefined) { + return data; + } + } + const newData = fn(this); + this._cache.set(fn, newData); + return newData; + } + + /** + * Get data from cache (ignoring sorting) + * @template R + * @param {function(SortableSet): R} fn function to calculate value + * @returns {R} returns result of fn(this), cached until set changes + */ + getFromUnorderedCache(fn) { + if (this._cacheOrderIndependent === undefined) { + this._cacheOrderIndependent = new Map(); + } else { + const result = this._cacheOrderIndependent.get(fn); + const data = /** @type {R} */ (result); + if (data !== undefined) { + return data; + } + } + const newData = fn(this); + this._cacheOrderIndependent.set(fn, newData); + return newData; + } + + /** + * @private + * @returns {void} + */ + _invalidateCache() { + if (this._cache !== undefined) { + this._cache.clear(); + } + } + + /** + * @private + * @returns {void} + */ + _invalidateOrderedCache() { + if (this._cacheOrderIndependent !== undefined) { + this._cacheOrderIndependent.clear(); + } + } + + /** + * @returns {T[]} the raw array + */ + toJSON() { + return Array.from(this); + } +} + +module.exports = SortableSet; diff --git a/webpack-lib/lib/util/StackedCacheMap.js b/webpack-lib/lib/util/StackedCacheMap.js new file mode 100644 index 00000000000..820f0d1b3d8 --- /dev/null +++ b/webpack-lib/lib/util/StackedCacheMap.js @@ -0,0 +1,140 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * The StackedCacheMap is a data structure designed as an alternative to a Map + * in situations where you need to handle multiple item additions and + * frequently access the largest map. + * + * It is particularly optimized for efficiently adding multiple items + * at once, which can be achieved using the `addAll` method. + * + * It has a fallback Map that is used when the map to be added is mutable. + * + * Note: `delete` and `has` are not supported for performance reasons. + * @example + * ```js + * const map = new StackedCacheMap(); + * map.addAll(new Map([["a", 1], ["b", 2]]), true); + * map.addAll(new Map([["c", 3], ["d", 4]]), true); + * map.get("a"); // 1 + * map.get("d"); // 4 + * for (const [key, value] of map) { + * console.log(key, value); + * } + * ``` + * @template K + * @template V + */ +class StackedCacheMap { + constructor() { + /** @type {Map} */ + this.map = new Map(); + /** @type {ReadonlyMap[]} */ + this.stack = []; + } + + /** + * If `immutable` is true, the map can be referenced by the StackedCacheMap + * and should not be changed afterwards. If the map is mutable, all items + * are copied into a fallback Map. + * @param {ReadonlyMap} map map to add + * @param {boolean=} immutable if 'map' is immutable and StackedCacheMap can keep referencing it + */ + addAll(map, immutable) { + if (immutable) { + this.stack.push(map); + + // largest map should go first + for (let i = this.stack.length - 1; i > 0; i--) { + const beforeLast = this.stack[i - 1]; + if (beforeLast.size >= map.size) break; + this.stack[i] = beforeLast; + this.stack[i - 1] = map; + } + } else { + for (const [key, value] of map) { + this.map.set(key, value); + } + } + } + + /** + * @param {K} item the key of the element to add + * @param {V} value the value of the element to add + * @returns {void} + */ + set(item, value) { + this.map.set(item, value); + } + + /** + * @param {K} item the item to delete + * @returns {void} + */ + delete(item) { + throw new Error("Items can't be deleted from a StackedCacheMap"); + } + + /** + * @param {K} item the item to test + * @returns {boolean} true if the item exists in this set + */ + has(item) { + throw new Error( + "Checking StackedCacheMap.has before reading is inefficient, use StackedCacheMap.get and check for undefined" + ); + } + + /** + * @param {K} item the key of the element to return + * @returns {V | undefined} the value of the element + */ + get(item) { + for (const map of this.stack) { + const value = map.get(item); + if (value !== undefined) return value; + } + return this.map.get(item); + } + + clear() { + this.stack.length = 0; + this.map.clear(); + } + + /** + * @returns {number} size of the map + */ + get size() { + let size = this.map.size; + for (const map of this.stack) { + size += map.size; + } + return size; + } + + /** + * @returns {Iterator<[K, V]>} iterator + */ + [Symbol.iterator]() { + const iterators = this.stack.map(map => map[Symbol.iterator]()); + let current = this.map[Symbol.iterator](); + return { + next() { + let result = current.next(); + while (result.done && iterators.length > 0) { + current = /** @type {IterableIterator<[K, V]>} */ (iterators.pop()); + result = current.next(); + } + return result; + } + }; + } +} + +module.exports = StackedCacheMap; diff --git a/webpack-lib/lib/util/StackedMap.js b/webpack-lib/lib/util/StackedMap.js new file mode 100644 index 00000000000..0f4011d0ce7 --- /dev/null +++ b/webpack-lib/lib/util/StackedMap.js @@ -0,0 +1,164 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const TOMBSTONE = Symbol("tombstone"); +const UNDEFINED_MARKER = Symbol("undefined"); + +/** + * @template T + * @typedef {T | undefined} Cell + */ + +/** + * @template T + * @typedef {T | typeof TOMBSTONE | typeof UNDEFINED_MARKER} InternalCell + */ + +/** + * @template K + * @template V + * @param {[K, InternalCell]} pair the internal cell + * @returns {[K, Cell]} its “safe” representation + */ +const extractPair = pair => { + const key = pair[0]; + const val = pair[1]; + if (val === UNDEFINED_MARKER || val === TOMBSTONE) { + return [key, undefined]; + } + return /** @type {[K, Cell]} */ (pair); +}; + +/** + * @template K + * @template V + */ +class StackedMap { + /** + * @param {Map>[]=} parentStack an optional parent + */ + constructor(parentStack) { + /** @type {Map>} */ + this.map = new Map(); + /** @type {Map>[]} */ + this.stack = parentStack === undefined ? [] : parentStack.slice(); + this.stack.push(this.map); + } + + /** + * @param {K} item the key of the element to add + * @param {V} value the value of the element to add + * @returns {void} + */ + set(item, value) { + this.map.set(item, value === undefined ? UNDEFINED_MARKER : value); + } + + /** + * @param {K} item the item to delete + * @returns {void} + */ + delete(item) { + if (this.stack.length > 1) { + this.map.set(item, TOMBSTONE); + } else { + this.map.delete(item); + } + } + + /** + * @param {K} item the item to test + * @returns {boolean} true if the item exists in this set + */ + has(item) { + const topValue = this.map.get(item); + if (topValue !== undefined) { + return topValue !== TOMBSTONE; + } + if (this.stack.length > 1) { + for (let i = this.stack.length - 2; i >= 0; i--) { + const value = this.stack[i].get(item); + if (value !== undefined) { + this.map.set(item, value); + return value !== TOMBSTONE; + } + } + this.map.set(item, TOMBSTONE); + } + return false; + } + + /** + * @param {K} item the key of the element to return + * @returns {Cell} the value of the element + */ + get(item) { + const topValue = this.map.get(item); + if (topValue !== undefined) { + return topValue === TOMBSTONE || topValue === UNDEFINED_MARKER + ? undefined + : topValue; + } + if (this.stack.length > 1) { + for (let i = this.stack.length - 2; i >= 0; i--) { + const value = this.stack[i].get(item); + if (value !== undefined) { + this.map.set(item, value); + return value === TOMBSTONE || value === UNDEFINED_MARKER + ? undefined + : value; + } + } + this.map.set(item, TOMBSTONE); + } + } + + _compress() { + if (this.stack.length === 1) return; + this.map = new Map(); + for (const data of this.stack) { + for (const pair of data) { + if (pair[1] === TOMBSTONE) { + this.map.delete(pair[0]); + } else { + this.map.set(pair[0], pair[1]); + } + } + } + this.stack = [this.map]; + } + + asArray() { + this._compress(); + return Array.from(this.map.keys()); + } + + asSet() { + this._compress(); + return new Set(this.map.keys()); + } + + asPairArray() { + this._compress(); + return Array.from(this.map.entries(), extractPair); + } + + asMap() { + return new Map(this.asPairArray()); + } + + get size() { + this._compress(); + return this.map.size; + } + + createChild() { + return new StackedMap(this.stack); + } +} + +module.exports = StackedMap; diff --git a/webpack-lib/lib/util/StringXor.js b/webpack-lib/lib/util/StringXor.js new file mode 100644 index 00000000000..ea5c8f83544 --- /dev/null +++ b/webpack-lib/lib/util/StringXor.js @@ -0,0 +1,101 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../util/Hash")} Hash */ + +/** + * StringXor class provides methods for performing + * [XOR operations](https://en.wikipedia.org/wiki/Exclusive_or) on strings. In this context + * we operating on the character codes of two strings, which are represented as + * [Buffer](https://nodejs.org/api/buffer.html) objects. + * + * We use [StringXor in webpack](https://github.com/webpack/webpack/commit/41a8e2ea483a544c4ccd3e6217bdfb80daffca39) + * to create a hash of the current state of the compilation. By XOR'ing the Module hashes, it + * doesn't matter if the Module hashes are sorted or not. This is useful because it allows us to avoid sorting the + * Module hashes. + * @example + * ```js + * const xor = new StringXor(); + * xor.add('hello'); + * xor.add('world'); + * console.log(xor.toString()); + * ``` + * @example + * ```js + * const xor = new StringXor(); + * xor.add('foo'); + * xor.add('bar'); + * const hash = createHash('sha256'); + * hash.update(xor.toString()); + * console.log(hash.digest('hex')); + * ``` + */ +class StringXor { + constructor() { + /** @type {Buffer|undefined} */ + this._value = undefined; + } + + /** + * Adds a string to the current StringXor object. + * @param {string} str string + * @returns {void} + */ + add(str) { + const len = str.length; + const value = this._value; + if (value === undefined) { + /** + * We are choosing to use Buffer.allocUnsafe() because it is often faster than Buffer.alloc() because + * it allocates a new buffer of the specified size without initializing the memory. + */ + const newValue = (this._value = Buffer.allocUnsafe(len)); + for (let i = 0; i < len; i++) { + newValue[i] = str.charCodeAt(i); + } + return; + } + const valueLen = value.length; + if (valueLen < len) { + const newValue = (this._value = Buffer.allocUnsafe(len)); + let i; + for (i = 0; i < valueLen; i++) { + newValue[i] = value[i] ^ str.charCodeAt(i); + } + for (; i < len; i++) { + newValue[i] = str.charCodeAt(i); + } + } else { + for (let i = 0; i < len; i++) { + value[i] = value[i] ^ str.charCodeAt(i); + } + } + } + + /** + * Returns a string that represents the current state of the StringXor object. We chose to use "latin1" encoding + * here because "latin1" encoding is a single-byte encoding that can represent all characters in the + * [ISO-8859-1 character set](https://en.wikipedia.org/wiki/ISO/IEC_8859-1). This is useful when working + * with binary data that needs to be represented as a string. + * @returns {string} Returns a string that represents the current state of the StringXor object. + */ + toString() { + const value = this._value; + return value === undefined ? "" : value.toString("latin1"); + } + + /** + * Updates the hash with the current state of the StringXor object. + * @param {Hash} hash Hash instance + */ + updateHash(hash) { + const value = this._value; + if (value !== undefined) hash.update(value); + } +} + +module.exports = StringXor; diff --git a/webpack-lib/lib/util/TupleQueue.js b/webpack-lib/lib/util/TupleQueue.js new file mode 100644 index 00000000000..6cdd7ea9f2b --- /dev/null +++ b/webpack-lib/lib/util/TupleQueue.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const TupleSet = require("./TupleSet"); + +/** + * @template {any[]} T + */ +class TupleQueue { + /** + * @param {Iterable=} items The initial elements. + */ + constructor(items) { + /** + * @private + * @type {TupleSet} + */ + this._set = new TupleSet(items); + /** + * @private + * @type {Iterator} + */ + this._iterator = this._set[Symbol.iterator](); + } + + /** + * Returns the number of elements in this queue. + * @returns {number} The number of elements in this queue. + */ + get length() { + return this._set.size; + } + + /** + * Appends the specified element to this queue. + * @param {T} item The element to add. + * @returns {void} + */ + enqueue(...item) { + this._set.add(...item); + } + + /** + * Retrieves and removes the head of this queue. + * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. + */ + dequeue() { + const result = this._iterator.next(); + if (result.done) { + if (this._set.size > 0) { + this._iterator = this._set[Symbol.iterator](); + const value = this._iterator.next().value; + this._set.delete(...value); + return value; + } + return; + } + this._set.delete(...result.value); + return result.value; + } +} + +module.exports = TupleQueue; diff --git a/webpack-lib/lib/util/TupleSet.js b/webpack-lib/lib/util/TupleSet.js new file mode 100644 index 00000000000..803ae194ec7 --- /dev/null +++ b/webpack-lib/lib/util/TupleSet.js @@ -0,0 +1,160 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template {any[]} T + */ +class TupleSet { + /** + * @param {Iterable=} init init + */ + constructor(init) { + /** @type {Map} */ + this._map = new Map(); + this.size = 0; + if (init) { + for (const tuple of init) { + this.add(...tuple); + } + } + } + + /** + * @param {T} args tuple + * @returns {void} + */ + add(...args) { + let map = this._map; + for (let i = 0; i < args.length - 2; i++) { + const arg = args[i]; + const innerMap = map.get(arg); + if (innerMap === undefined) { + map.set(arg, (map = new Map())); + } else { + map = innerMap; + } + } + + const beforeLast = args[args.length - 2]; + let set = map.get(beforeLast); + if (set === undefined) { + map.set(beforeLast, (set = new Set())); + } + + const last = args[args.length - 1]; + this.size -= set.size; + set.add(last); + this.size += set.size; + } + + /** + * @param {T} args tuple + * @returns {boolean} true, if the tuple is in the Set + */ + has(...args) { + let map = this._map; + for (let i = 0; i < args.length - 2; i++) { + const arg = args[i]; + map = map.get(arg); + if (map === undefined) { + return false; + } + } + + const beforeLast = args[args.length - 2]; + const set = map.get(beforeLast); + if (set === undefined) { + return false; + } + + const last = args[args.length - 1]; + return set.has(last); + } + + /** + * @param {T} args tuple + * @returns {void} + */ + delete(...args) { + let map = this._map; + for (let i = 0; i < args.length - 2; i++) { + const arg = args[i]; + map = map.get(arg); + if (map === undefined) { + return; + } + } + + const beforeLast = args[args.length - 2]; + const set = map.get(beforeLast); + if (set === undefined) { + return; + } + + const last = args[args.length - 1]; + this.size -= set.size; + set.delete(last); + this.size += set.size; + } + + /** + * @returns {Iterator} iterator + */ + [Symbol.iterator]() { + /** @type {TODO[]} */ + const iteratorStack = []; + /** @type {T[]} */ + const tuple = []; + /** @type {Iterator | undefined} */ + let currentSetIterator; + + /** + * @param {TODO} it iterator + * @returns {boolean} result + */ + const next = it => { + const result = it.next(); + if (result.done) { + if (iteratorStack.length === 0) return false; + tuple.pop(); + return next(iteratorStack.pop()); + } + const [key, value] = result.value; + iteratorStack.push(it); + tuple.push(key); + if (value instanceof Set) { + currentSetIterator = value[Symbol.iterator](); + return true; + } + return next(value[Symbol.iterator]()); + }; + + next(this._map[Symbol.iterator]()); + + return { + next() { + while (currentSetIterator) { + const result = currentSetIterator.next(); + if (result.done) { + tuple.pop(); + if (!next(iteratorStack.pop())) { + currentSetIterator = undefined; + } + } else { + return { + done: false, + value: /** @type {T} */ (tuple.concat(result.value)) + }; + } + } + return { done: true, value: undefined }; + } + }; + } +} + +module.exports = TupleSet; diff --git a/webpack-lib/lib/util/URLAbsoluteSpecifier.js b/webpack-lib/lib/util/URLAbsoluteSpecifier.js new file mode 100644 index 00000000000..f5cec7e4be0 --- /dev/null +++ b/webpack-lib/lib/util/URLAbsoluteSpecifier.js @@ -0,0 +1,87 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +/** @typedef {import("./fs").InputFileSystem} InputFileSystem */ +/** @typedef {(error: Error|null, result?: Buffer) => void} ErrorFirstCallback */ + +const backSlashCharCode = "\\".charCodeAt(0); +const slashCharCode = "/".charCodeAt(0); +const aLowerCaseCharCode = "a".charCodeAt(0); +const zLowerCaseCharCode = "z".charCodeAt(0); +const aUpperCaseCharCode = "A".charCodeAt(0); +const zUpperCaseCharCode = "Z".charCodeAt(0); +const _0CharCode = "0".charCodeAt(0); +const _9CharCode = "9".charCodeAt(0); +const plusCharCode = "+".charCodeAt(0); +const hyphenCharCode = "-".charCodeAt(0); +const colonCharCode = ":".charCodeAt(0); +const hashCharCode = "#".charCodeAt(0); +const queryCharCode = "?".charCodeAt(0); +/** + * Get scheme if specifier is an absolute URL specifier + * e.g. Absolute specifiers like 'file:///user/webpack/index.js' + * https://tools.ietf.org/html/rfc3986#section-3.1 + * @param {string} specifier specifier + * @returns {string|undefined} scheme if absolute URL specifier provided + */ +function getScheme(specifier) { + const start = specifier.charCodeAt(0); + + // First char maybe only a letter + if ( + (start < aLowerCaseCharCode || start > zLowerCaseCharCode) && + (start < aUpperCaseCharCode || start > zUpperCaseCharCode) + ) { + return; + } + + let i = 1; + let ch = specifier.charCodeAt(i); + + while ( + (ch >= aLowerCaseCharCode && ch <= zLowerCaseCharCode) || + (ch >= aUpperCaseCharCode && ch <= zUpperCaseCharCode) || + (ch >= _0CharCode && ch <= _9CharCode) || + ch === plusCharCode || + ch === hyphenCharCode + ) { + if (++i === specifier.length) return; + ch = specifier.charCodeAt(i); + } + + // Scheme must end with colon + if (ch !== colonCharCode) return; + + // Check for Windows absolute path + // https://url.spec.whatwg.org/#url-miscellaneous + if (i === 1) { + const nextChar = i + 1 < specifier.length ? specifier.charCodeAt(i + 1) : 0; + if ( + nextChar === 0 || + nextChar === backSlashCharCode || + nextChar === slashCharCode || + nextChar === hashCharCode || + nextChar === queryCharCode + ) { + return; + } + } + + return specifier.slice(0, i).toLowerCase(); +} + +/** + * @param {string} specifier specifier + * @returns {string | null | undefined} protocol if absolute URL specifier provided + */ +function getProtocol(specifier) { + const scheme = getScheme(specifier); + return scheme === undefined ? undefined : `${scheme}:`; +} + +module.exports.getScheme = getScheme; +module.exports.getProtocol = getProtocol; diff --git a/webpack-lib/lib/util/WeakTupleMap.js b/webpack-lib/lib/util/WeakTupleMap.js new file mode 100644 index 00000000000..ac64e8695df --- /dev/null +++ b/webpack-lib/lib/util/WeakTupleMap.js @@ -0,0 +1,213 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template {any[]} T + * @template V + * @typedef {Map>} M + */ +/** + * @template {any[]} T + * @template V + * @typedef {WeakMap>} W + */ + +/** + * @param {any} thing thing + * @returns {boolean} true if is weak + */ +const isWeakKey = thing => typeof thing === "object" && thing !== null; + +/** + * @template {any[]} T + * @template V + */ +class WeakTupleMap { + constructor() { + /** @private */ + this.f = 0; + /** + * @private + * @type {any} + */ + this.v = undefined; + /** + * @private + * @type {M | undefined} + */ + this.m = undefined; + /** + * @private + * @type {W | undefined} + */ + this.w = undefined; + } + + /** + * @param {[...T, V]} args tuple + * @returns {void} + */ + set(...args) { + /** @type {WeakTupleMap} */ + let node = this; + for (let i = 0; i < args.length - 1; i++) { + node = node._get(args[i]); + } + node._setValue(args[args.length - 1]); + } + + /** + * @param {T} args tuple + * @returns {boolean} true, if the tuple is in the Set + */ + has(...args) { + /** @type {WeakTupleMap | undefined} */ + let node = this; + for (let i = 0; i < args.length; i++) { + node = node._peek(args[i]); + if (node === undefined) return false; + } + return node._hasValue(); + } + + /** + * @param {T} args tuple + * @returns {V | undefined} the value + */ + get(...args) { + /** @type {WeakTupleMap | undefined} */ + let node = this; + for (let i = 0; i < args.length; i++) { + node = node._peek(args[i]); + if (node === undefined) return; + } + return node._getValue(); + } + + /** + * @param {[...T, function(): V]} args tuple + * @returns {V} the value + */ + provide(...args) { + /** @type {WeakTupleMap} */ + let node = this; + for (let i = 0; i < args.length - 1; i++) { + node = node._get(args[i]); + } + if (node._hasValue()) return node._getValue(); + const fn = args[args.length - 1]; + const newValue = fn(...args.slice(0, -1)); + node._setValue(newValue); + return newValue; + } + + /** + * @param {T} args tuple + * @returns {void} + */ + delete(...args) { + /** @type {WeakTupleMap | undefined} */ + let node = this; + for (let i = 0; i < args.length; i++) { + node = node._peek(args[i]); + if (node === undefined) return; + } + node._deleteValue(); + } + + /** + * @returns {void} + */ + clear() { + this.f = 0; + this.v = undefined; + this.w = undefined; + this.m = undefined; + } + + _getValue() { + return this.v; + } + + _hasValue() { + return (this.f & 1) === 1; + } + + /** + * @param {any} v value + * @private + */ + _setValue(v) { + this.f |= 1; + this.v = v; + } + + _deleteValue() { + this.f &= 6; + this.v = undefined; + } + + /** + * @param {any} thing thing + * @returns {WeakTupleMap | undefined} thing + * @private + */ + _peek(thing) { + if (isWeakKey(thing)) { + if ((this.f & 4) !== 4) return; + return /** @type {W} */ (this.w).get(thing); + } + if ((this.f & 2) !== 2) return; + return /** @type {M} */ (this.m).get(thing); + } + + /** + * @private + * @param {any} thing thing + * @returns {WeakTupleMap} value + */ + _get(thing) { + if (isWeakKey(thing)) { + if ((this.f & 4) !== 4) { + const newMap = new WeakMap(); + this.f |= 4; + const newNode = new WeakTupleMap(); + (this.w = newMap).set(thing, newNode); + return newNode; + } + const entry = + /** @type {W} */ + (this.w).get(thing); + if (entry !== undefined) { + return entry; + } + const newNode = new WeakTupleMap(); + /** @type {W} */ + (this.w).set(thing, newNode); + return newNode; + } + if ((this.f & 2) !== 2) { + const newMap = new Map(); + this.f |= 2; + const newNode = new WeakTupleMap(); + (this.m = newMap).set(thing, newNode); + return newNode; + } + const entry = + /** @type {M} */ + (this.m).get(thing); + if (entry !== undefined) { + return entry; + } + const newNode = new WeakTupleMap(); + /** @type {M} */ + (this.m).set(thing, newNode); + return newNode; + } +} + +module.exports = WeakTupleMap; diff --git a/webpack-lib/lib/util/binarySearchBounds.js b/webpack-lib/lib/util/binarySearchBounds.js new file mode 100644 index 00000000000..c61623c1bf5 --- /dev/null +++ b/webpack-lib/lib/util/binarySearchBounds.js @@ -0,0 +1,128 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Mikola Lysenko @mikolalysenko +*/ + +"use strict"; + +/* cspell:disable-next-line */ +// Refactor: Peter Somogyvari @petermetz + +/** @typedef {">=" | "<=" | "<" | ">" | "-" } BinarySearchPredicate */ +/** @typedef {"GE" | "GT" | "LT" | "LE" | "EQ" } SearchPredicateSuffix */ + +/** + * Helper function for compiling binary search functions. + * + * The generated code uses a while loop to repeatedly divide the search interval + * in half until the desired element is found, or the search interval is empty. + * + * The following is an example of a generated function for calling `compileSearch("P", "c(x,y)<=0", true, ["y", "c"], false)`: + * + * ```js + * function P(a,l,h,y,c){var i=l-1;while(l<=h){var m=(l+h)>>>1,x=a[m];if(c(x,y)<=0){i=m;l=m+1}else{h=m-1}}return i}; + * ``` + * @param {string} funcName The name of the function to be compiled. + * @param {string} predicate The predicate / comparison operator to be used in the binary search. + * @param {boolean} reversed Whether the search should be reversed. + * @param {string[]} extraArgs Extra arguments to be passed to the function. + * @param {boolean=} earlyOut Whether the search should return as soon as a match is found. + * @returns {string} The compiled binary search function. + */ +const compileSearch = (funcName, predicate, reversed, extraArgs, earlyOut) => { + const code = [ + "function ", + funcName, + "(a,l,h,", + extraArgs.join(","), + "){", + earlyOut ? "" : "var i=", + reversed ? "l-1" : "h+1", + ";while(l<=h){var m=(l+h)>>>1,x=a[m]" + ]; + + if (earlyOut) { + if (!predicate.includes("c")) { + code.push(";if(x===y){return m}else if(x<=y){"); + } else { + code.push(";var p=c(x,y);if(p===0){return m}else if(p<=0){"); + } + } else { + code.push(";if(", predicate, "){i=m;"); + } + if (reversed) { + code.push("l=m+1}else{h=m-1}"); + } else { + code.push("h=m-1}else{l=m+1}"); + } + code.push("}"); + if (earlyOut) { + code.push("return -1};"); + } else { + code.push("return i};"); + } + return code.join(""); +}; + +/** + * This helper functions generate code for two binary search functions: + * A(): Performs a binary search on an array using the comparison operator specified. + * P(): Performs a binary search on an array using a _custom comparison function_ + * `c(x,y)` **and** comparison operator specified by `predicate`. + * @param {BinarySearchPredicate} predicate The predicate / comparison operator to be used in the binary search. + * @param {boolean} reversed Whether the search should be reversed. + * @param {SearchPredicateSuffix} suffix The suffix to be used in the function name. + * @param {boolean=} earlyOut Whether the search should return as soon as a match is found. + * @returns {Function} The compiled binary search function. + */ +const compileBoundsSearch = (predicate, reversed, suffix, earlyOut) => { + const arg1 = compileSearch("A", `x${predicate}y`, reversed, ["y"], earlyOut); + + const arg2 = compileSearch( + "P", + `c(x,y)${predicate}0`, + reversed, + ["y", "c"], + earlyOut + ); + + const fnHeader = "function dispatchBinarySearch"; + + const fnBody = + // eslint-disable-next-line no-multi-str + "(a,y,c,l,h){\ +if(typeof(c)==='function'){\ +return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\ +}else{\ +return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\ +}}\ +return dispatchBinarySearch"; + + const fnArgList = [arg1, arg2, fnHeader, suffix, fnBody, suffix]; + const fnSource = fnArgList.join(""); + // eslint-disable-next-line no-new-func + const result = new Function(fnSource); + return result(); +}; + +/** + * These functions are used to perform binary searches on arrays. + * @example + * ```js + * const { gt, le} = require("./binarySearchBounds"); + * const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + * + * // Find the index of the first element greater than 5 + * const index1 = gt(arr, 5); // index1 === 3 + * + * // Find the index of the first element less than or equal to 5 + * const index2 = le(arr, 5); // index2 === 4 + * ``` + */ +module.exports = { + ge: compileBoundsSearch(">=", false, "GE"), + gt: compileBoundsSearch(">", false, "GT"), + lt: compileBoundsSearch("<", true, "LT"), + le: compileBoundsSearch("<=", true, "LE"), + eq: compileBoundsSearch("-", true, "EQ", true) +}; diff --git a/webpack-lib/lib/util/chainedImports.js b/webpack-lib/lib/util/chainedImports.js new file mode 100644 index 00000000000..295233b7d1c --- /dev/null +++ b/webpack-lib/lib/util/chainedImports.js @@ -0,0 +1,97 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ + +/** + * @summary Get the subset of ids and their corresponding range in an id chain that should be re-rendered by webpack. + * Only those in the chain that are actually referring to namespaces or imports should be re-rendered. + * Deeper member accessors on the imported object should not be re-rendered. If deeper member accessors are re-rendered, + * there is a potential loss of meaning with rendering a quoted accessor as an unquoted accessor, or vice versa, + * because minifiers treat quoted accessors differently. e.g. import { a } from "./module"; a["b"] vs a.b + * @param {string[]} untrimmedIds chained ids + * @param {Range} untrimmedRange range encompassing allIds + * @param {Range[] | undefined} ranges cumulative range of ids for each of allIds + * @param {ModuleGraph} moduleGraph moduleGraph + * @param {Dependency} dependency dependency + * @returns {{trimmedIds: string[], trimmedRange: Range}} computed trimmed ids and cumulative range of those ids + */ +module.exports.getTrimmedIdsAndRange = ( + untrimmedIds, + untrimmedRange, + ranges, + moduleGraph, + dependency +) => { + let trimmedIds = trimIdsToThoseImported( + untrimmedIds, + moduleGraph, + dependency + ); + let trimmedRange = untrimmedRange; + if (trimmedIds.length !== untrimmedIds.length) { + // The array returned from dep.idRanges is right-aligned with the array returned from dep.names. + // Meaning, the two arrays may not always have the same number of elements, but the last element of + // dep.idRanges corresponds to [the expression fragment to the left of] the last element of dep.names. + // Use this to find the correct replacement range based on the number of ids that were trimmed. + const idx = + ranges === undefined + ? -1 /* trigger failure case below */ + : ranges.length + (trimmedIds.length - untrimmedIds.length); + if (idx < 0 || idx >= /** @type {Range[]} */ (ranges).length) { + // cspell:ignore minifiers + // Should not happen but we can't throw an error here because of backward compatibility with + // external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers. + trimmedIds = untrimmedIds; + // TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead. + // throw new Error("Missing range starts data for id replacement trimming."); + } else { + trimmedRange = /** @type {Range[]} */ (ranges)[idx]; + } + } + + return { trimmedIds, trimmedRange }; +}; + +/** + * @summary Determine which IDs in the id chain are actually referring to namespaces or imports, + * and which are deeper member accessors on the imported object. + * @param {string[]} ids untrimmed ids + * @param {ModuleGraph} moduleGraph moduleGraph + * @param {Dependency} dependency dependency + * @returns {string[]} trimmed ids + */ +function trimIdsToThoseImported(ids, moduleGraph, dependency) { + /** @type {string[]} */ + let trimmedIds = []; + let currentExportsInfo = moduleGraph.getExportsInfo( + /** @type {Module} */ (moduleGraph.getModule(dependency)) + ); + for (let i = 0; i < ids.length; i++) { + if (i === 0 && ids[i] === "default") { + continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo + } + const exportInfo = currentExportsInfo.getExportInfo(ids[i]); + if (exportInfo.provided === false) { + // json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided + trimmedIds = ids.slice(0, i); + break; + } + const nestedInfo = exportInfo.getNestedExportsInfo(); + if (!nestedInfo) { + // once all nested exports are traversed, the next item is the actual import so stop there + trimmedIds = ids.slice(0, i + 1); + break; + } + currentExportsInfo = nestedInfo; + } + // Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule") + return trimmedIds.length ? trimmedIds : ids; +} diff --git a/webpack-lib/lib/util/cleverMerge.js b/webpack-lib/lib/util/cleverMerge.js new file mode 100644 index 00000000000..14852011b44 --- /dev/null +++ b/webpack-lib/lib/util/cleverMerge.js @@ -0,0 +1,607 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @type {WeakMap>} */ +const mergeCache = new WeakMap(); +/** @type {WeakMap>>} */ +const setPropertyCache = new WeakMap(); +const DELETE = Symbol("DELETE"); +const DYNAMIC_INFO = Symbol("cleverMerge dynamic info"); + +/** + * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again. + * @template T + * @template O + * @example + * // performs cleverMerge(first, second), stores the result in WeakMap and returns result + * cachedCleverMerge({a: 1}, {a: 2}) + * {a: 2} + * // when same arguments passed, gets the result from WeakMap and returns it. + * cachedCleverMerge({a: 1}, {a: 2}) + * {a: 2} + * @param {T | null | undefined} first first object + * @param {O | null | undefined} second second object + * @returns {T & O | T | O} merged object of first and second object + */ +const cachedCleverMerge = (first, second) => { + if (second === undefined) return /** @type {T} */ (first); + if (first === undefined) return /** @type {O} */ (second); + if (typeof second !== "object" || second === null) + return /** @type {O} */ (second); + if (typeof first !== "object" || first === null) + return /** @type {T} */ (first); + + let innerCache = mergeCache.get(first); + if (innerCache === undefined) { + innerCache = new WeakMap(); + mergeCache.set(first, innerCache); + } + const prevMerge = /** @type {T & O} */ (innerCache.get(second)); + if (prevMerge !== undefined) return prevMerge; + const newMerge = _cleverMerge(first, second, true); + innerCache.set(second, newMerge); + return /** @type {T & O} */ (newMerge); +}; + +/** + * @template T + * @param {Partial} obj object + * @param {string} property property + * @param {string|number|boolean} value assignment value + * @returns {T} new object + */ +const cachedSetProperty = (obj, property, value) => { + let mapByProperty = setPropertyCache.get(obj); + + if (mapByProperty === undefined) { + mapByProperty = new Map(); + setPropertyCache.set(obj, mapByProperty); + } + + let mapByValue = mapByProperty.get(property); + + if (mapByValue === undefined) { + mapByValue = new Map(); + mapByProperty.set(property, mapByValue); + } + + let result = mapByValue.get(value); + + if (result) return /** @type {T} */ (result); + + result = { + ...obj, + [property]: value + }; + mapByValue.set(value, result); + + return /** @type {T} */ (result); +}; + +/** @typedef {Map} ByValues */ + +/** + * @typedef {object} ObjectParsedPropertyEntry + * @property {any | undefined} base base value + * @property {string | undefined} byProperty the name of the selector property + * @property {ByValues} byValues value depending on selector property, merged with base + */ + +/** + * @typedef {object} ParsedObject + * @property {Map} static static properties (key is property name) + * @property {{ byProperty: string, fn: Function } | undefined} dynamic dynamic part + */ + +/** @type {WeakMap} */ +const parseCache = new WeakMap(); + +/** + * @param {object} obj the object + * @returns {ParsedObject} parsed object + */ +const cachedParseObject = obj => { + const entry = parseCache.get(obj); + if (entry !== undefined) return entry; + const result = parseObject(obj); + parseCache.set(obj, result); + return result; +}; + +/** + * @template {object} T + * @param {T} obj the object + * @returns {ParsedObject} parsed object + */ +const parseObject = obj => { + const info = new Map(); + let dynamicInfo; + /** + * @param {string} p path + * @returns {Partial} object parsed property entry + */ + const getInfo = p => { + const entry = info.get(p); + if (entry !== undefined) return entry; + const newEntry = { + base: undefined, + byProperty: undefined, + byValues: undefined + }; + info.set(p, newEntry); + return newEntry; + }; + for (const key of Object.keys(obj)) { + if (key.startsWith("by")) { + const byProperty = /** @type {keyof T} */ (key); + const byObj = /** @type {object} */ (obj[byProperty]); + if (typeof byObj === "object") { + for (const byValue of Object.keys(byObj)) { + const obj = byObj[/** @type {keyof (keyof T)} */ (byValue)]; + for (const key of Object.keys(obj)) { + const entry = getInfo(key); + if (entry.byProperty === undefined) { + entry.byProperty = /** @type {string} */ (byProperty); + entry.byValues = new Map(); + } else if (entry.byProperty !== byProperty) { + throw new Error( + `${/** @type {string} */ (byProperty)} and ${entry.byProperty} for a single property is not supported` + ); + } + /** @type {ByValues} */ + (entry.byValues).set( + byValue, + obj[/** @type {keyof (keyof T)} */ (key)] + ); + if (byValue === "default") { + for (const otherByValue of Object.keys(byObj)) { + if ( + !(/** @type {ByValues} */ (entry.byValues).has(otherByValue)) + ) + /** @type {ByValues} */ + (entry.byValues).set(otherByValue, undefined); + } + } + } + } + } else if (typeof byObj === "function") { + if (dynamicInfo === undefined) { + dynamicInfo = { + byProperty: key, + fn: byObj + }; + } else { + throw new Error( + `${key} and ${dynamicInfo.byProperty} when both are functions is not supported` + ); + } + } else { + const entry = getInfo(key); + entry.base = obj[/** @type {keyof T} */ (key)]; + } + } else { + const entry = getInfo(key); + entry.base = obj[/** @type {keyof T} */ (key)]; + } + } + return { + static: info, + dynamic: dynamicInfo + }; +}; + +/** + * @template {object} T + * @param {Map} info static properties (key is property name) + * @param {{ byProperty: string, fn: Function } | undefined} dynamicInfo dynamic part + * @returns {T} the object + */ +const serializeObject = (info, dynamicInfo) => { + const obj = /** @type {T} */ ({}); + // Setup byProperty structure + for (const entry of info.values()) { + if (entry.byProperty !== undefined) { + const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); + for (const byValue of entry.byValues.keys()) { + byObj[byValue] = byObj[byValue] || {}; + } + } + } + for (const [key, entry] of info) { + if (entry.base !== undefined) { + obj[/** @type {keyof T} */ (key)] = entry.base; + } + // Fill byProperty structure + if (entry.byProperty !== undefined) { + const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); + for (const byValue of Object.keys(byObj)) { + const value = getFromByValues(entry.byValues, byValue); + if (value !== undefined) byObj[byValue][key] = value; + } + } + } + if (dynamicInfo !== undefined) { + obj[dynamicInfo.byProperty] = dynamicInfo.fn; + } + return obj; +}; + +const VALUE_TYPE_UNDEFINED = 0; +const VALUE_TYPE_ATOM = 1; +const VALUE_TYPE_ARRAY_EXTEND = 2; +const VALUE_TYPE_OBJECT = 3; +const VALUE_TYPE_DELETE = 4; + +/** + * @param {any} value a single value + * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type + */ +const getValueType = value => { + if (value === undefined) { + return VALUE_TYPE_UNDEFINED; + } else if (value === DELETE) { + return VALUE_TYPE_DELETE; + } else if (Array.isArray(value)) { + if (value.includes("...")) return VALUE_TYPE_ARRAY_EXTEND; + return VALUE_TYPE_ATOM; + } else if ( + typeof value === "object" && + value !== null && + (!value.constructor || value.constructor === Object) + ) { + return VALUE_TYPE_OBJECT; + } + return VALUE_TYPE_ATOM; +}; + +/** + * Merges two objects. Objects are deeply clever merged. + * Arrays might reference the old value with "...". + * Non-object values take preference over object values. + * @template T + * @template O + * @param {T} first first object + * @param {O} second second object + * @returns {T & O | T | O} merged object of first and second object + */ +const cleverMerge = (first, second) => { + if (second === undefined) return first; + if (first === undefined) return second; + if (typeof second !== "object" || second === null) return second; + if (typeof first !== "object" || first === null) return first; + + return /** @type {T & O} */ (_cleverMerge(first, second, false)); +}; + +/** + * Merges two objects. Objects are deeply clever merged. + * @param {object} first first object + * @param {object} second second object + * @param {boolean} internalCaching should parsing of objects and nested merges be cached + * @returns {object} merged object of first and second object + */ +const _cleverMerge = (first, second, internalCaching = false) => { + const firstObject = internalCaching + ? cachedParseObject(first) + : parseObject(first); + const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject; + + // If the first argument has a dynamic part we modify the dynamic part to merge the second argument + if (firstDynamicInfo !== undefined) { + let { byProperty, fn } = firstDynamicInfo; + const fnInfo = fn[DYNAMIC_INFO]; + if (fnInfo) { + second = internalCaching + ? cachedCleverMerge(fnInfo[1], second) + : cleverMerge(fnInfo[1], second); + fn = fnInfo[0]; + } + const newFn = (...args) => { + const fnResult = fn(...args); + return internalCaching + ? cachedCleverMerge(fnResult, second) + : cleverMerge(fnResult, second); + }; + newFn[DYNAMIC_INFO] = [fn, second]; + return serializeObject(firstObject.static, { byProperty, fn: newFn }); + } + + // If the first part is static only, we merge the static parts and keep the dynamic part of the second argument + const secondObject = internalCaching + ? cachedParseObject(second) + : parseObject(second); + const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject; + /** @type {Map} */ + const resultInfo = new Map(); + for (const [key, firstEntry] of firstInfo) { + const secondEntry = secondInfo.get(key); + const entry = + secondEntry !== undefined + ? mergeEntries(firstEntry, secondEntry, internalCaching) + : firstEntry; + resultInfo.set(key, entry); + } + for (const [key, secondEntry] of secondInfo) { + if (!firstInfo.has(key)) { + resultInfo.set(key, secondEntry); + } + } + return serializeObject(resultInfo, secondDynamicInfo); +}; + +/** + * @param {ObjectParsedPropertyEntry} firstEntry a + * @param {ObjectParsedPropertyEntry} secondEntry b + * @param {boolean} internalCaching should parsing of objects and nested merges be cached + * @returns {ObjectParsedPropertyEntry} new entry + */ +const mergeEntries = (firstEntry, secondEntry, internalCaching) => { + switch (getValueType(secondEntry.base)) { + case VALUE_TYPE_ATOM: + case VALUE_TYPE_DELETE: + // No need to consider firstEntry at all + // second value override everything + // = second.base + second.byProperty + return secondEntry; + case VALUE_TYPE_UNDEFINED: + if (!firstEntry.byProperty) { + // = first.base + second.byProperty + return { + base: firstEntry.base, + byProperty: secondEntry.byProperty, + byValues: secondEntry.byValues + }; + } else if (firstEntry.byProperty !== secondEntry.byProperty) { + throw new Error( + `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` + ); + } else { + // = first.base + (first.byProperty + second.byProperty) + // need to merge first and second byValues + const newByValues = new Map(firstEntry.byValues); + for (const [key, value] of secondEntry.byValues) { + const firstValue = getFromByValues(firstEntry.byValues, key); + newByValues.set( + key, + mergeSingleValue(firstValue, value, internalCaching) + ); + } + return { + base: firstEntry.base, + byProperty: firstEntry.byProperty, + byValues: newByValues + }; + } + default: { + if (!firstEntry.byProperty) { + // The simple case + // = (first.base + second.base) + second.byProperty + return { + base: mergeSingleValue( + firstEntry.base, + secondEntry.base, + internalCaching + ), + byProperty: secondEntry.byProperty, + byValues: secondEntry.byValues + }; + } + let newBase; + const intermediateByValues = new Map(firstEntry.byValues); + for (const [key, value] of intermediateByValues) { + intermediateByValues.set( + key, + mergeSingleValue(value, secondEntry.base, internalCaching) + ); + } + if ( + Array.from(firstEntry.byValues.values()).every(value => { + const type = getValueType(value); + return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE; + }) + ) { + // = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty) + newBase = mergeSingleValue( + firstEntry.base, + secondEntry.base, + internalCaching + ); + } else { + // = first.base + ((first.byProperty (+default) + second.base) + second.byProperty) + newBase = firstEntry.base; + if (!intermediateByValues.has("default")) + intermediateByValues.set("default", secondEntry.base); + } + if (!secondEntry.byProperty) { + // = first.base + (first.byProperty + second.base) + return { + base: newBase, + byProperty: firstEntry.byProperty, + byValues: intermediateByValues + }; + } else if (firstEntry.byProperty !== secondEntry.byProperty) { + throw new Error( + `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` + ); + } + const newByValues = new Map(intermediateByValues); + for (const [key, value] of secondEntry.byValues) { + const firstValue = getFromByValues(intermediateByValues, key); + newByValues.set( + key, + mergeSingleValue(firstValue, value, internalCaching) + ); + } + return { + base: newBase, + byProperty: firstEntry.byProperty, + byValues: newByValues + }; + } + } +}; + +/** + * @param {Map} byValues all values + * @param {string} key value of the selector + * @returns {any | undefined} value + */ +const getFromByValues = (byValues, key) => { + if (key !== "default" && byValues.has(key)) { + return byValues.get(key); + } + return byValues.get("default"); +}; + +/** + * @param {any} a value + * @param {any} b value + * @param {boolean} internalCaching should parsing of objects and nested merges be cached + * @returns {any} value + */ +const mergeSingleValue = (a, b, internalCaching) => { + const bType = getValueType(b); + const aType = getValueType(a); + switch (bType) { + case VALUE_TYPE_DELETE: + case VALUE_TYPE_ATOM: + return b; + case VALUE_TYPE_OBJECT: { + return aType !== VALUE_TYPE_OBJECT + ? b + : internalCaching + ? cachedCleverMerge(a, b) + : cleverMerge(a, b); + } + case VALUE_TYPE_UNDEFINED: + return a; + case VALUE_TYPE_ARRAY_EXTEND: + switch ( + aType !== VALUE_TYPE_ATOM + ? aType + : Array.isArray(a) + ? VALUE_TYPE_ARRAY_EXTEND + : VALUE_TYPE_OBJECT + ) { + case VALUE_TYPE_UNDEFINED: + return b; + case VALUE_TYPE_DELETE: + return /** @type {any[]} */ (b).filter(item => item !== "..."); + case VALUE_TYPE_ARRAY_EXTEND: { + const newArray = []; + for (const item of b) { + if (item === "...") { + for (const item of a) { + newArray.push(item); + } + } else { + newArray.push(item); + } + } + return newArray; + } + case VALUE_TYPE_OBJECT: + return /** @type {any[]} */ (b).map(item => + item === "..." ? a : item + ); + default: + throw new Error("Not implemented"); + } + default: + throw new Error("Not implemented"); + } +}; + +/** + * @template {object} T + * @param {T} obj the object + * @param {(keyof T)[]=} keysToKeepOriginalValue keys to keep original value + * @returns {T} the object without operations like "..." or DELETE + */ +const removeOperations = (obj, keysToKeepOriginalValue = []) => { + const newObj = /** @type {T} */ ({}); + for (const key of Object.keys(obj)) { + const value = obj[/** @type {keyof T} */ (key)]; + const type = getValueType(value); + if ( + type === VALUE_TYPE_OBJECT && + keysToKeepOriginalValue.includes(/** @type {keyof T} */ (key)) + ) { + newObj[/** @type {keyof T} */ (key)] = value; + continue; + } + switch (type) { + case VALUE_TYPE_UNDEFINED: + case VALUE_TYPE_DELETE: + break; + case VALUE_TYPE_OBJECT: + newObj[/** @type {keyof T} */ (key)] = + /** @type {T[keyof T]} */ + ( + removeOperations( + /** @type {TODO} */ (value), + keysToKeepOriginalValue + ) + ); + break; + case VALUE_TYPE_ARRAY_EXTEND: + newObj[/** @type {keyof T} */ (key)] = + /** @type {T[keyof T]} */ + ( + /** @type {any[]} */ + (value).filter(i => i !== "...") + ); + break; + default: + newObj[/** @type {keyof T} */ (key)] = value; + break; + } + } + return newObj; +}; + +/** + * @template T + * @template {string} P + * @param {T} obj the object + * @param {P} byProperty the by description + * @param {...any} values values + * @returns {Omit} object with merged byProperty + */ +const resolveByProperty = (obj, byProperty, ...values) => { + if (typeof obj !== "object" || obj === null || !(byProperty in obj)) { + return obj; + } + const { [byProperty]: _byValue, ..._remaining } = obj; + const remaining = /** @type {T} */ (_remaining); + const byValue = + /** @type {Record | function(...any[]): T} */ + (_byValue); + if (typeof byValue === "object") { + const key = values[0]; + if (key in byValue) { + return cachedCleverMerge(remaining, byValue[key]); + } else if ("default" in byValue) { + return cachedCleverMerge(remaining, byValue.default); + } + return remaining; + } else if (typeof byValue === "function") { + // eslint-disable-next-line prefer-spread + const result = byValue.apply(null, values); + return cachedCleverMerge( + remaining, + resolveByProperty(result, byProperty, ...values) + ); + } +}; + +module.exports.cachedSetProperty = cachedSetProperty; +module.exports.cachedCleverMerge = cachedCleverMerge; +module.exports.cleverMerge = cleverMerge; +module.exports.resolveByProperty = resolveByProperty; +module.exports.removeOperations = removeOperations; +module.exports.DELETE = DELETE; diff --git a/webpack-lib/lib/util/comparators.js b/webpack-lib/lib/util/comparators.js new file mode 100644 index 00000000000..8d228026e4f --- /dev/null +++ b/webpack-lib/lib/util/comparators.js @@ -0,0 +1,522 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { compareRuntime } = require("./runtime"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Chunk").ChunkId} ChunkId */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../ChunkGroup")} ChunkGroup */ +/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ + +/** + * @template T + * @typedef {function(T, T): -1|0|1} Comparator + */ +/** + * @template TArg + * @template T + * @typedef {function(TArg, T, T): -1|0|1} RawParameterizedComparator + */ +/** + * @template TArg + * @template T + * @typedef {function(TArg): Comparator} ParameterizedComparator + */ + +/** + * @template T + * @param {RawParameterizedComparator} fn comparator with argument + * @returns {ParameterizedComparator} comparator + */ +const createCachedParameterizedComparator = fn => { + /** @type {WeakMap>} */ + const map = new WeakMap(); + return arg => { + const cachedResult = map.get(arg); + if (cachedResult !== undefined) return cachedResult; + /** + * @param {T} a first item + * @param {T} b second item + * @returns {-1|0|1} compare result + */ + const result = fn.bind(null, arg); + map.set(arg, result); + return result; + }; +}; + +/** + * @param {Chunk} a chunk + * @param {Chunk} b chunk + * @returns {-1|0|1} compare result + */ +module.exports.compareChunksById = (a, b) => + compareIds(/** @type {ChunkId} */ (a.id), /** @type {ChunkId} */ (b.id)); + +/** + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +module.exports.compareModulesByIdentifier = (a, b) => + compareIds(a.identifier(), b.identifier()); + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +const compareModulesById = (chunkGraph, a, b) => + compareIds( + /** @type {ModuleId} */ (chunkGraph.getModuleId(a)), + /** @type {ModuleId} */ (chunkGraph.getModuleId(b)) + ); +/** @type {ParameterizedComparator} */ +module.exports.compareModulesById = + createCachedParameterizedComparator(compareModulesById); + +/** + * @param {number} a number + * @param {number} b number + * @returns {-1|0|1} compare result + */ +const compareNumbers = (a, b) => { + if (typeof a !== typeof b) { + return typeof a < typeof b ? -1 : 1; + } + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; +module.exports.compareNumbers = compareNumbers; + +/** + * @param {string} a string + * @param {string} b string + * @returns {-1|0|1} compare result + */ +const compareStringsNumeric = (a, b) => { + const aLength = a.length; + const bLength = b.length; + + let aChar = 0; + let bChar = 0; + + let aIsDigit = false; + let bIsDigit = false; + let i = 0; + let j = 0; + while (i < aLength && j < bLength) { + aChar = a.charCodeAt(i); + bChar = b.charCodeAt(j); + + aIsDigit = aChar >= 48 && aChar <= 57; + bIsDigit = bChar >= 48 && bChar <= 57; + + if (!aIsDigit && !bIsDigit) { + if (aChar < bChar) return -1; + if (aChar > bChar) return 1; + i++; + j++; + } else if (aIsDigit && !bIsDigit) { + // This segment of a is shorter than in b + return 1; + } else if (!aIsDigit && bIsDigit) { + // This segment of b is shorter than in a + return -1; + } else { + let aNumber = aChar - 48; + let bNumber = bChar - 48; + + while (++i < aLength) { + aChar = a.charCodeAt(i); + if (aChar < 48 || aChar > 57) break; + aNumber = aNumber * 10 + aChar - 48; + } + + while (++j < bLength) { + bChar = b.charCodeAt(j); + if (bChar < 48 || bChar > 57) break; + bNumber = bNumber * 10 + bChar - 48; + } + + if (aNumber < bNumber) return -1; + if (aNumber > bNumber) return 1; + } + } + + if (j < bLength) { + // a is shorter than b + bChar = b.charCodeAt(j); + bIsDigit = bChar >= 48 && bChar <= 57; + return bIsDigit ? -1 : 1; + } + if (i < aLength) { + // b is shorter than a + aChar = a.charCodeAt(i); + aIsDigit = aChar >= 48 && aChar <= 57; + return aIsDigit ? 1 : -1; + } + + return 0; +}; +module.exports.compareStringsNumeric = compareStringsNumeric; + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +const compareModulesByPostOrderIndexOrIdentifier = (moduleGraph, a, b) => { + const cmp = compareNumbers( + /** @type {number} */ (moduleGraph.getPostOrderIndex(a)), + /** @type {number} */ (moduleGraph.getPostOrderIndex(b)) + ); + if (cmp !== 0) return cmp; + return compareIds(a.identifier(), b.identifier()); +}; +/** @type {ParameterizedComparator} */ +module.exports.compareModulesByPostOrderIndexOrIdentifier = + createCachedParameterizedComparator( + compareModulesByPostOrderIndexOrIdentifier + ); + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +const compareModulesByPreOrderIndexOrIdentifier = (moduleGraph, a, b) => { + const cmp = compareNumbers( + /** @type {number} */ (moduleGraph.getPreOrderIndex(a)), + /** @type {number} */ (moduleGraph.getPreOrderIndex(b)) + ); + if (cmp !== 0) return cmp; + return compareIds(a.identifier(), b.identifier()); +}; +/** @type {ParameterizedComparator} */ +module.exports.compareModulesByPreOrderIndexOrIdentifier = + createCachedParameterizedComparator( + compareModulesByPreOrderIndexOrIdentifier + ); + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Module} a module + * @param {Module} b module + * @returns {-1|0|1} compare result + */ +const compareModulesByIdOrIdentifier = (chunkGraph, a, b) => { + const cmp = compareIds( + /** @type {ModuleId} */ (chunkGraph.getModuleId(a)), + /** @type {ModuleId} */ (chunkGraph.getModuleId(b)) + ); + if (cmp !== 0) return cmp; + return compareIds(a.identifier(), b.identifier()); +}; +/** @type {ParameterizedComparator} */ +module.exports.compareModulesByIdOrIdentifier = + createCachedParameterizedComparator(compareModulesByIdOrIdentifier); + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Chunk} a chunk + * @param {Chunk} b chunk + * @returns {-1|0|1} compare result + */ +const compareChunks = (chunkGraph, a, b) => chunkGraph.compareChunks(a, b); +/** @type {ParameterizedComparator} */ +module.exports.compareChunks = + createCachedParameterizedComparator(compareChunks); + +/** + * @param {string|number} a first id + * @param {string|number} b second id + * @returns {-1|0|1} compare result + */ +const compareIds = (a, b) => { + if (typeof a !== typeof b) { + return typeof a < typeof b ? -1 : 1; + } + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; + +module.exports.compareIds = compareIds; + +/** + * @param {string} a first string + * @param {string} b second string + * @returns {-1|0|1} compare result + */ +const compareStrings = (a, b) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; + +module.exports.compareStrings = compareStrings; + +/** + * @param {ChunkGroup} a first chunk group + * @param {ChunkGroup} b second chunk group + * @returns {-1|0|1} compare result + */ +const compareChunkGroupsByIndex = (a, b) => + /** @type {number} */ (a.index) < /** @type {number} */ (b.index) ? -1 : 1; +module.exports.compareChunkGroupsByIndex = compareChunkGroupsByIndex; + +/** + * @template K1 {Object} + * @template K2 + * @template T + */ +class TwoKeyWeakMap { + constructor() { + /** + * @private + * @type {WeakMap>} + */ + this._map = new WeakMap(); + } + + /** + * @param {K1} key1 first key + * @param {K2} key2 second key + * @returns {T | undefined} value + */ + get(key1, key2) { + const childMap = this._map.get(key1); + if (childMap === undefined) { + return; + } + return childMap.get(key2); + } + + /** + * @param {K1} key1 first key + * @param {K2} key2 second key + * @param {T | undefined} value new value + * @returns {void} + */ + set(key1, key2, value) { + let childMap = this._map.get(key1); + if (childMap === undefined) { + childMap = new WeakMap(); + this._map.set(key1, childMap); + } + childMap.set(key2, value); + } +} + +/** @type {TwoKeyWeakMap, Comparator, Comparator>}} */ +const concatComparatorsCache = new TwoKeyWeakMap(); + +/** + * @template T + * @param {Comparator} c1 comparator + * @param {Comparator} c2 comparator + * @param {Comparator[]} cRest comparators + * @returns {Comparator} comparator + */ +const concatComparators = (c1, c2, ...cRest) => { + if (cRest.length > 0) { + const [c3, ...cRest2] = cRest; + return concatComparators(c1, concatComparators(c2, c3, ...cRest2)); + } + const cacheEntry = /** @type {Comparator} */ ( + concatComparatorsCache.get(c1, c2) + ); + if (cacheEntry !== undefined) return cacheEntry; + /** + * @param {T} a first value + * @param {T} b second value + * @returns {-1|0|1} compare result + */ + const result = (a, b) => { + const res = c1(a, b); + if (res !== 0) return res; + return c2(a, b); + }; + concatComparatorsCache.set(c1, c2, result); + return result; +}; +module.exports.concatComparators = concatComparators; + +/** + * @template A, B + * @typedef {(input: A) => B | undefined | null} Selector + */ + +/** @type {TwoKeyWeakMap, Comparator, Comparator>}} */ +const compareSelectCache = new TwoKeyWeakMap(); + +/** + * @template T + * @template R + * @param {Selector} getter getter for value + * @param {Comparator} comparator comparator + * @returns {Comparator} comparator + */ +const compareSelect = (getter, comparator) => { + const cacheEntry = compareSelectCache.get(getter, comparator); + if (cacheEntry !== undefined) return cacheEntry; + /** + * @param {T} a first value + * @param {T} b second value + * @returns {-1|0|1} compare result + */ + const result = (a, b) => { + const aValue = getter(a); + const bValue = getter(b); + if (aValue !== undefined && aValue !== null) { + if (bValue !== undefined && bValue !== null) { + return comparator(aValue, bValue); + } + return -1; + } + if (bValue !== undefined && bValue !== null) { + return 1; + } + return 0; + }; + compareSelectCache.set(getter, comparator, result); + return result; +}; +module.exports.compareSelect = compareSelect; + +/** @type {WeakMap, Comparator>>} */ +const compareIteratorsCache = new WeakMap(); + +/** + * @template T + * @param {Comparator} elementComparator comparator for elements + * @returns {Comparator>} comparator for iterables of elements + */ +const compareIterables = elementComparator => { + const cacheEntry = compareIteratorsCache.get(elementComparator); + if (cacheEntry !== undefined) return cacheEntry; + /** + * @param {Iterable} a first value + * @param {Iterable} b second value + * @returns {-1|0|1} compare result + */ + const result = (a, b) => { + const aI = a[Symbol.iterator](); + const bI = b[Symbol.iterator](); + while (true) { + const aItem = aI.next(); + const bItem = bI.next(); + if (aItem.done) { + return bItem.done ? 0 : -1; + } else if (bItem.done) { + return 1; + } + const res = elementComparator(aItem.value, bItem.value); + if (res !== 0) return res; + } + }; + compareIteratorsCache.set(elementComparator, result); + return result; +}; +module.exports.compareIterables = compareIterables; + +// TODO this is no longer needed when minimum node.js version is >= 12 +// since these versions ship with a stable sort function +/** + * @template T + * @param {Iterable} iterable original ordered list + * @returns {Comparator} comparator + */ +module.exports.keepOriginalOrder = iterable => { + /** @type {Map} */ + const map = new Map(); + let i = 0; + for (const item of iterable) { + map.set(item, i++); + } + return (a, b) => + compareNumbers( + /** @type {number} */ (map.get(a)), + /** @type {number} */ (map.get(b)) + ); +}; + +/** + * @param {ChunkGraph} chunkGraph the chunk graph + * @returns {Comparator} comparator + */ +module.exports.compareChunksNatural = chunkGraph => { + const cmpFn = module.exports.compareModulesById(chunkGraph); + const cmpIterableFn = compareIterables(cmpFn); + return concatComparators( + compareSelect( + chunk => /** @type {string|number} */ (chunk.name), + compareIds + ), + compareSelect(chunk => chunk.runtime, compareRuntime), + compareSelect( + /** + * @param {Chunk} chunk a chunk + * @returns {Iterable} modules + */ + chunk => chunkGraph.getOrderedChunkModulesIterable(chunk, cmpFn), + cmpIterableFn + ) + ); +}; + +/** + * Compare two locations + * @param {DependencyLocation} a A location node + * @param {DependencyLocation} b A location node + * @returns {-1|0|1} sorting comparator value + */ +module.exports.compareLocations = (a, b) => { + const isObjectA = typeof a === "object" && a !== null; + const isObjectB = typeof b === "object" && b !== null; + if (!isObjectA || !isObjectB) { + if (isObjectA) return 1; + if (isObjectB) return -1; + return 0; + } + if ("start" in a) { + if ("start" in b) { + const ap = a.start; + const bp = b.start; + if (ap.line < bp.line) return -1; + if (ap.line > bp.line) return 1; + if (/** @type {number} */ (ap.column) < /** @type {number} */ (bp.column)) + return -1; + if (/** @type {number} */ (ap.column) > /** @type {number} */ (bp.column)) + return 1; + } else return -1; + } else if ("start" in b) return 1; + if ("name" in a) { + if ("name" in b) { + if (a.name < b.name) return -1; + if (a.name > b.name) return 1; + } else return -1; + } else if ("name" in b) return 1; + if ("index" in a) { + if ("index" in b) { + if (/** @type {number} */ (a.index) < /** @type {number} */ (b.index)) + return -1; + if (/** @type {number} */ (a.index) > /** @type {number} */ (b.index)) + return 1; + } else return -1; + } else if ("index" in b) return 1; + return 0; +}; diff --git a/webpack-lib/lib/util/compileBooleanMatcher.js b/webpack-lib/lib/util/compileBooleanMatcher.js new file mode 100644 index 00000000000..e388602f246 --- /dev/null +++ b/webpack-lib/lib/util/compileBooleanMatcher.js @@ -0,0 +1,234 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @param {string} str string + * @returns {string} quoted meta + */ +const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); + +/** + * @param {string} str string + * @returns {string} string + */ +const toSimpleString = str => { + if (`${Number(str)}` === str) { + return str; + } + return JSON.stringify(str); +}; + +/** + * @param {Record} map value map + * @returns {boolean|(function(string): string)} true/false, when unconditionally true/false, or a template function to determine the value at runtime + */ +const compileBooleanMatcher = map => { + const positiveItems = Object.keys(map).filter(i => map[i]); + const negativeItems = Object.keys(map).filter(i => !map[i]); + if (positiveItems.length === 0) return false; + if (negativeItems.length === 0) return true; + return compileBooleanMatcherFromLists(positiveItems, negativeItems); +}; + +/** + * @param {string[]} positiveItems positive items + * @param {string[]} negativeItems negative items + * @returns {function(string): string} a template function to determine the value at runtime + */ +const compileBooleanMatcherFromLists = (positiveItems, negativeItems) => { + if (positiveItems.length === 0) return () => "false"; + if (negativeItems.length === 0) return () => "true"; + if (positiveItems.length === 1) + return value => `${toSimpleString(positiveItems[0])} == ${value}`; + if (negativeItems.length === 1) + return value => `${toSimpleString(negativeItems[0])} != ${value}`; + const positiveRegexp = itemsToRegexp(positiveItems); + const negativeRegexp = itemsToRegexp(negativeItems); + if (positiveRegexp.length <= negativeRegexp.length) { + return value => `/^${positiveRegexp}$/.test(${value})`; + } + return value => `!/^${negativeRegexp}$/.test(${value})`; +}; + +/** + * @param {Set} itemsSet items set + * @param {(str: string) => string | false} getKey get key function + * @param {(str: Array) => boolean} condition condition + * @returns {Array>} list of common items + */ +const popCommonItems = (itemsSet, getKey, condition) => { + /** @type {Map>} */ + const map = new Map(); + for (const item of itemsSet) { + const key = getKey(item); + if (key) { + let list = map.get(key); + if (list === undefined) { + /** @type {Array} */ + list = []; + map.set(key, list); + } + list.push(item); + } + } + /** @type {Array>} */ + const result = []; + for (const list of map.values()) { + if (condition(list)) { + for (const item of list) { + itemsSet.delete(item); + } + result.push(list); + } + } + return result; +}; + +/** + * @param {Array} items items + * @returns {string} common prefix + */ +const getCommonPrefix = items => { + let prefix = items[0]; + for (let i = 1; i < items.length; i++) { + const item = items[i]; + for (let p = 0; p < prefix.length; p++) { + if (item[p] !== prefix[p]) { + prefix = prefix.slice(0, p); + break; + } + } + } + return prefix; +}; + +/** + * @param {Array} items items + * @returns {string} common suffix + */ +const getCommonSuffix = items => { + let suffix = items[0]; + for (let i = 1; i < items.length; i++) { + const item = items[i]; + for (let p = item.length - 1, s = suffix.length - 1; s >= 0; p--, s--) { + if (item[p] !== suffix[s]) { + suffix = suffix.slice(s + 1); + break; + } + } + } + return suffix; +}; + +/** + * @param {Array} itemsArr array of items + * @returns {string} regexp + */ +const itemsToRegexp = itemsArr => { + if (itemsArr.length === 1) { + return quoteMeta(itemsArr[0]); + } + /** @type {Array} */ + const finishedItems = []; + + // merge single char items: (a|b|c|d|ef) => ([abcd]|ef) + let countOfSingleCharItems = 0; + for (const item of itemsArr) { + if (item.length === 1) { + countOfSingleCharItems++; + } + } + // special case for only single char items + if (countOfSingleCharItems === itemsArr.length) { + return `[${quoteMeta(itemsArr.sort().join(""))}]`; + } + const items = new Set(itemsArr.sort()); + if (countOfSingleCharItems > 2) { + let singleCharItems = ""; + for (const item of items) { + if (item.length === 1) { + singleCharItems += item; + items.delete(item); + } + } + finishedItems.push(`[${quoteMeta(singleCharItems)}]`); + } + + // special case for 2 items with common prefix/suffix + if (finishedItems.length === 0 && items.size === 2) { + const prefix = getCommonPrefix(itemsArr); + const suffix = getCommonSuffix( + itemsArr.map(item => item.slice(prefix.length)) + ); + if (prefix.length > 0 || suffix.length > 0) { + return `${quoteMeta(prefix)}${itemsToRegexp( + itemsArr.map(i => i.slice(prefix.length, -suffix.length || undefined)) + )}${quoteMeta(suffix)}`; + } + } + + // special case for 2 items with common suffix + if (finishedItems.length === 0 && items.size === 2) { + /** @type {Iterator} */ + const it = items[Symbol.iterator](); + const a = it.next().value; + const b = it.next().value; + if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) { + return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta( + a.slice(-1) + )}`; + } + } + + // find common prefix: (a1|a2|a3|a4|b5) => (a(1|2|3|4)|b5) + const prefixed = popCommonItems( + items, + item => (item.length >= 1 ? item[0] : false), + list => { + if (list.length >= 3) return true; + if (list.length <= 1) return false; + return list[0][1] === list[1][1]; + } + ); + for (const prefixedItems of prefixed) { + const prefix = getCommonPrefix(prefixedItems); + finishedItems.push( + `${quoteMeta(prefix)}${itemsToRegexp( + prefixedItems.map(i => i.slice(prefix.length)) + )}` + ); + } + + // find common suffix: (a1|b1|c1|d1|e2) => ((a|b|c|d)1|e2) + const suffixed = popCommonItems( + items, + item => (item.length >= 1 ? item.slice(-1) : false), + list => { + if (list.length >= 3) return true; + if (list.length <= 1) return false; + return list[0].slice(-2) === list[1].slice(-2); + } + ); + for (const suffixedItems of suffixed) { + const suffix = getCommonSuffix(suffixedItems); + finishedItems.push( + `${itemsToRegexp( + suffixedItems.map(i => i.slice(0, -suffix.length)) + )}${quoteMeta(suffix)}` + ); + } + + // TODO further optimize regexp, i. e. + // use ranges: (1|2|3|4|a) => [1-4a] + const conditional = finishedItems.concat(Array.from(items, quoteMeta)); + if (conditional.length === 1) return conditional[0]; + return `(${conditional.join("|")})`; +}; + +compileBooleanMatcher.fromLists = compileBooleanMatcherFromLists; +compileBooleanMatcher.itemsToRegexp = itemsToRegexp; +module.exports = compileBooleanMatcher; diff --git a/webpack-lib/lib/util/concatenate.js b/webpack-lib/lib/util/concatenate.js new file mode 100644 index 00000000000..8d19001c9d8 --- /dev/null +++ b/webpack-lib/lib/util/concatenate.js @@ -0,0 +1,227 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Template = require("../Template"); + +/** @typedef {import("eslint-scope").Scope} Scope */ +/** @typedef {import("eslint-scope").Reference} Reference */ +/** @typedef {import("eslint-scope").Variable} Variable */ +/** @typedef {import("estree").Node} Node */ +/** @typedef {import("../javascript/JavascriptParser").Range} Range */ +/** @typedef {import("../javascript/JavascriptParser").Program} Program */ +/** @typedef {Set} UsedNames */ + +const DEFAULT_EXPORT = "__WEBPACK_DEFAULT_EXPORT__"; +const NAMESPACE_OBJECT_EXPORT = "__WEBPACK_NAMESPACE_OBJECT__"; + +/** + * @param {Variable} variable variable + * @returns {Reference[]} references + */ +const getAllReferences = variable => { + let set = variable.references; + // Look for inner scope variables too (like in class Foo { t() { Foo } }) + const identifiers = new Set(variable.identifiers); + for (const scope of variable.scope.childScopes) { + for (const innerVar of scope.variables) { + if (innerVar.identifiers.some(id => identifiers.has(id))) { + set = set.concat(innerVar.references); + break; + } + } + } + return set; +}; + +/** + * @param {Node | Node[]} ast ast + * @param {Node} node node + * @returns {undefined | Node[]} result + */ +const getPathInAst = (ast, node) => { + if (ast === node) { + return []; + } + + const nr = /** @type {Range} */ (node.range); + + /** + * @param {Node} n node + * @returns {Node[] | undefined} result + */ + const enterNode = n => { + if (!n) return; + const r = n.range; + if (r && r[0] <= nr[0] && r[1] >= nr[1]) { + const path = getPathInAst(n, node); + if (path) { + path.push(n); + return path; + } + } + }; + + if (Array.isArray(ast)) { + for (let i = 0; i < ast.length; i++) { + const enterResult = enterNode(ast[i]); + if (enterResult !== undefined) return enterResult; + } + } else if (ast && typeof ast === "object") { + const keys = + /** @type {Array} */ + (Object.keys(ast)); + for (let i = 0; i < keys.length; i++) { + // We are making the faster check in `enterNode` using `n.range` + const value = + ast[ + /** @type {Exclude} */ + (keys[i]) + ]; + if (Array.isArray(value)) { + const pathResult = getPathInAst(value, node); + if (pathResult !== undefined) return pathResult; + } else if (value && typeof value === "object") { + const enterResult = enterNode(value); + if (enterResult !== undefined) return enterResult; + } + } + } +}; + +/** + * @param {string} oldName old name + * @param {UsedNames} usedNamed1 used named 1 + * @param {UsedNames} usedNamed2 used named 2 + * @param {string} extraInfo extra info + * @returns {string} found new name + */ +function findNewName(oldName, usedNamed1, usedNamed2, extraInfo) { + let name = oldName; + + if (name === DEFAULT_EXPORT) { + name = ""; + } + if (name === NAMESPACE_OBJECT_EXPORT) { + name = "namespaceObject"; + } + + // Remove uncool stuff + extraInfo = extraInfo.replace( + /\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g, + "" + ); + + const splittedInfo = extraInfo.split("/"); + while (splittedInfo.length) { + name = splittedInfo.pop() + (name ? `_${name}` : ""); + const nameIdent = Template.toIdentifier(name); + if ( + !usedNamed1.has(nameIdent) && + (!usedNamed2 || !usedNamed2.has(nameIdent)) + ) + return nameIdent; + } + + let i = 0; + let nameWithNumber = Template.toIdentifier(`${name}_${i}`); + while ( + usedNamed1.has(nameWithNumber) || + // eslint-disable-next-line no-unmodified-loop-condition + (usedNamed2 && usedNamed2.has(nameWithNumber)) + ) { + i++; + nameWithNumber = Template.toIdentifier(`${name}_${i}`); + } + return nameWithNumber; +} + +/** + * @param {Scope | null} s scope + * @param {UsedNames} nameSet name set + * @param {TODO} scopeSet1 scope set 1 + * @param {TODO} scopeSet2 scope set 2 + */ +const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => { + let scope = s; + while (scope) { + if (scopeSet1.has(scope)) break; + if (scopeSet2.has(scope)) break; + scopeSet1.add(scope); + for (const variable of scope.variables) { + nameSet.add(variable.name); + } + scope = scope.upper; + } +}; + +const RESERVED_NAMES = new Set( + [ + // internal names (should always be renamed) + DEFAULT_EXPORT, + NAMESPACE_OBJECT_EXPORT, + + // keywords + "abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue", + "debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float", + "for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null", + "package,private,protected,public,return,short,static,super,switch,synchronized,this,throw", + "throws,transient,true,try,typeof,var,void,volatile,while,with,yield", + + // commonjs/amd + "module,__dirname,__filename,exports,require,define", + + // js globals + "Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math", + "NaN,name,Number,Object,prototype,String,Symbol,toString,undefined,valueOf", + + // browser globals + "alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout", + "clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent", + "defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape", + "event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location", + "mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering", + "open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat", + "parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll", + "secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape", + "untaint,window", + + // window events + "onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit" + ] + .join(",") + .split(",") +); + +/** + * @param {Map }>} usedNamesInScopeInfo used names in scope info + * @param {string} module module identifier + * @param {string} id export id + * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set }} info + */ +const getUsedNamesInScopeInfo = (usedNamesInScopeInfo, module, id) => { + const key = `${module}-${id}`; + let info = usedNamesInScopeInfo.get(key); + if (info === undefined) { + info = { + usedNames: new Set(), + alreadyCheckedScopes: new Set() + }; + usedNamesInScopeInfo.set(key, info); + } + return info; +}; + +module.exports = { + getUsedNamesInScopeInfo, + findNewName, + getAllReferences, + getPathInAst, + NAMESPACE_OBJECT_EXPORT, + DEFAULT_EXPORT, + RESERVED_NAMES, + addScopeSymbols +}; diff --git a/webpack-lib/lib/util/conventions.js b/webpack-lib/lib/util/conventions.js new file mode 100644 index 00000000000..4f78df1c095 --- /dev/null +++ b/webpack-lib/lib/util/conventions.js @@ -0,0 +1,126 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Gengkun He @ahabhgk +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ + +/** + * @param {string} input input + * @param {CssGeneratorExportsConvention | undefined} convention convention + * @returns {string[]} results + */ +module.exports.cssExportConvention = (input, convention) => { + const set = new Set(); + if (typeof convention === "function") { + set.add(convention(input)); + } else { + switch (convention) { + case "camel-case": { + set.add(input); + set.add(module.exports.camelCase(input)); + break; + } + case "camel-case-only": { + set.add(module.exports.camelCase(input)); + break; + } + case "dashes": { + set.add(input); + set.add(module.exports.dashesCamelCase(input)); + break; + } + case "dashes-only": { + set.add(module.exports.dashesCamelCase(input)); + break; + } + case "as-is": { + set.add(input); + break; + } + } + } + return Array.from(set); +}; + +// Copy from css-loader +/** + * @param {string} input input + * @returns {string} result + */ +module.exports.dashesCamelCase = input => + input.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase()); + +// Copy from css-loader +/** + * @param {string} input input + * @returns {string} result + */ +module.exports.camelCase = input => { + let result = input.trim(); + + if (result.length === 0) { + return ""; + } + + if (result.length === 1) { + return result.toLowerCase(); + } + + const hasUpperCase = result !== result.toLowerCase(); + + if (hasUpperCase) { + result = preserveCamelCase(result); + } + + return result + .replace(/^[_.\- ]+/, "") + .toLowerCase() + .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toUpperCase()) + .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toUpperCase()); +}; + +// Copy from css-loader +/** + * @param {string} string string + * @returns {string} result + */ +const preserveCamelCase = string => { + let result = string; + let isLastCharLower = false; + let isLastCharUpper = false; + let isLastLastCharUpper = false; + + for (let i = 0; i < result.length; i++) { + const character = result[i]; + + if (isLastCharLower && /[\p{Lu}]/u.test(character)) { + result = `${result.slice(0, i)}-${result.slice(i)}`; + isLastCharLower = false; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = true; + i += 1; + } else if ( + isLastCharUpper && + isLastLastCharUpper && + /[\p{Ll}]/u.test(character) + ) { + result = `${result.slice(0, i - 1)}-${result.slice(i - 1)}`; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = false; + isLastCharLower = true; + } else { + isLastCharLower = + character.toLowerCase() === character && + character.toUpperCase() !== character; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = + character.toUpperCase() === character && + character.toLowerCase() !== character; + } + } + + return result; +}; diff --git a/webpack-lib/lib/util/create-schema-validation.js b/webpack-lib/lib/util/create-schema-validation.js new file mode 100644 index 00000000000..4f12c8e69af --- /dev/null +++ b/webpack-lib/lib/util/create-schema-validation.js @@ -0,0 +1,41 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const memoize = require("./memoize"); + +/** @typedef {import("schema-utils/declarations/validate").ValidationErrorConfiguration} ValidationErrorConfiguration */ +/** @typedef {import("./fs").JsonObject} JsonObject */ + +const getValidate = memoize(() => require("schema-utils").validate); + +/** + * @template {object | object[]} T + * @param {(function(T): boolean) | undefined} check check + * @param {() => JsonObject} getSchema get schema fn + * @param {ValidationErrorConfiguration} options options + * @returns {function(T=): void} validate + */ +const createSchemaValidation = (check, getSchema, options) => { + getSchema = memoize(getSchema); + return value => { + if (check && !check(/** @type {T} */ (value))) { + getValidate()( + getSchema(), + /** @type {object | object[]} */ + (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" + )(); + } + }; +}; + +module.exports = createSchemaValidation; diff --git a/webpack-lib/lib/util/createHash.js b/webpack-lib/lib/util/createHash.js new file mode 100644 index 00000000000..991a1a2dbd8 --- /dev/null +++ b/webpack-lib/lib/util/createHash.js @@ -0,0 +1,194 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Hash = require("./Hash"); + +const BULK_SIZE = 2000; + +// We are using an object instead of a Map as this will stay static during the runtime +// so access to it can be optimized by v8 +/** @type {{[key: string]: Map}} */ +const digestCaches = {}; + +/** @typedef {function(): Hash} HashFactory */ + +class BulkUpdateDecorator extends Hash { + /** + * @param {Hash | HashFactory} hashOrFactory function to create a hash + * @param {string=} hashKey key for caching + */ + constructor(hashOrFactory, hashKey) { + super(); + this.hashKey = hashKey; + if (typeof hashOrFactory === "function") { + this.hashFactory = hashOrFactory; + this.hash = undefined; + } else { + this.hashFactory = undefined; + this.hash = hashOrFactory; + } + this.buffer = ""; + } + + /** + * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} + * @param {string|Buffer} data data + * @param {string=} inputEncoding data encoding + * @returns {this} updated hash + */ + update(data, inputEncoding) { + if ( + inputEncoding !== undefined || + typeof data !== "string" || + data.length > BULK_SIZE + ) { + if (this.hash === undefined) + this.hash = /** @type {HashFactory} */ (this.hashFactory)(); + if (this.buffer.length > 0) { + this.hash.update(this.buffer); + this.buffer = ""; + } + this.hash.update(data, inputEncoding); + } else { + this.buffer += data; + if (this.buffer.length > BULK_SIZE) { + if (this.hash === undefined) + this.hash = /** @type {HashFactory} */ (this.hashFactory)(); + this.hash.update(this.buffer); + this.buffer = ""; + } + } + return this; + } + + /** + * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} + * @param {string=} encoding encoding of the return value + * @returns {string|Buffer} digest + */ + digest(encoding) { + let digestCache; + const buffer = this.buffer; + if (this.hash === undefined) { + // short data for hash, we can use caching + const cacheKey = `${this.hashKey}-${encoding}`; + digestCache = digestCaches[cacheKey]; + if (digestCache === undefined) { + digestCache = digestCaches[cacheKey] = new Map(); + } + const cacheEntry = digestCache.get(buffer); + if (cacheEntry !== undefined) return cacheEntry; + this.hash = /** @type {HashFactory} */ (this.hashFactory)(); + } + if (buffer.length > 0) { + this.hash.update(buffer); + } + const digestResult = this.hash.digest(encoding); + const result = + typeof digestResult === "string" ? digestResult : digestResult.toString(); + if (digestCache !== undefined) { + digestCache.set(buffer, result); + } + return result; + } +} + +/* istanbul ignore next */ +class DebugHash extends Hash { + constructor() { + super(); + this.string = ""; + } + + /** + * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} + * @param {string|Buffer} data data + * @param {string=} inputEncoding data encoding + * @returns {this} updated hash + */ + update(data, inputEncoding) { + if (typeof data !== "string") data = data.toString("utf-8"); + const prefix = Buffer.from("@webpack-debug-digest@").toString("hex"); + if (data.startsWith(prefix)) { + data = Buffer.from(data.slice(prefix.length), "hex").toString(); + } + this.string += `[${data}](${ + /** @type {string} */ (new Error().stack).split("\n", 3)[2] + })\n`; + return this; + } + + /** + * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} + * @param {string=} encoding encoding of the return value + * @returns {string|Buffer} digest + */ + digest(encoding) { + return Buffer.from(`@webpack-debug-digest@${this.string}`).toString("hex"); + } +} + +/** @type {typeof import("crypto") | undefined} */ +let crypto; +/** @type {typeof import("./hash/xxhash64") | undefined} */ +let createXXHash64; +/** @type {typeof import("./hash/md4") | undefined} */ +let createMd4; +/** @type {typeof import("./hash/BatchedHash") | undefined} */ +let BatchedHash; + +/** @typedef {string | typeof Hash} Algorithm */ + +/** + * Creates a hash by name or function + * @param {Algorithm} algorithm the algorithm name or a constructor creating a hash + * @returns {Hash} the hash + */ +module.exports = algorithm => { + if (typeof algorithm === "function") { + // eslint-disable-next-line new-cap + return new BulkUpdateDecorator(() => new algorithm()); + } + switch (algorithm) { + // TODO add non-cryptographic algorithm here + case "debug": + return new DebugHash(); + case "xxhash64": + if (createXXHash64 === undefined) { + createXXHash64 = require("./hash/xxhash64"); + if (BatchedHash === undefined) { + BatchedHash = require("./hash/BatchedHash"); + } + } + return new /** @type {typeof import("./hash/BatchedHash")} */ ( + BatchedHash + )(createXXHash64()); + case "md4": + if (createMd4 === undefined) { + createMd4 = require("./hash/md4"); + if (BatchedHash === undefined) { + BatchedHash = require("./hash/BatchedHash"); + } + } + return new /** @type {typeof import("./hash/BatchedHash")} */ ( + BatchedHash + )(createMd4()); + case "native-md4": + if (crypto === undefined) crypto = require("crypto"); + return new BulkUpdateDecorator( + () => /** @type {typeof import("crypto")} */ (crypto).createHash("md4"), + "md4" + ); + default: + if (crypto === undefined) crypto = require("crypto"); + return new BulkUpdateDecorator( + () => + /** @type {typeof import("crypto")} */ (crypto).createHash(algorithm), + algorithm + ); + } +}; diff --git a/webpack-lib/lib/util/deprecation.js b/webpack-lib/lib/util/deprecation.js new file mode 100644 index 00000000000..d59cbd78f0f --- /dev/null +++ b/webpack-lib/lib/util/deprecation.js @@ -0,0 +1,347 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); + +/** @type {Map} */ +const deprecationCache = new Map(); + +/** + * @typedef {object} FakeHookMarker + * @property {true} _fakeHook it's a fake hook + */ + +/** + * @template T + * @typedef {T & FakeHookMarker} FakeHook + */ + +/** + * @param {string} message deprecation message + * @param {string} code deprecation code + * @returns {Function} function to trigger deprecation + */ +const createDeprecation = (message, code) => { + const cached = deprecationCache.get(message); + if (cached !== undefined) return cached; + const fn = util.deprecate( + () => {}, + message, + `DEP_WEBPACK_DEPRECATION_${code}` + ); + deprecationCache.set(message, fn); + return fn; +}; + +const COPY_METHODS = [ + "concat", + "entry", + "filter", + "find", + "findIndex", + "includes", + "indexOf", + "join", + "lastIndexOf", + "map", + "reduce", + "reduceRight", + "slice", + "some" +]; + +const DISABLED_METHODS = [ + "copyWithin", + "entries", + "fill", + "keys", + "pop", + "reverse", + "shift", + "splice", + "sort", + "unshift" +]; + +/** + * @param {any} set new set + * @param {string} name property name + * @returns {void} + */ +module.exports.arrayToSetDeprecation = (set, name) => { + for (const method of COPY_METHODS) { + if (set[method]) continue; + const d = createDeprecation( + `${name} was changed from Array to Set (using Array method '${method}' is deprecated)`, + "ARRAY_TO_SET" + ); + /** + * @deprecated + * @this {Set} + * @returns {number} count + */ + set[method] = function () { + d(); + const array = Array.from(this); + return Array.prototype[/** @type {keyof COPY_METHODS} */ (method)].apply( + array, + // eslint-disable-next-line prefer-rest-params + arguments + ); + }; + } + const dPush = createDeprecation( + `${name} was changed from Array to Set (using Array method 'push' is deprecated)`, + "ARRAY_TO_SET_PUSH" + ); + const dLength = createDeprecation( + `${name} was changed from Array to Set (using Array property 'length' is deprecated)`, + "ARRAY_TO_SET_LENGTH" + ); + const dIndexer = createDeprecation( + `${name} was changed from Array to Set (indexing Array is deprecated)`, + "ARRAY_TO_SET_INDEXER" + ); + /** + * @deprecated + * @this {Set} + * @returns {number} count + */ + set.push = function () { + dPush(); + // eslint-disable-next-line prefer-rest-params + for (const item of Array.from(arguments)) { + this.add(item); + } + return this.size; + }; + for (const method of DISABLED_METHODS) { + if (set[method]) continue; + set[method] = () => { + throw new Error( + `${name} was changed from Array to Set (using Array method '${method}' is not possible)` + ); + }; + } + /** + * @param {number} index index + * @returns {any} value + */ + const createIndexGetter = index => { + /** + * @this {Set} a Set + * @returns {any} the value at this location + */ + // eslint-disable-next-line func-style + const fn = function () { + dIndexer(); + let i = 0; + for (const item of this) { + if (i++ === index) return item; + } + }; + return fn; + }; + /** + * @param {number} index index + */ + const defineIndexGetter = index => { + Object.defineProperty(set, index, { + get: createIndexGetter(index), + set(value) { + throw new Error( + `${name} was changed from Array to Set (indexing Array with write is not possible)` + ); + } + }); + }; + defineIndexGetter(0); + let indexerDefined = 1; + Object.defineProperty(set, "length", { + get() { + dLength(); + const length = this.size; + for (indexerDefined; indexerDefined < length + 1; indexerDefined++) { + defineIndexGetter(indexerDefined); + } + return length; + }, + set(value) { + throw new Error( + `${name} was changed from Array to Set (writing to Array property 'length' is not possible)` + ); + } + }); + set[Symbol.isConcatSpreadable] = true; +}; + +/** + * @template T + * @param {string} name name + * @returns {{ new (values?: readonly T[] | null): SetDeprecatedArray }} SetDeprecatedArray + */ +module.exports.createArrayToSetDeprecationSet = name => { + let initialized = false; + + /** + * @template T + */ + class SetDeprecatedArray extends Set { + /** + * @param {readonly T[] | null=} items items + */ + constructor(items) { + super(items); + if (!initialized) { + initialized = true; + module.exports.arrayToSetDeprecation( + SetDeprecatedArray.prototype, + name + ); + } + } + } + return SetDeprecatedArray; +}; + +/** + * @template {object} T + * @param {T} obj object + * @param {string} name property name + * @param {string} code deprecation code + * @param {string} note additional note + * @returns {Proxy} frozen object with deprecation when modifying + */ +module.exports.soonFrozenObjectDeprecation = (obj, name, code, note = "") => { + const message = `${name} will be frozen in future, all modifications are deprecated.${ + note && `\n${note}` + }`; + return /** @type {Proxy} */ ( + new Proxy(/** @type {object} */ (obj), { + set: util.deprecate( + /** + * @param {T} target target + * @param {string | symbol} property property + * @param {any} value value + * @param {any} receiver receiver + * @returns {boolean} result + */ + (target, property, value, receiver) => + Reflect.set( + /** @type {object} */ (target), + property, + value, + receiver + ), + message, + code + ), + defineProperty: util.deprecate( + /** + * @param {T} target target + * @param {string | symbol} property property + * @param {PropertyDescriptor} descriptor descriptor + * @returns {boolean} result + */ + (target, property, descriptor) => + Reflect.defineProperty( + /** @type {object} */ (target), + property, + descriptor + ), + message, + code + ), + deleteProperty: util.deprecate( + /** + * @param {T} target target + * @param {string | symbol} property property + * @returns {boolean} result + */ + (target, property) => + Reflect.deleteProperty(/** @type {object} */ (target), property), + message, + code + ), + setPrototypeOf: util.deprecate( + /** + * @param {T} target target + * @param {object | null} proto proto + * @returns {boolean} result + */ + (target, proto) => + Reflect.setPrototypeOf(/** @type {object} */ (target), proto), + message, + code + ) + }) + ); +}; + +/** + * @template T + * @param {T} obj object + * @param {string} message deprecation message + * @param {string} code deprecation code + * @returns {T} object with property access deprecated + */ +const deprecateAllProperties = (obj, message, code) => { + const newObj = {}; + const descriptors = Object.getOwnPropertyDescriptors(obj); + for (const name of Object.keys(descriptors)) { + const descriptor = descriptors[name]; + if (typeof descriptor.value === "function") { + Object.defineProperty(newObj, name, { + ...descriptor, + value: util.deprecate(descriptor.value, message, code) + }); + } else if (descriptor.get || descriptor.set) { + Object.defineProperty(newObj, name, { + ...descriptor, + get: descriptor.get && util.deprecate(descriptor.get, message, code), + set: descriptor.set && util.deprecate(descriptor.set, message, code) + }); + } else { + let value = descriptor.value; + Object.defineProperty(newObj, name, { + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + get: util.deprecate(() => value, message, code), + set: descriptor.writable + ? util.deprecate( + /** + * @template T + * @param {T} v value + * @returns {T} result + */ + v => (value = v), + message, + code + ) + : undefined + }); + } + } + return /** @type {T} */ (newObj); +}; +module.exports.deprecateAllProperties = deprecateAllProperties; + +/** + * @template {object} T + * @param {T} fakeHook fake hook implementation + * @param {string=} message deprecation message (not deprecated when unset) + * @param {string=} code deprecation code (not deprecated when unset) + * @returns {FakeHook} fake hook which redirects + */ +module.exports.createFakeHook = (fakeHook, message, code) => { + if (message && code) { + fakeHook = deprecateAllProperties(fakeHook, message, code); + } + return Object.freeze( + Object.assign(fakeHook, { _fakeHook: /** @type {true} */ (true) }) + ); +}; diff --git a/webpack-lib/lib/util/deterministicGrouping.js b/webpack-lib/lib/util/deterministicGrouping.js new file mode 100644 index 00000000000..b69be028899 --- /dev/null +++ b/webpack-lib/lib/util/deterministicGrouping.js @@ -0,0 +1,540 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +// Simulations show these probabilities for a single change +// 93.1% that one group is invalidated +// 4.8% that two groups are invalidated +// 1.1% that 3 groups are invalidated +// 0.1% that 4 or more groups are invalidated +// +// And these for removing/adding 10 lexically adjacent files +// 64.5% that one group is invalidated +// 24.8% that two groups are invalidated +// 7.8% that 3 groups are invalidated +// 2.7% that 4 or more groups are invalidated +// +// And these for removing/adding 3 random files +// 0% that one group is invalidated +// 3.7% that two groups are invalidated +// 80.8% that 3 groups are invalidated +// 12.3% that 4 groups are invalidated +// 3.2% that 5 or more groups are invalidated + +/** + * @param {string} a key + * @param {string} b key + * @returns {number} the similarity as number + */ +const similarity = (a, b) => { + const l = Math.min(a.length, b.length); + let dist = 0; + for (let i = 0; i < l; i++) { + const ca = a.charCodeAt(i); + const cb = b.charCodeAt(i); + dist += Math.max(0, 10 - Math.abs(ca - cb)); + } + return dist; +}; + +/** + * @param {string} a key + * @param {string} b key + * @param {Set} usedNames set of already used names + * @returns {string} the common part and a single char for the difference + */ +const getName = (a, b, usedNames) => { + const l = Math.min(a.length, b.length); + let i = 0; + while (i < l) { + if (a.charCodeAt(i) !== b.charCodeAt(i)) { + i++; + break; + } + i++; + } + while (i < l) { + const name = a.slice(0, i); + const lowerName = name.toLowerCase(); + if (!usedNames.has(lowerName)) { + usedNames.add(lowerName); + return name; + } + i++; + } + // names always contain a hash, so this is always unique + // we don't need to check usedNames nor add it + return a; +}; + +/** + * @param {Record} total total size + * @param {Record} size single size + * @returns {void} + */ +const addSizeTo = (total, size) => { + for (const key of Object.keys(size)) { + total[key] = (total[key] || 0) + size[key]; + } +}; + +/** + * @param {Record} total total size + * @param {Record} size single size + * @returns {void} + */ +const subtractSizeFrom = (total, size) => { + for (const key of Object.keys(size)) { + total[key] -= size[key]; + } +}; + +/** + * @template T + * @param {Iterable>} nodes some nodes + * @returns {Record} total size + */ +const sumSize = nodes => { + const sum = Object.create(null); + for (const node of nodes) { + addSizeTo(sum, node.size); + } + return sum; +}; + +/** + * @param {Record} size size + * @param {Record} maxSize minimum size + * @returns {boolean} true, when size is too big + */ +const isTooBig = (size, maxSize) => { + for (const key of Object.keys(size)) { + const s = size[key]; + if (s === 0) continue; + const maxSizeValue = maxSize[key]; + if (typeof maxSizeValue === "number" && s > maxSizeValue) return true; + } + return false; +}; + +/** + * @param {Record} size size + * @param {Record} minSize minimum size + * @returns {boolean} true, when size is too small + */ +const isTooSmall = (size, minSize) => { + for (const key of Object.keys(size)) { + const s = size[key]; + if (s === 0) continue; + const minSizeValue = minSize[key]; + if (typeof minSizeValue === "number" && s < minSizeValue) return true; + } + return false; +}; + +/** + * @param {Record} size size + * @param {Record} minSize minimum size + * @returns {Set} set of types that are too small + */ +const getTooSmallTypes = (size, minSize) => { + const types = new Set(); + for (const key of Object.keys(size)) { + const s = size[key]; + if (s === 0) continue; + const minSizeValue = minSize[key]; + if (typeof minSizeValue === "number" && s < minSizeValue) types.add(key); + } + return types; +}; + +/** + * @template T + * @param {TODO} size size + * @param {Set} types types + * @returns {number} number of matching size types + */ +const getNumberOfMatchingSizeTypes = (size, types) => { + let i = 0; + for (const key of Object.keys(size)) { + if (size[key] !== 0 && types.has(key)) i++; + } + return i; +}; + +/** + * @param {Record} size size + * @param {Set} types types + * @returns {number} selective size sum + */ +const selectiveSizeSum = (size, types) => { + let sum = 0; + for (const key of Object.keys(size)) { + if (size[key] !== 0 && types.has(key)) sum += size[key]; + } + return sum; +}; + +/** + * @template T + */ +class Node { + /** + * @param {T} item item + * @param {string} key key + * @param {Record} size size + */ + constructor(item, key, size) { + this.item = item; + this.key = key; + this.size = size; + } +} + +/** + * @template T + */ +class Group { + /** + * @param {Node[]} nodes nodes + * @param {number[] | null} similarities similarities between the nodes (length = nodes.length - 1) + * @param {Record=} size size of the group + */ + constructor(nodes, similarities, size) { + this.nodes = nodes; + this.similarities = similarities; + this.size = size || sumSize(nodes); + /** @type {string | undefined} */ + this.key = undefined; + } + + /** + * @param {function(Node): boolean} filter filter function + * @returns {Node[] | undefined} removed nodes + */ + popNodes(filter) { + const newNodes = []; + const newSimilarities = []; + const resultNodes = []; + let lastNode; + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; + if (filter(node)) { + resultNodes.push(node); + } else { + if (newNodes.length > 0) { + newSimilarities.push( + lastNode === this.nodes[i - 1] + ? /** @type {number[]} */ (this.similarities)[i - 1] + : similarity(/** @type {Node} */ (lastNode).key, node.key) + ); + } + newNodes.push(node); + lastNode = node; + } + } + if (resultNodes.length === this.nodes.length) return; + this.nodes = newNodes; + this.similarities = newSimilarities; + this.size = sumSize(newNodes); + return resultNodes; + } +} + +/** + * @template T + * @param {Iterable>} nodes nodes + * @returns {number[]} similarities + */ +const getSimilarities = nodes => { + // calculate similarities between lexically adjacent nodes + /** @type {number[]} */ + const similarities = []; + let last; + for (const node of nodes) { + if (last !== undefined) { + similarities.push(similarity(last.key, node.key)); + } + last = node; + } + return similarities; +}; + +/** + * @template T + * @typedef {object} GroupedItems + * @property {string} key + * @property {T[]} items + * @property {Record} size + */ + +/** + * @template T + * @typedef {object} Options + * @property {Record} maxSize maximum size of a group + * @property {Record} minSize minimum size of a group (preferred over maximum size) + * @property {Iterable} items a list of items + * @property {function(T): Record} getSize function to get size of an item + * @property {function(T): string} getKey function to get the key of an item + */ + +/** + * @template T + * @param {Options} options options object + * @returns {GroupedItems[]} grouped items + */ +module.exports = ({ maxSize, minSize, items, getSize, getKey }) => { + /** @type {Group[]} */ + const result = []; + + const nodes = Array.from( + items, + item => new Node(item, getKey(item), getSize(item)) + ); + + /** @type {Node[]} */ + const initialNodes = []; + + // lexically ordering of keys + nodes.sort((a, b) => { + if (a.key < b.key) return -1; + if (a.key > b.key) return 1; + return 0; + }); + + // return nodes bigger than maxSize directly as group + // But make sure that minSize is not violated + for (const node of nodes) { + if (isTooBig(node.size, maxSize) && !isTooSmall(node.size, minSize)) { + result.push(new Group([node], [])); + } else { + initialNodes.push(node); + } + } + + if (initialNodes.length > 0) { + const initialGroup = new Group(initialNodes, getSimilarities(initialNodes)); + + /** + * @param {Group} group group + * @param {Record} consideredSize size of the group to consider + * @returns {boolean} true, if the group was modified + */ + const removeProblematicNodes = (group, consideredSize = group.size) => { + const problemTypes = getTooSmallTypes(consideredSize, minSize); + if (problemTypes.size > 0) { + // We hit an edge case where the working set is already smaller than minSize + // We merge problematic nodes with the smallest result node to keep minSize intact + const problemNodes = group.popNodes( + n => getNumberOfMatchingSizeTypes(n.size, problemTypes) > 0 + ); + if (problemNodes === undefined) return false; + // Only merge it with result nodes that have the problematic size type + const possibleResultGroups = result.filter( + n => getNumberOfMatchingSizeTypes(n.size, problemTypes) > 0 + ); + if (possibleResultGroups.length > 0) { + const bestGroup = possibleResultGroups.reduce((min, group) => { + const minMatches = getNumberOfMatchingSizeTypes(min, problemTypes); + const groupMatches = getNumberOfMatchingSizeTypes( + group, + problemTypes + ); + if (minMatches !== groupMatches) + return minMatches < groupMatches ? group : min; + if ( + selectiveSizeSum(min.size, problemTypes) > + selectiveSizeSum(group.size, problemTypes) + ) + return group; + return min; + }); + for (const node of problemNodes) bestGroup.nodes.push(node); + bestGroup.nodes.sort((a, b) => { + if (a.key < b.key) return -1; + if (a.key > b.key) return 1; + return 0; + }); + } else { + // There are no other nodes with the same size types + // We create a new group and have to accept that it's smaller than minSize + result.push(new Group(problemNodes, null)); + } + return true; + } + return false; + }; + + if (initialGroup.nodes.length > 0) { + const queue = [initialGroup]; + + while (queue.length) { + const group = /** @type {Group} */ (queue.pop()); + // only groups bigger than maxSize need to be splitted + if (!isTooBig(group.size, maxSize)) { + result.push(group); + continue; + } + // If the group is already too small + // we try to work only with the unproblematic nodes + if (removeProblematicNodes(group)) { + // This changed something, so we try this group again + queue.push(group); + continue; + } + + // find unsplittable area from left and right + // going minSize from left and right + // at least one node need to be included otherwise we get stuck + let left = 1; + const leftSize = Object.create(null); + addSizeTo(leftSize, group.nodes[0].size); + while (left < group.nodes.length && isTooSmall(leftSize, minSize)) { + addSizeTo(leftSize, group.nodes[left].size); + left++; + } + let right = group.nodes.length - 2; + const rightSize = Object.create(null); + addSizeTo(rightSize, group.nodes[group.nodes.length - 1].size); + while (right >= 0 && isTooSmall(rightSize, minSize)) { + addSizeTo(rightSize, group.nodes[right].size); + right--; + } + + // left v v right + // [ O O O ] O O O [ O O O ] + // ^^^^^^^^^ leftSize + // rightSize ^^^^^^^^^ + // leftSize > minSize + // rightSize > minSize + + // Perfect split: [ O O O ] [ O O O ] + // right === left - 1 + + if (left - 1 > right) { + // We try to remove some problematic nodes to "fix" that + let prevSize; + if (right < group.nodes.length - left) { + subtractSizeFrom(rightSize, group.nodes[right + 1].size); + prevSize = rightSize; + } else { + subtractSizeFrom(leftSize, group.nodes[left - 1].size); + prevSize = leftSize; + } + if (removeProblematicNodes(group, prevSize)) { + // This changed something, so we try this group again + queue.push(group); + continue; + } + // can't split group while holding minSize + // because minSize is preferred of maxSize we return + // the problematic nodes as result here even while it's too big + // To avoid this make sure maxSize > minSize * 3 + result.push(group); + continue; + } + if (left <= right) { + // when there is a area between left and right + // we look for best split point + // we split at the minimum similarity + // here key space is separated the most + // But we also need to make sure to not create too small groups + let best = -1; + let bestSimilarity = Infinity; + let pos = left; + const rightSize = sumSize(group.nodes.slice(pos)); + + // pos v v right + // [ O O O ] O O O [ O O O ] + // ^^^^^^^^^ leftSize + // rightSize ^^^^^^^^^^^^^^^ + + while (pos <= right + 1) { + const similarity = /** @type {number[]} */ (group.similarities)[ + pos - 1 + ]; + if ( + similarity < bestSimilarity && + !isTooSmall(leftSize, minSize) && + !isTooSmall(rightSize, minSize) + ) { + best = pos; + bestSimilarity = similarity; + } + addSizeTo(leftSize, group.nodes[pos].size); + subtractSizeFrom(rightSize, group.nodes[pos].size); + pos++; + } + if (best < 0) { + // This can't happen + // but if that assumption is wrong + // fallback to a big group + result.push(group); + continue; + } + left = best; + right = best - 1; + } + + // create two new groups for left and right area + // and queue them up + const rightNodes = [group.nodes[right + 1]]; + /** @type {number[]} */ + const rightSimilarities = []; + for (let i = right + 2; i < group.nodes.length; i++) { + rightSimilarities.push( + /** @type {number[]} */ (group.similarities)[i - 1] + ); + rightNodes.push(group.nodes[i]); + } + queue.push(new Group(rightNodes, rightSimilarities)); + + const leftNodes = [group.nodes[0]]; + /** @type {number[]} */ + const leftSimilarities = []; + for (let i = 1; i < left; i++) { + leftSimilarities.push( + /** @type {number[]} */ (group.similarities)[i - 1] + ); + leftNodes.push(group.nodes[i]); + } + queue.push(new Group(leftNodes, leftSimilarities)); + } + } + } + + // lexically ordering + result.sort((a, b) => { + if (a.nodes[0].key < b.nodes[0].key) return -1; + if (a.nodes[0].key > b.nodes[0].key) return 1; + return 0; + }); + + // give every group a name + const usedNames = new Set(); + for (let i = 0; i < result.length; i++) { + const group = result[i]; + if (group.nodes.length === 1) { + group.key = group.nodes[0].key; + } else { + const first = group.nodes[0]; + const last = group.nodes[group.nodes.length - 1]; + const name = getName(first.key, last.key, usedNames); + group.key = name; + } + } + + // return the results + return result.map( + group => + /** @type {GroupedItems} */ + ({ + key: group.key, + items: group.nodes.map(node => node.item), + size: group.size + }) + ); +}; diff --git a/webpack-lib/lib/util/extractUrlAndGlobal.js b/webpack-lib/lib/util/extractUrlAndGlobal.js new file mode 100644 index 00000000000..ade0a7cf25c --- /dev/null +++ b/webpack-lib/lib/util/extractUrlAndGlobal.js @@ -0,0 +1,18 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Sam Chen @chenxsan +*/ + +"use strict"; + +/** + * @param {string} urlAndGlobal the script request + * @returns {string[]} script url and its global variable + */ +module.exports = function extractUrlAndGlobal(urlAndGlobal) { + const index = urlAndGlobal.indexOf("@"); + if (index <= 0 || index === urlAndGlobal.length - 1) { + throw new Error(`Invalid request "${urlAndGlobal}"`); + } + return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)]; +}; diff --git a/webpack-lib/lib/util/findGraphRoots.js b/webpack-lib/lib/util/findGraphRoots.js new file mode 100644 index 00000000000..795f99055ff --- /dev/null +++ b/webpack-lib/lib/util/findGraphRoots.js @@ -0,0 +1,231 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const NO_MARKER = 0; +const IN_PROGRESS_MARKER = 1; +const DONE_MARKER = 2; +const DONE_MAYBE_ROOT_CYCLE_MARKER = 3; +const DONE_AND_ROOT_MARKER = 4; + +/** + * @template T + */ +class Node { + /** + * @param {T} item the value of the node + */ + constructor(item) { + this.item = item; + /** @type {Set>} */ + this.dependencies = new Set(); + this.marker = NO_MARKER; + /** @type {Cycle | undefined} */ + this.cycle = undefined; + this.incoming = 0; + } +} + +/** + * @template T + */ +class Cycle { + constructor() { + /** @type {Set>} */ + this.nodes = new Set(); + } +} + +/** + * @template T + * @typedef {object} StackEntry + * @property {Node} node + * @property {Node[]} openEdges + */ + +/** + * @template T + * @param {Iterable} items list of items + * @param {function(T): Iterable} getDependencies function to get dependencies of an item (items that are not in list are ignored) + * @returns {Iterable} graph roots of the items + */ +module.exports = (items, getDependencies) => { + /** @type {Map>} */ + const itemToNode = new Map(); + for (const item of items) { + const node = new Node(item); + itemToNode.set(item, node); + } + + // early exit when there is only a single item + if (itemToNode.size <= 1) return items; + + // grab all the dependencies + for (const node of itemToNode.values()) { + for (const dep of getDependencies(node.item)) { + const depNode = itemToNode.get(dep); + if (depNode !== undefined) { + node.dependencies.add(depNode); + } + } + } + + // Set of current root modules + // items will be removed if a new reference to it has been found + /** @type {Set>} */ + const roots = new Set(); + + // Set of current cycles without references to it + // cycles will be removed if a new reference to it has been found + // that is not part of the cycle + /** @type {Set>} */ + const rootCycles = new Set(); + + // For all non-marked nodes + for (const selectedNode of itemToNode.values()) { + if (selectedNode.marker === NO_MARKER) { + // deep-walk all referenced modules + // in a non-recursive way + + // start by entering the selected node + selectedNode.marker = IN_PROGRESS_MARKER; + + // keep a stack to avoid recursive walk + /** @type {StackEntry[]} */ + const stack = [ + { + node: selectedNode, + openEdges: Array.from(selectedNode.dependencies) + } + ]; + + // process the top item until stack is empty + while (stack.length > 0) { + const topOfStack = stack[stack.length - 1]; + + // Are there still edges unprocessed in the current node? + if (topOfStack.openEdges.length > 0) { + // Process one dependency + const dependency = + /** @type {Node} */ + (topOfStack.openEdges.pop()); + switch (dependency.marker) { + case NO_MARKER: + // dependency has not be visited yet + // mark it as in-progress and recurse + stack.push({ + node: dependency, + openEdges: Array.from(dependency.dependencies) + }); + dependency.marker = IN_PROGRESS_MARKER; + break; + case IN_PROGRESS_MARKER: { + // It's a in-progress cycle + let cycle = dependency.cycle; + if (!cycle) { + cycle = new Cycle(); + cycle.nodes.add(dependency); + dependency.cycle = cycle; + } + // set cycle property for each node in the cycle + // if nodes are already part of a cycle + // we merge the cycles to a shared cycle + for ( + let i = stack.length - 1; + stack[i].node !== dependency; + i-- + ) { + const node = stack[i].node; + if (node.cycle) { + if (node.cycle !== cycle) { + // merge cycles + for (const cycleNode of node.cycle.nodes) { + cycleNode.cycle = cycle; + cycle.nodes.add(cycleNode); + } + } + } else { + node.cycle = cycle; + cycle.nodes.add(node); + } + } + // don't recurse into dependencies + // these are already on the stack + break; + } + case DONE_AND_ROOT_MARKER: + // This node has be visited yet and is currently a root node + // But as this is a new reference to the node + // it's not really a root + // so we have to convert it to a normal node + dependency.marker = DONE_MARKER; + roots.delete(dependency); + break; + case DONE_MAYBE_ROOT_CYCLE_MARKER: + // This node has be visited yet and + // is maybe currently part of a completed root cycle + // we found a new reference to the cycle + // so it's not really a root cycle + // remove the cycle from the root cycles + // and convert it to a normal node + rootCycles.delete(/** @type {Cycle} */ (dependency.cycle)); + dependency.marker = DONE_MARKER; + break; + // DONE_MARKER: nothing to do, don't recurse into dependencies + } + } else { + // All dependencies of the current node has been visited + // we leave the node + stack.pop(); + topOfStack.node.marker = DONE_MARKER; + } + } + const cycle = selectedNode.cycle; + if (cycle) { + for (const node of cycle.nodes) { + node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER; + } + rootCycles.add(cycle); + } else { + selectedNode.marker = DONE_AND_ROOT_MARKER; + roots.add(selectedNode); + } + } + } + + // Extract roots from root cycles + // We take the nodes with most incoming edges + // inside of the cycle + for (const cycle of rootCycles) { + let max = 0; + /** @type {Set>} */ + const cycleRoots = new Set(); + const nodes = cycle.nodes; + for (const node of nodes) { + for (const dep of node.dependencies) { + if (nodes.has(dep)) { + dep.incoming++; + if (dep.incoming < max) continue; + if (dep.incoming > max) { + cycleRoots.clear(); + max = dep.incoming; + } + cycleRoots.add(dep); + } + } + } + for (const cycleRoot of cycleRoots) { + roots.add(cycleRoot); + } + } + + // When roots were found, return them + if (roots.size > 0) { + return Array.from(roots, r => r.item); + } + + throw new Error("Implementation of findGraphRoots is broken"); +}; diff --git a/webpack-lib/lib/util/fs.js b/webpack-lib/lib/util/fs.js new file mode 100644 index 00000000000..14a18c6dc7b --- /dev/null +++ b/webpack-lib/lib/util/fs.js @@ -0,0 +1,651 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const path = require("path"); + +/** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */ +/** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ + +/** + * @template T + * @typedef {object} IStatsBase + * @property {() => boolean} isFile + * @property {() => boolean} isDirectory + * @property {() => boolean} isBlockDevice + * @property {() => boolean} isCharacterDevice + * @property {() => boolean} isSymbolicLink + * @property {() => boolean} isFIFO + * @property {() => boolean} isSocket + * @property {T} dev + * @property {T} ino + * @property {T} mode + * @property {T} nlink + * @property {T} uid + * @property {T} gid + * @property {T} rdev + * @property {T} size + * @property {T} blksize + * @property {T} blocks + * @property {T} atimeMs + * @property {T} mtimeMs + * @property {T} ctimeMs + * @property {T} birthtimeMs + * @property {Date} atime + * @property {Date} mtime + * @property {Date} ctime + * @property {Date} birthtime + */ + +/** + * @typedef {IStatsBase} IStats + */ + +/** + * @typedef {IStatsBase & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats + */ + +/** + * @typedef {object} Dirent + * @property {() => boolean} isFile + * @property {() => boolean} isDirectory + * @property {() => boolean} isBlockDevice + * @property {() => boolean} isCharacterDevice + * @property {() => boolean} isSymbolicLink + * @property {() => boolean} isFIFO + * @property {() => boolean} isSocket + * @property {string} name + * @property {string} path + */ + +/** @typedef {string | number | boolean | null} JsonPrimitive */ +/** @typedef {JsonValue[]} JsonArray */ +/** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */ +/** @typedef {{[Key in string]: JsonValue} & {[Key in string]?: JsonValue | undefined}} JsonObject */ + +/** @typedef {function(NodeJS.ErrnoException | null): void} NoParamCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, string=): void} StringCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, Buffer=): void} BufferCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (string | Buffer)=): void} StringOrBufferCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (string[])=): void} ReaddirStringCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (Buffer[])=): void} ReaddirBufferCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (string[] | Buffer[])=): void} ReaddirStringOrBufferCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (Dirent[])=): void} ReaddirDirentCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, IStats=): void} StatsCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, IBigIntStats=): void} BigIntStatsCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, (IStats | IBigIntStats)=): void} StatsOrBigIntStatsCallback */ +/** @typedef {function(NodeJS.ErrnoException | null, number=): void} NumberCallback */ +/** @typedef {function(NodeJS.ErrnoException | Error | null, JsonObject=): void} ReadJsonCallback */ + +/** @typedef {Map} TimeInfoEntries */ + +/** + * @typedef {object} WatcherInfo + * @property {Set | null} changes get current aggregated changes that have not yet send to callback + * @property {Set | null} removals get current aggregated removals that have not yet send to callback + * @property {TimeInfoEntries} fileTimeInfoEntries get info about files + * @property {TimeInfoEntries} contextTimeInfoEntries get info about directories + */ + +/** @typedef {Set} Changes */ +/** @typedef {Set} Removals */ + +// TODO webpack 6 deprecate missing getInfo +/** + * @typedef {object} Watcher + * @property {function(): void} close closes the watcher and all underlying file watchers + * @property {function(): void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call + * @property {(function(): Changes | null)=} getAggregatedChanges get current aggregated changes that have not yet send to callback + * @property {(function(): Removals | null)=} getAggregatedRemovals get current aggregated removals that have not yet send to callback + * @property {function(): TimeInfoEntries} getFileTimeInfoEntries get info about files + * @property {function(): TimeInfoEntries} getContextTimeInfoEntries get info about directories + * @property {function(): WatcherInfo=} getInfo get info about timestamps and changes + */ + +/** + * @callback WatchMethod + * @param {Iterable} files watched files + * @param {Iterable} directories watched directories + * @param {Iterable} missing watched existence entries + * @param {number} startTime timestamp of start time + * @param {WatchOptions} options options object + * @param {function(Error | null, TimeInfoEntries=, TimeInfoEntries=, Changes=, Removals=): void} callback aggregated callback + * @param {function(string, number): void} callbackUndelayed callback when the first change was detected + * @returns {Watcher} a watcher + */ + +// TODO webpack 6 make optional methods required and avoid using non standard methods like `join`, `relative`, `dirname`, move IntermediateFileSystemExtras methods to InputFilesystem or OutputFilesystem + +/** + * @typedef {string | Buffer | URL} PathLike + */ + +/** + * @typedef {PathLike | number} PathOrFileDescriptor + */ + +/** + * @typedef {object} ObjectEncodingOptions + * @property {BufferEncoding | null | undefined} [encoding] + */ + +/** + * @typedef {{ + * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void; + * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void; + * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void; + * (path: PathOrFileDescriptor, callback: BufferCallback): void; + * }} ReadFile + */ + +/** + * @typedef {{ + * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer; + * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string; + * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer; + * }} ReadFileSync + */ + +/** + * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption + */ + +/** + * @typedef {'buffer'| { encoding: 'buffer' }} BufferEncodingOption + */ + +/** + * @typedef {object} StatOptions + * @property {(boolean | undefined)=} bigint + */ + +/** + * @typedef {object} StatSyncOptions + * @property {(boolean | undefined)=} bigint + * @property {(boolean | undefined)=} throwIfNoEntry + */ + +/** + * @typedef {{ + * (path: PathLike, options: EncodingOption, callback: StringCallback): void; + * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void; + * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void; + * (path: PathLike, callback: StringCallback): void; + * }} Readlink + */ + +/** + * @typedef {{ + * (path: PathLike, options?: EncodingOption): string; + * (path: PathLike, options: BufferEncodingOption): Buffer; + * (path: PathLike, options?: EncodingOption): string | Buffer; + * }} ReadlinkSync + */ + +/** + * @typedef {{ + * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: ReaddirStringCallback): void; + * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer', callback: ReaddirBufferCallback): void; + * (path: PathLike, callback: ReaddirStringCallback): void; + * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: ReaddirStringOrBufferCallback): void; + * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: ReaddirDirentCallback): void; + * }} Readdir + */ + +/** + * @typedef {{ + * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[]; + * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer'): Buffer[]; + * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[]; + * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[]; + * }} ReaddirSync + */ + +/** + * @typedef {{ + * (path: PathLike, callback: StatsCallback): void; + * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void; + * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void; + * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void; + * }} Stat + */ + +/** + * @typedef {{ + * (path: PathLike, options?: undefined): IStats; + * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined; + * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined; + * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats; + * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats; + * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats; + * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined; + * }} StatSync + */ + +/** + * @typedef {{ + * (path: PathLike, callback: StatsCallback): void; + * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void; + * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void; + * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void; + * }} LStat + */ + +/** + * @typedef {{ + * (path: PathLike, options?: undefined): IStats; + * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined; + * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined; + * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats; + * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats; + * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats; + * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined; + * }} LStatSync + */ + +/** + * @typedef {{ + * (path: PathLike, options: EncodingOption, callback: StringCallback): void; + * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void; + * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void; + * (path: PathLike, callback: StringCallback): void; + * }} RealPath + */ + +/** + * @typedef {{ + * (path: PathLike, options?: EncodingOption): string; + * (path: PathLike, options: BufferEncodingOption): Buffer; + * (path: PathLike, options?: EncodingOption): string | Buffer; + * }} RealPathSync + */ + +/** + * @typedef {function(PathOrFileDescriptor, ReadJsonCallback): void} ReadJson + */ + +/** + * @typedef {function(PathOrFileDescriptor): JsonObject} ReadJsonSync + */ + +/** + * @typedef {function((string | string[] | Set)=): void} Purge + */ + +/** + * @typedef {object} InputFileSystem + * @property {ReadFile} readFile + * @property {ReadFileSync=} readFileSync + * @property {Readlink} readlink + * @property {ReadlinkSync=} readlinkSync + * @property {Readdir} readdir + * @property {ReaddirSync=} readdirSync + * @property {Stat} stat + * @property {StatSync=} statSync + * @property {LStat=} lstat + * @property {LStatSync=} lstatSync + * @property {RealPath=} realpath + * @property {RealPathSync=} realpathSync + * @property {ReadJson=} readJson + * @property {ReadJsonSync=} readJsonSync + * @property {Purge=} purge + * @property {(function(string, string): string)=} join + * @property {(function(string, string): string)=} relative + * @property {(function(string): string)=} dirname + */ + +/** + * @typedef {number | string} Mode + */ + +/** + * @typedef {(ObjectEncodingOptions & import("events").Abortable & { mode?: Mode | undefined, flag?: string | undefined, flush?: boolean | undefined }) | BufferEncoding | null} WriteFileOptions + */ + +/** + * @typedef {{ + * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options: WriteFileOptions, callback: NoParamCallback): void; + * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, callback: NoParamCallback): void; + * }} WriteFile + */ + +/** + * @typedef {{ recursive?: boolean | undefined, mode?: Mode | undefined }} MakeDirectoryOptions + */ + +/** + * @typedef {{ + * (file: PathLike, options: MakeDirectoryOptions & { recursive: true }, callback: StringCallback): void; + * (file: PathLike, options: Mode | (MakeDirectoryOptions & { recursive?: false | undefined; }) | null | undefined, callback: NoParamCallback): void; + * (file: PathLike, options: Mode | MakeDirectoryOptions | null | undefined, callback: StringCallback): void; + * (file: PathLike, callback: NoParamCallback): void; + * }} Mkdir + */ + +/** + * @typedef {{ maxRetries?: number | undefined, recursive?: boolean | undefined, retryDelay?: number | undefined }} RmDirOptions + */ + +/** + * @typedef {{ + * (file: PathLike, callback: NoParamCallback): void; + * (file: PathLike, options: RmDirOptions, callback: NoParamCallback): void; + * }} Rmdir + */ + +/** + * @typedef {function(PathLike, NoParamCallback): void} Unlink + */ + +/** + * @typedef {object} OutputFileSystem + * @property {WriteFile} writeFile + * @property {Mkdir} mkdir + * @property {Readdir=} readdir + * @property {Rmdir=} rmdir + * @property {Unlink=} unlink + * @property {Stat} stat + * @property {LStat=} lstat + * @property {ReadFile} readFile + * @property {(function(string, string): string)=} join + * @property {(function(string, string): string)=} relative + * @property {(function(string): string)=} dirname + */ + +/** + * @typedef {object} WatchFileSystem + * @property {WatchMethod} watch + */ + +/** + * @typedef {{ + * (path: PathLike, options: MakeDirectoryOptions & { recursive: true }): string | undefined; + * (path: PathLike, options?: Mode | (MakeDirectoryOptions & { recursive?: false | undefined }) | null): void; + * (path: PathLike, options?: Mode | MakeDirectoryOptions | null): string | undefined; + * }} MkdirSync + */ + +/** + * @typedef {object} StreamOptions + * @property {(string | undefined)=} flags + * @property {(BufferEncoding | undefined)} encoding + * @property {(number | EXPECTED_ANY | undefined)=} fd + * @property {(number | undefined)=} mode + * @property {(boolean | undefined)=} autoClose + * @property {(boolean | undefined)=} emitClose + * @property {(number | undefined)=} start + * @property {(AbortSignal | null | undefined)=} signal + */ + +/** + * @typedef {object} FSImplementation + * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} open + * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} close + */ + +/** + * @typedef {FSImplementation & { write: (...args: EXPECTED_ANY[]) => EXPECTED_ANY; close?: (...args: EXPECTED_ANY[]) => EXPECTED_ANY }} CreateWriteStreamFSImplementation + */ + +/** + * @typedef {StreamOptions & { fs?: CreateWriteStreamFSImplementation | null | undefined }} WriteStreamOptions + */ + +/** + * @typedef {function(PathLike, (BufferEncoding | WriteStreamOptions)=): NodeJS.WritableStream} CreateWriteStream + */ + +/** + * @typedef {number | string} OpenMode + */ + +/** + * @typedef {{ + * (file: PathLike, flags: OpenMode | undefined, mode: Mode | undefined | null, callback: NumberCallback): void; + * (file: PathLike, flags: OpenMode | undefined, callback: NumberCallback): void; + * (file: PathLike, callback: NumberCallback): void; + * }} Open + */ + +/** + * @typedef {number | bigint} ReadPosition + */ + +/** + * @typedef {object} ReadSyncOptions + * @property {(number | undefined)=} offset + * @property {(number | undefined)=} length + * @property {(ReadPosition | null | undefined)=} position + */ + +/** + * @template {NodeJS.ArrayBufferView} TBuffer + * @typedef {object} ReadAsyncOptions + * @property {(number | undefined)=} offset + * @property {(number | undefined)=} length + * @property {(ReadPosition | null | undefined)=} position + * @property {TBuffer=} buffer + */ + +/** + * @template {NodeJS.ArrayBufferView} [TBuffer=Buffer] + * @typedef {{ + * (fd: number, buffer: TBuffer, offset: number, length: number, position: ReadPosition | null, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void; + * (fd: number, options: ReadAsyncOptions, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void; + * (fd: number, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: NodeJS.ArrayBufferView) => void): void; + * }} Read + */ + +/** @typedef {function(number, NoParamCallback): void} Close */ + +/** @typedef {function(PathLike, PathLike, NoParamCallback): void} Rename */ + +/** + * @typedef {object} IntermediateFileSystemExtras + * @property {MkdirSync} mkdirSync + * @property {CreateWriteStream} createWriteStream + * @property {Open} open + * @property {Read} read + * @property {Close} close + * @property {Rename} rename + */ + +/** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */ + +/** + * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system + * @param {string} rootPath the root path + * @param {string} targetPath the target path + * @returns {string} location of targetPath relative to rootPath + */ +const relative = (fs, rootPath, targetPath) => { + if (fs && fs.relative) { + return fs.relative(rootPath, targetPath); + } else if (path.posix.isAbsolute(rootPath)) { + return path.posix.relative(rootPath, targetPath); + } else if (path.win32.isAbsolute(rootPath)) { + return path.win32.relative(rootPath, targetPath); + } + throw new Error( + `${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system` + ); +}; +module.exports.relative = relative; + +/** + * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system + * @param {string} rootPath a path + * @param {string} filename a filename + * @returns {string} the joined path + */ +const join = (fs, rootPath, filename) => { + if (fs && fs.join) { + return fs.join(rootPath, filename); + } else if (path.posix.isAbsolute(rootPath)) { + return path.posix.join(rootPath, filename); + } else if (path.win32.isAbsolute(rootPath)) { + return path.win32.join(rootPath, filename); + } + throw new Error( + `${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system` + ); +}; +module.exports.join = join; + +/** + * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system + * @param {string} absPath an absolute path + * @returns {string} the parent directory of the absolute path + */ +const dirname = (fs, absPath) => { + if (fs && fs.dirname) { + return fs.dirname(absPath); + } else if (path.posix.isAbsolute(absPath)) { + return path.posix.dirname(absPath); + } else if (path.win32.isAbsolute(absPath)) { + return path.win32.dirname(absPath); + } + throw new Error( + `${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system` + ); +}; +module.exports.dirname = dirname; + +/** + * @param {OutputFileSystem} fs a file system + * @param {string} p an absolute path + * @param {function(Error=): void} callback callback function for the error + * @returns {void} + */ +const mkdirp = (fs, p, callback) => { + fs.mkdir(p, err => { + if (err) { + if (err.code === "ENOENT") { + const dir = dirname(fs, p); + if (dir === p) { + callback(err); + return; + } + mkdirp(fs, dir, err => { + if (err) { + callback(err); + return; + } + fs.mkdir(p, err => { + if (err) { + if (err.code === "EEXIST") { + callback(); + return; + } + callback(err); + return; + } + callback(); + }); + }); + return; + } else if (err.code === "EEXIST") { + callback(); + return; + } + callback(err); + return; + } + callback(); + }); +}; +module.exports.mkdirp = mkdirp; + +/** + * @param {IntermediateFileSystem} fs a file system + * @param {string} p an absolute path + * @returns {void} + */ +const mkdirpSync = (fs, p) => { + try { + fs.mkdirSync(p); + } catch (err) { + if (err) { + if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") { + const dir = dirname(fs, p); + if (dir === p) { + throw err; + } + mkdirpSync(fs, dir); + fs.mkdirSync(p); + return; + } else if (/** @type {NodeJS.ErrnoException} */ (err).code === "EEXIST") { + return; + } + throw err; + } + } +}; +module.exports.mkdirpSync = mkdirpSync; + +/** + * @param {InputFileSystem} fs a file system + * @param {string} p an absolute path + * @param {ReadJsonCallback} callback callback + * @returns {void} + */ +const readJson = (fs, p, callback) => { + if ("readJson" in fs) + return /** @type {NonNullable} */ ( + fs.readJson + )(p, callback); + fs.readFile(p, (err, buf) => { + if (err) return callback(err); + let data; + try { + data = JSON.parse(/** @type {Buffer} */ (buf).toString("utf-8")); + } catch (err1) { + return callback(/** @type {Error} */ (err1)); + } + return callback(null, data); + }); +}; +module.exports.readJson = readJson; + +/** + * @param {InputFileSystem} fs a file system + * @param {string} p an absolute path + * @param {function(NodeJS.ErrnoException | Error | null, (IStats | string)=): void} callback callback + * @returns {void} + */ +const lstatReadlinkAbsolute = (fs, p, callback) => { + let i = 3; + const doReadLink = () => { + fs.readlink(p, (err, target) => { + if (err && --i > 0) { + // It might was just changed from symlink to file + // we retry 2 times to catch this case before throwing the error + return doStat(); + } + if (err) return callback(err); + const value = /** @type {string} */ (target).toString(); + callback(null, join(fs, dirname(fs, p), value)); + }); + }; + const doStat = () => { + if ("lstat" in fs) { + return /** @type {NonNullable} */ (fs.lstat)( + p, + (err, stats) => { + if (err) return callback(err); + if (/** @type {IStats} */ (stats).isSymbolicLink()) { + return doReadLink(); + } + callback(null, stats); + } + ); + } + return fs.stat(p, callback); + }; + if ("lstat" in fs) return doStat(); + doReadLink(); +}; +module.exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute; diff --git a/webpack-lib/lib/util/generateDebugId.js b/webpack-lib/lib/util/generateDebugId.js new file mode 100644 index 00000000000..bd501f89a2d --- /dev/null +++ b/webpack-lib/lib/util/generateDebugId.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +const createHash = require("./createHash"); + +/** + * @param {string | Buffer} content content + * @param {string} file file + * @returns {string} generated debug id + */ +module.exports = (content, file) => { + // We need a uuid which is 128 bits so we need 2x 64 bit hashes. + // The first 64 bits is a hash of the source. + const sourceHash = createHash("xxhash64").update(content).digest("hex"); + // The next 64 bits is a hash of the filename and sourceHash + const hash128 = `${sourceHash}${createHash("xxhash64") + .update(file) + .update(sourceHash) + .digest("hex")}`; + + return [ + hash128.slice(0, 8), + hash128.slice(8, 12), + `4${hash128.slice(12, 15)}`, + ((Number.parseInt(hash128.slice(15, 16), 16) & 3) | 8).toString(16) + + hash128.slice(17, 20), + hash128.slice(20, 32) + ].join("-"); +}; diff --git a/webpack-lib/lib/util/hash/BatchedHash.js b/webpack-lib/lib/util/hash/BatchedHash.js new file mode 100644 index 00000000000..cc030f8bd7d --- /dev/null +++ b/webpack-lib/lib/util/hash/BatchedHash.js @@ -0,0 +1,71 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Hash = require("../Hash"); +const MAX_SHORT_STRING = require("./wasm-hash").MAX_SHORT_STRING; + +class BatchedHash extends Hash { + /** + * @param {Hash} hash hash + */ + constructor(hash) { + super(); + this.string = undefined; + this.encoding = undefined; + this.hash = hash; + } + + /** + * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} + * @param {string|Buffer} data data + * @param {string=} inputEncoding data encoding + * @returns {this} updated hash + */ + update(data, inputEncoding) { + if (this.string !== undefined) { + if ( + typeof data === "string" && + inputEncoding === this.encoding && + this.string.length + data.length < MAX_SHORT_STRING + ) { + this.string += data; + return this; + } + this.hash.update(this.string, this.encoding); + this.string = undefined; + } + if (typeof data === "string") { + if ( + data.length < MAX_SHORT_STRING && + // base64 encoding is not valid since it may contain padding chars + (!inputEncoding || !inputEncoding.startsWith("ba")) + ) { + this.string = data; + this.encoding = inputEncoding; + } else { + this.hash.update(data, inputEncoding); + } + } else { + this.hash.update(data); + } + return this; + } + + /** + * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} + * @param {string=} encoding encoding of the return value + * @returns {string|Buffer} digest + */ + digest(encoding) { + if (this.string !== undefined) { + this.hash.update(this.string, this.encoding); + } + return this.hash.digest(encoding); + } +} + +module.exports = BatchedHash; diff --git a/webpack-lib/lib/util/hash/md4.js b/webpack-lib/lib/util/hash/md4.js new file mode 100644 index 00000000000..425edc3b9ba --- /dev/null +++ b/webpack-lib/lib/util/hash/md4.js @@ -0,0 +1,20 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const create = require("./wasm-hash"); + +// #region wasm code: md4 (../../../assembly/hash/md4.asm.ts) --initialMemory 1 +const md4 = new WebAssembly.Module( + Buffer.from( + // 2154 bytes + "AGFzbQEAAAABCAJgAX8AYAAAAwUEAQAAAAUDAQABBhoFfwFBAAt/AUEAC38BQQALfwFBAAt/AUEACwciBARpbml0AAAGdXBkYXRlAAIFZmluYWwAAwZtZW1vcnkCAAqJEAQmAEGBxpS6BiQBQYnXtv5+JAJB/rnrxXkkA0H2qMmBASQEQQAkAAvQCgEZfyMBIQUjAiECIwMhAyMEIQQDQCAAIAFLBEAgASgCBCIOIAQgAyABKAIAIg8gBSAEIAIgAyAEc3FzampBA3ciCCACIANzcXNqakEHdyEJIAEoAgwiBiACIAggASgCCCIQIAMgAiAJIAIgCHNxc2pqQQt3IgogCCAJc3FzampBE3chCyABKAIUIgcgCSAKIAEoAhAiESAIIAkgCyAJIApzcXNqakEDdyIMIAogC3Nxc2pqQQd3IQ0gASgCHCIJIAsgDCABKAIYIgggCiALIA0gCyAMc3FzampBC3ciEiAMIA1zcXNqakETdyETIAEoAiQiFCANIBIgASgCICIVIAwgDSATIA0gEnNxc2pqQQN3IgwgEiATc3FzampBB3chDSABKAIsIgsgEyAMIAEoAigiCiASIBMgDSAMIBNzcXNqakELdyISIAwgDXNxc2pqQRN3IRMgASgCNCIWIA0gEiABKAIwIhcgDCANIBMgDSASc3FzampBA3ciGCASIBNzcXNqakEHdyEZIBggASgCPCINIBMgGCABKAI4IgwgEiATIBkgEyAYc3FzampBC3ciEiAYIBlzcXNqakETdyITIBIgGXJxIBIgGXFyaiAPakGZ84nUBWpBA3ciGCATIBIgGSAYIBIgE3JxIBIgE3FyaiARakGZ84nUBWpBBXciEiATIBhycSATIBhxcmogFWpBmfOJ1AVqQQl3IhMgEiAYcnEgEiAYcXJqIBdqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAOakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAHakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogFGpBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIBZqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAQakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAIakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogCmpBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIAxqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAGakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAJakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogC2pBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIA1qQZnzidQFakENdyIYIBNzIBJzaiAPakGh1+f2BmpBA3ciDyAYIBMgEiAPIBhzIBNzaiAVakGh1+f2BmpBCXciEiAPcyAYc2ogEWpBodfn9gZqQQt3IhEgEnMgD3NqIBdqQaHX5/YGakEPdyIPIBFzIBJzaiAQakGh1+f2BmpBA3ciECAPIBEgEiAPIBBzIBFzaiAKakGh1+f2BmpBCXciCiAQcyAPc2ogCGpBodfn9gZqQQt3IgggCnMgEHNqIAxqQaHX5/YGakEPdyIMIAhzIApzaiAOakGh1+f2BmpBA3ciDiAMIAggCiAMIA5zIAhzaiAUakGh1+f2BmpBCXciCCAOcyAMc2ogB2pBodfn9gZqQQt3IgcgCHMgDnNqIBZqQaHX5/YGakEPdyIKIAdzIAhzaiAGakGh1+f2BmpBA3ciBiAFaiEFIAIgCiAHIAggBiAKcyAHc2ogC2pBodfn9gZqQQl3IgcgBnMgCnNqIAlqQaHX5/YGakELdyIIIAdzIAZzaiANakGh1+f2BmpBD3dqIQIgAyAIaiEDIAQgB2ohBCABQUBrIQEMAQsLIAUkASACJAIgAyQDIAQkBAsNACAAEAEjACAAaiQAC/8EAgN/AX4jACAAaq1CA4YhBCAAQcgAakFAcSICQQhrIQMgACIBQQFqIQAgAUGAAToAAANAIAAgAklBACAAQQdxGwRAIABBADoAACAAQQFqIQAMAQsLA0AgACACSQRAIABCADcDACAAQQhqIQAMAQsLIAMgBDcDACACEAFBACMBrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBCCMCrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBECMDrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBGCMErSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwAL", + "base64" + ) +); +// #endregion + +module.exports = create.bind(null, md4, [], 64, 32); diff --git a/webpack-lib/lib/util/hash/wasm-hash.js b/webpack-lib/lib/util/hash/wasm-hash.js new file mode 100644 index 00000000000..0c70b96158c --- /dev/null +++ b/webpack-lib/lib/util/hash/wasm-hash.js @@ -0,0 +1,174 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +// 65536 is the size of a wasm memory page +// 64 is the maximum chunk size for every possible wasm hash implementation +// 4 is the maximum number of bytes per char for string encoding (max is utf-8) +// ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64 +const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3; + +class WasmHash { + /** + * @param {WebAssembly.Instance} instance wasm instance + * @param {WebAssembly.Instance[]} instancesPool pool of instances + * @param {number} chunkSize size of data chunks passed to wasm + * @param {number} digestSize size of digest returned by wasm + */ + constructor(instance, instancesPool, chunkSize, digestSize) { + const exports = /** @type {any} */ (instance.exports); + exports.init(); + this.exports = exports; + this.mem = Buffer.from(exports.memory.buffer, 0, 65536); + this.buffered = 0; + this.instancesPool = instancesPool; + this.chunkSize = chunkSize; + this.digestSize = digestSize; + } + + reset() { + this.buffered = 0; + this.exports.init(); + } + + /** + * @param {Buffer | string} data data + * @param {BufferEncoding=} encoding encoding + * @returns {this} itself + */ + update(data, encoding) { + if (typeof data === "string") { + while (data.length > MAX_SHORT_STRING) { + this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding); + data = data.slice(MAX_SHORT_STRING); + } + this._updateWithShortString(data, encoding); + return this; + } + this._updateWithBuffer(data); + return this; + } + + /** + * @param {string} data data + * @param {BufferEncoding=} encoding encoding + * @returns {void} + */ + _updateWithShortString(data, encoding) { + const { exports, buffered, mem, chunkSize } = this; + let endPos; + if (data.length < 70) { + if (!encoding || encoding === "utf-8" || encoding === "utf8") { + endPos = buffered; + for (let i = 0; i < data.length; i++) { + const cc = data.charCodeAt(i); + if (cc < 0x80) mem[endPos++] = cc; + else if (cc < 0x800) { + mem[endPos] = (cc >> 6) | 0xc0; + mem[endPos + 1] = (cc & 0x3f) | 0x80; + endPos += 2; + } else { + // bail-out for weird chars + endPos += mem.write(data.slice(i), endPos, encoding); + break; + } + } + } else if (encoding === "latin1") { + endPos = buffered; + for (let i = 0; i < data.length; i++) { + const cc = data.charCodeAt(i); + mem[endPos++] = cc; + } + } else { + endPos = buffered + mem.write(data, buffered, encoding); + } + } else { + endPos = buffered + mem.write(data, buffered, encoding); + } + if (endPos < chunkSize) { + this.buffered = endPos; + } else { + const l = endPos & ~(this.chunkSize - 1); + exports.update(l); + const newBuffered = endPos - l; + this.buffered = newBuffered; + if (newBuffered > 0) mem.copyWithin(0, l, endPos); + } + } + + /** + * @param {Buffer} data data + * @returns {void} + */ + _updateWithBuffer(data) { + const { exports, buffered, mem } = this; + const length = data.length; + if (buffered + length < this.chunkSize) { + data.copy(mem, buffered, 0, length); + this.buffered += length; + } else { + const l = (buffered + length) & ~(this.chunkSize - 1); + if (l > 65536) { + let i = 65536 - buffered; + data.copy(mem, buffered, 0, i); + exports.update(65536); + const stop = l - buffered - 65536; + while (i < stop) { + data.copy(mem, 0, i, i + 65536); + exports.update(65536); + i += 65536; + } + data.copy(mem, 0, i, l - buffered); + exports.update(l - buffered - i); + } else { + data.copy(mem, buffered, 0, l - buffered); + exports.update(l); + } + const newBuffered = length + buffered - l; + this.buffered = newBuffered; + if (newBuffered > 0) data.copy(mem, 0, length - newBuffered, length); + } + } + + /** + * @param {BufferEncoding} type type + * @returns {Buffer | string} digest + */ + digest(type) { + const { exports, buffered, mem, digestSize } = this; + exports.final(buffered); + this.instancesPool.push(this); + const hex = mem.toString("latin1", 0, digestSize); + if (type === "hex") return hex; + if (type === "binary" || !type) return Buffer.from(hex, "hex"); + return Buffer.from(hex, "hex").toString(type); + } +} + +/** + * @param {TODO} wasmModule wasm module + * @param {WasmHash[]} instancesPool pool of instances + * @param {number} chunkSize size of data chunks passed to wasm + * @param {number} digestSize size of digest returned by wasm + * @returns {WasmHash} wasm hash + */ +const create = (wasmModule, instancesPool, chunkSize, digestSize) => { + if (instancesPool.length > 0) { + const old = /** @type {WasmHash} */ (instancesPool.pop()); + old.reset(); + return old; + } + + return new WasmHash( + new WebAssembly.Instance(wasmModule), + instancesPool, + chunkSize, + digestSize + ); +}; + +module.exports = create; +module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING; diff --git a/webpack-lib/lib/util/hash/xxhash64.js b/webpack-lib/lib/util/hash/xxhash64.js new file mode 100644 index 00000000000..b9262b8753c --- /dev/null +++ b/webpack-lib/lib/util/hash/xxhash64.js @@ -0,0 +1,20 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const create = require("./wasm-hash"); + +// #region wasm code: xxhash64 (../../../assembly/hash/xxhash64.asm.ts) --initialMemory 1 +const xxhash64 = new WebAssembly.Module( + Buffer.from( + // 1160 bytes + "AGFzbQEAAAABCAJgAX8AYAAAAwQDAQAABQMBAAEGGgV+AUIAC34BQgALfgFCAAt+AUIAC34BQgALByIEBGluaXQAAAZ1cGRhdGUAAQVmaW5hbAACBm1lbW9yeQIACqgIAzAAQtbrgu7q/Yn14AAkAELP1tO+0ser2UIkAUIAJAJC+erQ0OfJoeThACQDQgAkBAvUAQIBfwR+IABFBEAPCyMEIACtfCQEIwAhAiMBIQMjAiEEIwMhBQNAIAIgASkDAELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiECIAMgASkDCELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEDIAQgASkDEELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEEIAUgASkDGELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEFIAFBIGoiASAASQ0ACyACJAAgAyQBIAQkAiAFJAMLngYCAn8CfiMEQgBSBH4jAEIBiSMBQgeJfCMCQgyJfCMDQhKJfCMAQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IwFCz9bTvtLHq9lCfkIfiUKHla+vmLbem55/foVCh5Wvr5i23puef35CnaO16oOxjYr6AH0jAkLP1tO+0ser2UJ+Qh+JQoeVr6+Ytt6bnn9+hUKHla+vmLbem55/fkKdo7Xqg7GNivoAfSMDQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9BULFz9my8eW66icLIwQgAK18fCEDA0AgAUEIaiICIABNBEAgAyABKQMAQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQhuJQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IQMgAiEBDAELCyABQQRqIgIgAE0EQCADIAE1AgBCh5Wvr5i23puef36FQheJQs/W077Sx6vZQn5C+fPd8Zn2masWfCEDIAIhAQsDQCAAIAFHBEAgAyABMQAAQsXP2bLx5brqJ36FQguJQoeVr6+Ytt6bnn9+IQMgAUEBaiEBDAELC0EAIAMgA0IhiIVCz9bTvtLHq9lCfiIDQh2IIAOFQvnz3fGZ9pmrFn4iA0IgiCADhSIDQiCIIgRC//8Dg0IghiAEQoCA/P8Pg0IQiIQiBEL/gYCA8B+DQhCGIARCgP6DgIDgP4NCCIiEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAEEIIANC/////w+DIgNC//8Dg0IghiADQoCA/P8Pg0IQiIQiA0L/gYCA8B+DQhCGIANCgP6DgIDgP4NCCIiEIgNCj4C8gPCBwAeDQgiGIANC8IHAh4CegPgAg0IEiIQiA0KGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gA0Kw4MCBg4aMmDCEfDcDAAs=", + "base64" + ) +); +// #endregion + +module.exports = create.bind(null, xxhash64, [], 32, 16); diff --git a/webpack-lib/lib/util/identifier.js b/webpack-lib/lib/util/identifier.js new file mode 100644 index 00000000000..e94a63b5034 --- /dev/null +++ b/webpack-lib/lib/util/identifier.js @@ -0,0 +1,403 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const path = require("path"); + +const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; +const SEGMENTS_SPLIT_REGEXP = /([|!])/; +const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; + +/** + * @typedef {object} MakeRelativePathsCache + * @property {Map>=} relativePaths + */ + +/** + * @param {string} relativePath relative path + * @returns {string} request + */ +const relativePathToRequest = relativePath => { + if (relativePath === "") return "./."; + if (relativePath === "..") return "../."; + if (relativePath.startsWith("../")) return relativePath; + return `./${relativePath}`; +}; + +/** + * @param {string} context context for relative path + * @param {string} maybeAbsolutePath path to make relative + * @returns {string} relative path in request style + */ +const absoluteToRequest = (context, maybeAbsolutePath) => { + if (maybeAbsolutePath[0] === "/") { + if ( + maybeAbsolutePath.length > 1 && + maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/" + ) { + // this 'path' is actually a regexp generated by dynamic requires. + // Don't treat it as an absolute path. + return maybeAbsolutePath; + } + + const querySplitPos = maybeAbsolutePath.indexOf("?"); + let resource = + querySplitPos === -1 + ? maybeAbsolutePath + : maybeAbsolutePath.slice(0, querySplitPos); + resource = relativePathToRequest(path.posix.relative(context, resource)); + return querySplitPos === -1 + ? resource + : resource + maybeAbsolutePath.slice(querySplitPos); + } + + if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { + const querySplitPos = maybeAbsolutePath.indexOf("?"); + let resource = + querySplitPos === -1 + ? maybeAbsolutePath + : maybeAbsolutePath.slice(0, querySplitPos); + resource = path.win32.relative(context, resource); + if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { + resource = relativePathToRequest( + resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/") + ); + } + return querySplitPos === -1 + ? resource + : resource + maybeAbsolutePath.slice(querySplitPos); + } + + // not an absolute path + return maybeAbsolutePath; +}; + +/** + * @param {string} context context for relative path + * @param {string} relativePath path + * @returns {string} absolute path + */ +const requestToAbsolute = (context, relativePath) => { + if (relativePath.startsWith("./") || relativePath.startsWith("../")) + return path.join(context, relativePath); + return relativePath; +}; + +/** + * @template T + * @typedef {function(string, object=): T} MakeCacheableResult + */ + +/** + * @template T + * @typedef {function(string): T} BindCacheResultFn + */ + +/** + * @template T + * @typedef {function(object): BindCacheResultFn} BindCache + */ + +/** + * @template T + * @param {(function(string): T)} realFn real function + * @returns {MakeCacheableResult & { bindCache: BindCache }} cacheable function + */ +const makeCacheable = realFn => { + /** + * @template T + * @typedef {Map} CacheItem + */ + /** @type {WeakMap>} */ + const cache = new WeakMap(); + + /** + * @param {object} associatedObjectForCache an object to which the cache will be attached + * @returns {CacheItem} cache item + */ + const getCache = associatedObjectForCache => { + const entry = cache.get(associatedObjectForCache); + if (entry !== undefined) return entry; + /** @type {Map} */ + const map = new Map(); + cache.set(associatedObjectForCache, map); + return map; + }; + + /** @type {MakeCacheableResult & { bindCache: BindCache }} */ + const fn = (str, associatedObjectForCache) => { + if (!associatedObjectForCache) return realFn(str); + const cache = getCache(associatedObjectForCache); + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + + /** @type {BindCache} */ + fn.bindCache = associatedObjectForCache => { + const cache = getCache(associatedObjectForCache); + /** + * @param {string} str string + * @returns {T} value + */ + return str => { + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + }; + + return fn; +}; + +/** @typedef {function(string, string, object=): string} MakeCacheableWithContextResult */ +/** @typedef {function(string, string): string} BindCacheForContextResultFn */ +/** @typedef {function(string): string} BindContextCacheForContextResultFn */ +/** @typedef {function(object=): BindCacheForContextResultFn} BindCacheForContext */ +/** @typedef {function(string, object=): BindContextCacheForContextResultFn} BindContextCacheForContext */ + +/** + * @param {function(string, string): string} fn function + * @returns {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} cacheable function with context + */ +const makeCacheableWithContext = fn => { + /** @type {WeakMap>>} */ + const cache = new WeakMap(); + + /** @type {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} */ + const cachedFn = (context, identifier, associatedObjectForCache) => { + if (!associatedObjectForCache) return fn(context, identifier); + + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + let cachedResult; + let innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } else { + cachedResult = innerSubCache.get(identifier); + } + + if (cachedResult !== undefined) { + return cachedResult; + } + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + }; + + /** @type {BindCacheForContext} */ + cachedFn.bindCache = associatedObjectForCache => { + let innerCache; + if (associatedObjectForCache) { + innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + } else { + innerCache = new Map(); + } + + /** + * @param {string} context context used to create relative path + * @param {string} identifier identifier used to create relative path + * @returns {string} the returned relative path + */ + const boundFn = (context, identifier) => { + let cachedResult; + let innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } else { + cachedResult = innerSubCache.get(identifier); + } + + if (cachedResult !== undefined) { + return cachedResult; + } + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + }; + + return boundFn; + }; + + /** @type {BindContextCacheForContext} */ + cachedFn.bindContextCache = (context, associatedObjectForCache) => { + let innerSubCache; + if (associatedObjectForCache) { + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } + } else { + innerSubCache = new Map(); + } + + /** + * @param {string} identifier identifier used to create relative path + * @returns {string} the returned relative path + */ + const boundFn = identifier => { + const cachedResult = innerSubCache.get(identifier); + if (cachedResult !== undefined) { + return cachedResult; + } + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + }; + + return boundFn; + }; + + return cachedFn; +}; + +/** + * @param {string} context context for relative path + * @param {string} identifier identifier for path + * @returns {string} a converted relative path + */ +const _makePathsRelative = (context, identifier) => + identifier + .split(SEGMENTS_SPLIT_REGEXP) + .map(str => absoluteToRequest(context, str)) + .join(""); + +module.exports.makePathsRelative = makeCacheableWithContext(_makePathsRelative); + +/** + * @param {string} context context for relative path + * @param {string} identifier identifier for path + * @returns {string} a converted relative path + */ +const _makePathsAbsolute = (context, identifier) => + identifier + .split(SEGMENTS_SPLIT_REGEXP) + .map(str => requestToAbsolute(context, str)) + .join(""); + +module.exports.makePathsAbsolute = makeCacheableWithContext(_makePathsAbsolute); + +/** + * @param {string} context absolute context path + * @param {string} request any request string may containing absolute paths, query string, etc. + * @returns {string} a new request string avoiding absolute paths when possible + */ +const _contextify = (context, request) => + request + .split("!") + .map(r => absoluteToRequest(context, r)) + .join("!"); + +const contextify = makeCacheableWithContext(_contextify); +module.exports.contextify = contextify; + +/** + * @param {string} context absolute context path + * @param {string} request any request string + * @returns {string} a new request string using absolute paths when possible + */ +const _absolutify = (context, request) => + request + .split("!") + .map(r => requestToAbsolute(context, r)) + .join("!"); + +const absolutify = makeCacheableWithContext(_absolutify); +module.exports.absolutify = absolutify; + +const PATH_QUERY_FRAGMENT_REGEXP = + /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; +const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/; + +/** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ +/** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */ + +/** + * @param {string} str the path with query and fragment + * @returns {ParsedResource} parsed parts + */ +const _parseResource = str => { + const match = + /** @type {[string, string, string | undefined, string | undefined]} */ + (/** @type {unknown} */ (PATH_QUERY_FRAGMENT_REGEXP.exec(str))); + return { + resource: str, + path: match[1].replace(/\0(.)/g, "$1"), + query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", + fragment: match[3] || "" + }; +}; +module.exports.parseResource = makeCacheable(_parseResource); + +/** + * Parse resource, skips fragment part + * @param {string} str the path with query and fragment + * @returns {ParsedResourceWithoutFragment} parsed parts + */ +const _parseResourceWithoutFragment = str => { + const match = + /** @type {[string, string, string | undefined]} */ + (/** @type {unknown} */ (PATH_QUERY_REGEXP.exec(str))); + return { + resource: str, + path: match[1].replace(/\0(.)/g, "$1"), + query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "" + }; +}; +module.exports.parseResourceWithoutFragment = makeCacheable( + _parseResourceWithoutFragment +); + +/** + * @param {string} filename the filename which should be undone + * @param {string} outputPath the output path that is restored (only relevant when filename contains "..") + * @param {boolean} enforceRelative true returns ./ for empty paths + * @returns {string} repeated ../ to leave the directory of the provided filename to be back on output dir + */ +module.exports.getUndoPath = (filename, outputPath, enforceRelative) => { + let depth = -1; + let append = ""; + outputPath = outputPath.replace(/[\\/]$/, ""); + for (const part of filename.split(/[/\\]+/)) { + if (part === "..") { + if (depth > -1) { + depth--; + } else { + const i = outputPath.lastIndexOf("/"); + const j = outputPath.lastIndexOf("\\"); + const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j); + if (pos < 0) return `${outputPath}/`; + append = `${outputPath.slice(pos + 1)}/${append}`; + outputPath = outputPath.slice(0, pos); + } + } else if (part !== ".") { + depth++; + } + } + return depth > 0 + ? `${"../".repeat(depth)}${append}` + : enforceRelative + ? `./${append}` + : append; +}; diff --git a/webpack-lib/lib/util/internalSerializables.js b/webpack-lib/lib/util/internalSerializables.js new file mode 100644 index 00000000000..3ca8f2b9178 --- /dev/null +++ b/webpack-lib/lib/util/internalSerializables.js @@ -0,0 +1,224 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +// We need to include a list of requires here +// to allow webpack to be bundled with only static requires +// We could use a dynamic require(`../${request}`) but this +// would include too many modules and not every tool is able +// to process this +module.exports = { + AsyncDependenciesBlock: () => require("../AsyncDependenciesBlock"), + CommentCompilationWarning: () => require("../CommentCompilationWarning"), + ContextModule: () => require("../ContextModule"), + "cache/PackFileCacheStrategy": () => + require("../cache/PackFileCacheStrategy"), + "cache/ResolverCachePlugin": () => require("../cache/ResolverCachePlugin"), + "container/ContainerEntryDependency": () => + require("../container/ContainerEntryDependency"), + "container/ContainerEntryModule": () => + require("../container/ContainerEntryModule"), + "container/ContainerExposedDependency": () => + require("../container/ContainerExposedDependency"), + "container/FallbackDependency": () => + require("../container/FallbackDependency"), + "container/FallbackItemDependency": () => + require("../container/FallbackItemDependency"), + "container/FallbackModule": () => require("../container/FallbackModule"), + "container/RemoteModule": () => require("../container/RemoteModule"), + "container/RemoteToExternalDependency": () => + require("../container/RemoteToExternalDependency"), + "dependencies/AMDDefineDependency": () => + require("../dependencies/AMDDefineDependency"), + "dependencies/AMDRequireArrayDependency": () => + require("../dependencies/AMDRequireArrayDependency"), + "dependencies/AMDRequireContextDependency": () => + require("../dependencies/AMDRequireContextDependency"), + "dependencies/AMDRequireDependenciesBlock": () => + require("../dependencies/AMDRequireDependenciesBlock"), + "dependencies/AMDRequireDependency": () => + require("../dependencies/AMDRequireDependency"), + "dependencies/AMDRequireItemDependency": () => + require("../dependencies/AMDRequireItemDependency"), + "dependencies/CachedConstDependency": () => + require("../dependencies/CachedConstDependency"), + "dependencies/ExternalModuleDependency": () => + require("../dependencies/ExternalModuleDependency"), + "dependencies/ExternalModuleInitFragment": () => + require("../dependencies/ExternalModuleInitFragment"), + "dependencies/CreateScriptUrlDependency": () => + require("../dependencies/CreateScriptUrlDependency"), + "dependencies/CommonJsRequireContextDependency": () => + require("../dependencies/CommonJsRequireContextDependency"), + "dependencies/CommonJsExportRequireDependency": () => + require("../dependencies/CommonJsExportRequireDependency"), + "dependencies/CommonJsExportsDependency": () => + require("../dependencies/CommonJsExportsDependency"), + "dependencies/CommonJsFullRequireDependency": () => + require("../dependencies/CommonJsFullRequireDependency"), + "dependencies/CommonJsRequireDependency": () => + require("../dependencies/CommonJsRequireDependency"), + "dependencies/CommonJsSelfReferenceDependency": () => + require("../dependencies/CommonJsSelfReferenceDependency"), + "dependencies/ConstDependency": () => + require("../dependencies/ConstDependency"), + "dependencies/ContextDependency": () => + require("../dependencies/ContextDependency"), + "dependencies/ContextElementDependency": () => + require("../dependencies/ContextElementDependency"), + "dependencies/CriticalDependencyWarning": () => + require("../dependencies/CriticalDependencyWarning"), + "dependencies/CssImportDependency": () => + require("../dependencies/CssImportDependency"), + "dependencies/CssLocalIdentifierDependency": () => + require("../dependencies/CssLocalIdentifierDependency"), + "dependencies/CssSelfLocalIdentifierDependency": () => + require("../dependencies/CssSelfLocalIdentifierDependency"), + "dependencies/CssIcssImportDependency": () => + require("../dependencies/CssIcssImportDependency"), + "dependencies/CssIcssExportDependency": () => + require("../dependencies/CssIcssExportDependency"), + "dependencies/CssUrlDependency": () => + require("../dependencies/CssUrlDependency"), + "dependencies/CssIcssSymbolDependency": () => + require("../dependencies/CssIcssSymbolDependency"), + "dependencies/DelegatedSourceDependency": () => + require("../dependencies/DelegatedSourceDependency"), + "dependencies/DllEntryDependency": () => + require("../dependencies/DllEntryDependency"), + "dependencies/EntryDependency": () => + require("../dependencies/EntryDependency"), + "dependencies/ExportsInfoDependency": () => + require("../dependencies/ExportsInfoDependency"), + "dependencies/HarmonyAcceptDependency": () => + require("../dependencies/HarmonyAcceptDependency"), + "dependencies/HarmonyAcceptImportDependency": () => + require("../dependencies/HarmonyAcceptImportDependency"), + "dependencies/HarmonyCompatibilityDependency": () => + require("../dependencies/HarmonyCompatibilityDependency"), + "dependencies/HarmonyExportExpressionDependency": () => + require("../dependencies/HarmonyExportExpressionDependency"), + "dependencies/HarmonyExportHeaderDependency": () => + require("../dependencies/HarmonyExportHeaderDependency"), + "dependencies/HarmonyExportImportedSpecifierDependency": () => + require("../dependencies/HarmonyExportImportedSpecifierDependency"), + "dependencies/HarmonyExportSpecifierDependency": () => + require("../dependencies/HarmonyExportSpecifierDependency"), + "dependencies/HarmonyImportSideEffectDependency": () => + require("../dependencies/HarmonyImportSideEffectDependency"), + "dependencies/HarmonyImportSpecifierDependency": () => + require("../dependencies/HarmonyImportSpecifierDependency"), + "dependencies/HarmonyEvaluatedImportSpecifierDependency": () => + require("../dependencies/HarmonyEvaluatedImportSpecifierDependency"), + "dependencies/ImportContextDependency": () => + require("../dependencies/ImportContextDependency"), + "dependencies/ImportDependency": () => + require("../dependencies/ImportDependency"), + "dependencies/ImportEagerDependency": () => + require("../dependencies/ImportEagerDependency"), + "dependencies/ImportWeakDependency": () => + require("../dependencies/ImportWeakDependency"), + "dependencies/JsonExportsDependency": () => + require("../dependencies/JsonExportsDependency"), + "dependencies/LocalModule": () => require("../dependencies/LocalModule"), + "dependencies/LocalModuleDependency": () => + require("../dependencies/LocalModuleDependency"), + "dependencies/ModuleDecoratorDependency": () => + require("../dependencies/ModuleDecoratorDependency"), + "dependencies/ModuleHotAcceptDependency": () => + require("../dependencies/ModuleHotAcceptDependency"), + "dependencies/ModuleHotDeclineDependency": () => + require("../dependencies/ModuleHotDeclineDependency"), + "dependencies/ImportMetaHotAcceptDependency": () => + require("../dependencies/ImportMetaHotAcceptDependency"), + "dependencies/ImportMetaHotDeclineDependency": () => + require("../dependencies/ImportMetaHotDeclineDependency"), + "dependencies/ImportMetaContextDependency": () => + require("../dependencies/ImportMetaContextDependency"), + "dependencies/ProvidedDependency": () => + require("../dependencies/ProvidedDependency"), + "dependencies/PureExpressionDependency": () => + require("../dependencies/PureExpressionDependency"), + "dependencies/RequireContextDependency": () => + require("../dependencies/RequireContextDependency"), + "dependencies/RequireEnsureDependenciesBlock": () => + require("../dependencies/RequireEnsureDependenciesBlock"), + "dependencies/RequireEnsureDependency": () => + require("../dependencies/RequireEnsureDependency"), + "dependencies/RequireEnsureItemDependency": () => + require("../dependencies/RequireEnsureItemDependency"), + "dependencies/RequireHeaderDependency": () => + require("../dependencies/RequireHeaderDependency"), + "dependencies/RequireIncludeDependency": () => + require("../dependencies/RequireIncludeDependency"), + "dependencies/RequireIncludeDependencyParserPlugin": () => + require("../dependencies/RequireIncludeDependencyParserPlugin"), + "dependencies/RequireResolveContextDependency": () => + require("../dependencies/RequireResolveContextDependency"), + "dependencies/RequireResolveDependency": () => + require("../dependencies/RequireResolveDependency"), + "dependencies/RequireResolveHeaderDependency": () => + require("../dependencies/RequireResolveHeaderDependency"), + "dependencies/RuntimeRequirementsDependency": () => + require("../dependencies/RuntimeRequirementsDependency"), + "dependencies/StaticExportsDependency": () => + require("../dependencies/StaticExportsDependency"), + "dependencies/SystemPlugin": () => require("../dependencies/SystemPlugin"), + "dependencies/UnsupportedDependency": () => + require("../dependencies/UnsupportedDependency"), + "dependencies/URLDependency": () => require("../dependencies/URLDependency"), + "dependencies/WebAssemblyExportImportedDependency": () => + require("../dependencies/WebAssemblyExportImportedDependency"), + "dependencies/WebAssemblyImportDependency": () => + require("../dependencies/WebAssemblyImportDependency"), + "dependencies/WebpackIsIncludedDependency": () => + require("../dependencies/WebpackIsIncludedDependency"), + "dependencies/WorkerDependency": () => + require("../dependencies/WorkerDependency"), + "json/JsonData": () => require("../json/JsonData"), + "optimize/ConcatenatedModule": () => + require("../optimize/ConcatenatedModule"), + DelegatedModule: () => require("../DelegatedModule"), + DependenciesBlock: () => require("../DependenciesBlock"), + DllModule: () => require("../DllModule"), + ExternalModule: () => require("../ExternalModule"), + FileSystemInfo: () => require("../FileSystemInfo"), + InitFragment: () => require("../InitFragment"), + InvalidDependenciesModuleWarning: () => + require("../InvalidDependenciesModuleWarning"), + Module: () => require("../Module"), + ModuleBuildError: () => require("../ModuleBuildError"), + ModuleDependencyWarning: () => require("../ModuleDependencyWarning"), + ModuleError: () => require("../ModuleError"), + ModuleGraph: () => require("../ModuleGraph"), + ModuleParseError: () => require("../ModuleParseError"), + ModuleWarning: () => require("../ModuleWarning"), + NormalModule: () => require("../NormalModule"), + CssModule: () => require("../CssModule"), + RawDataUrlModule: () => require("../asset/RawDataUrlModule"), + RawModule: () => require("../RawModule"), + "sharing/ConsumeSharedModule": () => + require("../sharing/ConsumeSharedModule"), + "sharing/ConsumeSharedFallbackDependency": () => + require("../sharing/ConsumeSharedFallbackDependency"), + "sharing/ProvideSharedModule": () => + require("../sharing/ProvideSharedModule"), + "sharing/ProvideSharedDependency": () => + require("../sharing/ProvideSharedDependency"), + "sharing/ProvideForSharedDependency": () => + require("../sharing/ProvideForSharedDependency"), + UnsupportedFeatureWarning: () => require("../UnsupportedFeatureWarning"), + "util/LazySet": () => require("../util/LazySet"), + UnhandledSchemeError: () => require("../UnhandledSchemeError"), + NodeStuffInWebError: () => require("../NodeStuffInWebError"), + EnvironmentNotSupportAsyncWarning: () => + require("../EnvironmentNotSupportAsyncWarning"), + WebpackError: () => require("../WebpackError"), + + "util/registerExternalSerializer": () => { + // already registered + } +}; diff --git a/webpack-lib/lib/util/magicComment.js b/webpack-lib/lib/util/magicComment.js new file mode 100644 index 00000000000..c47cc5350f4 --- /dev/null +++ b/webpack-lib/lib/util/magicComment.js @@ -0,0 +1,21 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +// regexp to match at least one "magic comment" +module.exports.webpackCommentRegExp = new RegExp( + /(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/ +); + +// regexp to match at least one "magic comment" +/** + * @returns {import("vm").Context} magic comment context + */ +module.exports.createMagicCommentContext = () => + require("vm").createContext(undefined, { + name: "Webpack Magic Comment Parser", + codeGeneration: { strings: false, wasm: false } + }); diff --git a/webpack-lib/lib/util/makeSerializable.js b/webpack-lib/lib/util/makeSerializable.js new file mode 100644 index 00000000000..90b60fb3e16 --- /dev/null +++ b/webpack-lib/lib/util/makeSerializable.js @@ -0,0 +1,60 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { register } = require("./serialization"); + +/** @typedef {import("../serialization/ObjectMiddleware").Constructor} Constructor */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ + +/** @typedef {{ serialize: (context: ObjectSerializerContext) => void, deserialize: (context: ObjectDeserializerContext) => void }} SerializableClass */ +/** + * @template {SerializableClass} T + * @typedef {(new (...params: any[]) => T) & { deserialize?: (context: ObjectDeserializerContext) => T }} SerializableClassConstructor + */ + +/** + * @template {SerializableClass} T + */ +class ClassSerializer { + /** + * @param {SerializableClassConstructor} Constructor constructor + */ + constructor(Constructor) { + this.Constructor = Constructor; + } + + /** + * @param {T} obj obj + * @param {ObjectSerializerContext} context context + */ + serialize(obj, context) { + obj.serialize(context); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {T} obj + */ + deserialize(context) { + if (typeof this.Constructor.deserialize === "function") { + return this.Constructor.deserialize(context); + } + const obj = new this.Constructor(); + obj.deserialize(context); + return obj; + } +} + +/** + * @template {Constructor} T + * @param {T} Constructor the constructor + * @param {string} request the request which will be required when deserializing + * @param {string | null} [name] the name to make multiple serializer unique when sharing a request + */ +module.exports = (Constructor, request, name = null) => { + register(Constructor, request, name, new ClassSerializer(Constructor)); +}; diff --git a/webpack-lib/lib/util/memoize.js b/webpack-lib/lib/util/memoize.js new file mode 100644 index 00000000000..d3fc19634fe --- /dev/null +++ b/webpack-lib/lib/util/memoize.js @@ -0,0 +1,33 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** @template T @typedef {function(): T} FunctionReturning */ + +/** + * @template T + * @param {FunctionReturning} fn memorized function + * @returns {FunctionReturning} new function + */ +const memoize = fn => { + let cache = false; + /** @type {T | undefined} */ + let result; + return () => { + if (cache) { + return /** @type {T} */ (result); + } + + result = fn(); + cache = true; + // Allow to clean up memory for fn + // and all dependent resources + /** @type {FunctionReturning | undefined} */ + (fn) = undefined; + return /** @type {T} */ (result); + }; +}; + +module.exports = memoize; diff --git a/webpack-lib/lib/util/nonNumericOnlyHash.js b/webpack-lib/lib/util/nonNumericOnlyHash.js new file mode 100644 index 00000000000..ec8ca745ffc --- /dev/null +++ b/webpack-lib/lib/util/nonNumericOnlyHash.js @@ -0,0 +1,22 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Ivan Kopeykin @vankop +*/ + +"use strict"; + +const A_CODE = "a".charCodeAt(0); + +/** + * @param {string} hash hash + * @param {number} hashLength hash length + * @returns {string} returns hash that has at least one non numeric char + */ +module.exports = (hash, hashLength) => { + if (hashLength < 1) return ""; + const slice = hash.slice(0, hashLength); + if (/[^\d]/.test(slice)) return slice; + return `${String.fromCharCode( + A_CODE + (Number.parseInt(hash[0], 10) % 6) + )}${slice.slice(1)}`; +}; diff --git a/webpack-lib/lib/util/numberHash.js b/webpack-lib/lib/util/numberHash.js new file mode 100644 index 00000000000..950d14bf8bb --- /dev/null +++ b/webpack-lib/lib/util/numberHash.js @@ -0,0 +1,95 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * Threshold for switching from 32-bit to 64-bit hashing. This is selected to ensure that the bias towards lower modulo results when using 32-bit hashing is <0.5%. + * @type {number} + */ +const FNV_64_THRESHOLD = 1 << 24; + +/** + * The FNV-1a offset basis for 32-bit hash values. + * @type {number} + */ +const FNV_OFFSET_32 = 2166136261; +/** + * The FNV-1a prime for 32-bit hash values. + * @type {number} + */ +const FNV_PRIME_32 = 16777619; +/** + * The mask for a positive 32-bit signed integer. + * @type {number} + */ +const MASK_31 = 0x7fffffff; + +/** + * The FNV-1a offset basis for 64-bit hash values. + * @type {bigint} + */ +const FNV_OFFSET_64 = BigInt("0xCBF29CE484222325"); +/** + * The FNV-1a prime for 64-bit hash values. + * @type {bigint} + */ +const FNV_PRIME_64 = BigInt("0x100000001B3"); + +/** + * Computes a 32-bit FNV-1a hash value for the given string. + * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + * @param {string} str The input string to hash + * @returns {number} - The computed hash value. + */ +function fnv1a32(str) { + let hash = FNV_OFFSET_32; + for (let i = 0, len = str.length; i < len; i++) { + hash ^= str.charCodeAt(i); + // Use Math.imul to do c-style 32-bit multiplication and keep only the 32 least significant bits + hash = Math.imul(hash, FNV_PRIME_32); + } + // Force the result to be positive + return hash & MASK_31; +} + +/** + * Computes a 64-bit FNV-1a hash value for the given string. + * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + * @param {string} str The input string to hash + * @returns {bigint} - The computed hash value. + */ +function fnv1a64(str) { + let hash = FNV_OFFSET_64; + for (let i = 0, len = str.length; i < len; i++) { + hash ^= BigInt(str.charCodeAt(i)); + hash = BigInt.asUintN(64, hash * FNV_PRIME_64); + } + return hash; +} + +/** + * Computes a hash value for the given string and range. This hashing algorithm is a modified + * version of the [FNV-1a algorithm](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function). + * It is optimized for speed and does **not** generate a cryptographic hash value. + * + * We use `numberHash` in `lib/ids/IdHelpers.js` to generate hash values for the module identifier. The generated + * hash is used as a prefix for the module id's to avoid collisions with other modules. + * @param {string} str The input string to hash. + * @param {number} range The range of the hash value (0 to range-1). + * @returns {number} - The computed hash value. + * @example + * ```js + * const numberHash = require("webpack/lib/util/numberHash"); + * numberHash("hello", 1000); // 73 + * numberHash("hello world"); // 72 + * ``` + */ +module.exports = (str, range) => { + if (range < FNV_64_THRESHOLD) { + return fnv1a32(str) % range; + } + return Number(fnv1a64(str) % BigInt(range)); +}; diff --git a/webpack-lib/lib/util/objectToMap.js b/webpack-lib/lib/util/objectToMap.js new file mode 100644 index 00000000000..19ce8e08f77 --- /dev/null +++ b/webpack-lib/lib/util/objectToMap.js @@ -0,0 +1,14 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +/** + * Convert an object into an ES6 map + * @param {object} obj any object type that works with Object.entries() + * @returns {Map} an ES6 Map of KV pairs + */ +module.exports = function objectToMap(obj) { + return new Map(Object.entries(obj)); +}; diff --git a/webpack-lib/lib/util/processAsyncTree.js b/webpack-lib/lib/util/processAsyncTree.js new file mode 100644 index 00000000000..38253865231 --- /dev/null +++ b/webpack-lib/lib/util/processAsyncTree.js @@ -0,0 +1,68 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @template T + * @template {Error} E + * @param {Iterable} items initial items + * @param {number} concurrency number of items running in parallel + * @param {function(T, function(T): void, function(E=): void): void} processor worker which pushes more items + * @param {function(E=): void} callback all items processed + * @returns {void} + */ +const processAsyncTree = (items, concurrency, processor, callback) => { + const queue = Array.from(items); + if (queue.length === 0) return callback(); + let processing = 0; + let finished = false; + let processScheduled = true; + + /** + * @param {T} item item + */ + const push = item => { + queue.push(item); + if (!processScheduled && processing < concurrency) { + processScheduled = true; + process.nextTick(processQueue); + } + }; + + /** + * @param {E | null | undefined} err error + */ + const processorCallback = err => { + processing--; + if (err && !finished) { + finished = true; + callback(err); + return; + } + if (!processScheduled) { + processScheduled = true; + process.nextTick(processQueue); + } + }; + + const processQueue = () => { + if (finished) return; + while (processing < concurrency && queue.length > 0) { + processing++; + const item = /** @type {T} */ (queue.pop()); + processor(item, push, processorCallback); + } + processScheduled = false; + if (queue.length === 0 && processing === 0 && !finished) { + finished = true; + callback(); + } + }; + + processQueue(); +}; + +module.exports = processAsyncTree; diff --git a/webpack-lib/lib/util/propertyAccess.js b/webpack-lib/lib/util/propertyAccess.js new file mode 100644 index 00000000000..0cf647bd9e0 --- /dev/null +++ b/webpack-lib/lib/util/propertyAccess.js @@ -0,0 +1,30 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SAFE_IDENTIFIER, RESERVED_IDENTIFIER } = require("./propertyName"); + +/** + * @param {ArrayLike} properties properties + * @param {number} start start index + * @returns {string} chain of property accesses + */ +const propertyAccess = (properties, start = 0) => { + let str = ""; + for (let i = start; i < properties.length; i++) { + const p = properties[i]; + if (`${Number(p)}` === p) { + str += `[${p}]`; + } else if (SAFE_IDENTIFIER.test(p) && !RESERVED_IDENTIFIER.has(p)) { + str += `.${p}`; + } else { + str += `[${JSON.stringify(p)}]`; + } + } + return str; +}; + +module.exports = propertyAccess; diff --git a/webpack-lib/lib/util/propertyName.js b/webpack-lib/lib/util/propertyName.js new file mode 100644 index 00000000000..4ee9e3f5485 --- /dev/null +++ b/webpack-lib/lib/util/propertyName.js @@ -0,0 +1,77 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const SAFE_IDENTIFIER = /^[_a-zA-Z$][_a-zA-Z$0-9]*$/; +const RESERVED_IDENTIFIER = new Set([ + "break", + "case", + "catch", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "else", + "export", + "extends", + "finally", + "for", + "function", + "if", + "import", + "in", + "instanceof", + "new", + "return", + "super", + "switch", + "this", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with", + "enum", + // strict mode + "implements", + "interface", + "let", + "package", + "private", + "protected", + "public", + "static", + "yield", + "yield", + // module code + "await", + // skip future reserved keywords defined under ES1 till ES3 + // additional + "null", + "true", + "false" +]); + +/** + * @summary Returns a valid JS property name for the given property. + * Certain strings like "default", "null", and names with whitespace are not + * valid JS property names, so they are returned as strings. + * @param {string} prop property name to analyze + * @returns {string} valid JS property name + */ +const propertyName = prop => { + if (SAFE_IDENTIFIER.test(prop) && !RESERVED_IDENTIFIER.has(prop)) { + return prop; + } + return JSON.stringify(prop); +}; + +module.exports = { SAFE_IDENTIFIER, RESERVED_IDENTIFIER, propertyName }; diff --git a/webpack-lib/lib/util/registerExternalSerializer.js b/webpack-lib/lib/util/registerExternalSerializer.js new file mode 100644 index 00000000000..711bcfa210a --- /dev/null +++ b/webpack-lib/lib/util/registerExternalSerializer.js @@ -0,0 +1,337 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { register } = require("./serialization"); + +const Position = /** @type {TODO} */ (require("acorn")).Position; +const SourceLocation = require("acorn").SourceLocation; +const ValidationError = require("schema-utils").ValidationError; +const { + CachedSource, + ConcatSource, + OriginalSource, + PrefixSource, + RawSource, + ReplaceSource, + SourceMapSource +} = require("webpack-sources"); + +/** @typedef {import("acorn").Position} Position */ +/** @typedef {import("../Dependency").RealDependencyLocation} RealDependencyLocation */ +/** @typedef {import("../Dependency").SourcePosition} SourcePosition */ +/** @typedef {import("./serialization").ObjectDeserializerContext} ObjectDeserializerContext */ +/** @typedef {import("./serialization").ObjectSerializerContext} ObjectSerializerContext */ + +/** @typedef {ObjectSerializerContext & { writeLazy?: (value: any) => void }} WebpackObjectSerializerContext */ + +const CURRENT_MODULE = "webpack/lib/util/registerExternalSerializer"; + +register( + CachedSource, + CURRENT_MODULE, + "webpack-sources/CachedSource", + new (class CachedSourceSerializer { + /** + * @param {CachedSource} source the cached source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write, writeLazy }) { + if (writeLazy) { + writeLazy(source.originalLazy()); + } else { + write(source.original()); + } + write(source.getCachedData()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {CachedSource} cached source + */ + deserialize({ read }) { + const source = read(); + const cachedData = read(); + return new CachedSource(source, cachedData); + } + })() +); + +register( + RawSource, + CURRENT_MODULE, + "webpack-sources/RawSource", + new (class RawSourceSerializer { + /** + * @param {RawSource} source the raw source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.buffer()); + write(!source.isBuffer()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RawSource} raw source + */ + deserialize({ read }) { + const source = read(); + const convertToString = read(); + return new RawSource(source, convertToString); + } + })() +); + +register( + ConcatSource, + CURRENT_MODULE, + "webpack-sources/ConcatSource", + new (class ConcatSourceSerializer { + /** + * @param {ConcatSource} source the concat source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.getChildren()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ConcatSource} concat source + */ + deserialize({ read }) { + const source = new ConcatSource(); + source.addAllSkipOptimizing(read()); + return source; + } + })() +); + +register( + PrefixSource, + CURRENT_MODULE, + "webpack-sources/PrefixSource", + new (class PrefixSourceSerializer { + /** + * @param {PrefixSource} source the prefix source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.getPrefix()); + write(source.original()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {PrefixSource} prefix source + */ + deserialize({ read }) { + return new PrefixSource(read(), read()); + } + })() +); + +register( + ReplaceSource, + CURRENT_MODULE, + "webpack-sources/ReplaceSource", + new (class ReplaceSourceSerializer { + /** + * @param {ReplaceSource} source the replace source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.original()); + write(source.getName()); + const replacements = source.getReplacements(); + write(replacements.length); + for (const repl of replacements) { + write(repl.start); + write(repl.end); + } + for (const repl of replacements) { + write(repl.content); + write(repl.name); + } + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {ReplaceSource} replace source + */ + deserialize({ read }) { + const source = new ReplaceSource(read(), read()); + const len = read(); + const startEndBuffer = []; + for (let i = 0; i < len; i++) { + startEndBuffer.push(read(), read()); + } + let j = 0; + for (let i = 0; i < len; i++) { + source.replace( + startEndBuffer[j++], + startEndBuffer[j++], + read(), + read() + ); + } + return source; + } + })() +); + +register( + OriginalSource, + CURRENT_MODULE, + "webpack-sources/OriginalSource", + new (class OriginalSourceSerializer { + /** + * @param {OriginalSource} source the original source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.buffer()); + write(source.getName()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {OriginalSource} original source + */ + deserialize({ read }) { + const buffer = read(); + const name = read(); + return new OriginalSource(buffer, name); + } + })() +); + +register( + SourceLocation, + CURRENT_MODULE, + "acorn/SourceLocation", + new (class SourceLocationSerializer { + /** + * @param {SourceLocation} loc the location to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(loc, { write }) { + write(loc.start.line); + write(loc.start.column); + write(loc.end.line); + write(loc.end.column); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {RealDependencyLocation} location + */ + deserialize({ read }) { + return { + start: { + line: read(), + column: read() + }, + end: { + line: read(), + column: read() + } + }; + } + })() +); + +register( + Position, + CURRENT_MODULE, + "acorn/Position", + new (class PositionSerializer { + /** + * @param {Position} pos the position to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(pos, { write }) { + write(pos.line); + write(pos.column); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {SourcePosition} position + */ + deserialize({ read }) { + return { + line: read(), + column: read() + }; + } + })() +); + +register( + SourceMapSource, + CURRENT_MODULE, + "webpack-sources/SourceMapSource", + new (class SourceMapSourceSerializer { + /** + * @param {SourceMapSource} source the source map source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(source, { write }) { + write(source.getArgsAsBuffers()); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {SourceMapSource} source source map source + */ + deserialize({ read }) { + // @ts-expect-error + return new SourceMapSource(...read()); + } + })() +); + +register( + ValidationError, + CURRENT_MODULE, + "schema-utils/ValidationError", + new (class ValidationErrorSerializer { + // TODO error should be ValidationError, but this fails the type checks + /** + * @param {TODO} error the source map source to be serialized + * @param {WebpackObjectSerializerContext} context context + * @returns {void} + */ + serialize(error, { write }) { + write(error.errors); + write(error.schema); + write({ + name: error.headerName, + baseDataPath: error.baseDataPath, + postFormatter: error.postFormatter + }); + } + + /** + * @param {ObjectDeserializerContext} context context + * @returns {TODO} error + */ + deserialize({ read }) { + return new ValidationError(read(), read(), read()); + } + })() +); diff --git a/webpack-lib/lib/util/runtime.js b/webpack-lib/lib/util/runtime.js new file mode 100644 index 00000000000..021f799d4e9 --- /dev/null +++ b/webpack-lib/lib/util/runtime.js @@ -0,0 +1,687 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const SortableSet = require("./SortableSet"); + +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ + +/** @typedef {string | SortableSet | undefined} RuntimeSpec */ +/** @typedef {RuntimeSpec | boolean} RuntimeCondition */ + +/** + * @param {Compilation} compilation the compilation + * @param {string} name name of the entry + * @param {EntryOptions=} options optionally already received entry options + * @returns {RuntimeSpec} runtime + */ +module.exports.getEntryRuntime = (compilation, name, options) => { + let dependOn; + let runtime; + if (options) { + ({ dependOn, runtime } = options); + } else { + const entry = compilation.entries.get(name); + if (!entry) return name; + ({ dependOn, runtime } = entry.options); + } + if (dependOn) { + /** @type {RuntimeSpec} */ + let result; + const queue = new Set(dependOn); + for (const name of queue) { + const dep = compilation.entries.get(name); + if (!dep) continue; + const { dependOn, runtime } = dep.options; + if (dependOn) { + for (const name of dependOn) { + queue.add(name); + } + } else { + result = mergeRuntimeOwned(result, runtime || name); + } + } + return result || name; + } + return runtime || name; +}; + +/** + * @param {RuntimeSpec} runtime runtime + * @param {function(string | undefined): void} fn functor + * @param {boolean} deterministicOrder enforce a deterministic order + * @returns {void} + */ +const forEachRuntime = (runtime, fn, deterministicOrder = false) => { + if (runtime === undefined) { + fn(undefined); + } else if (typeof runtime === "string") { + fn(runtime); + } else { + if (deterministicOrder) runtime.sort(); + for (const r of runtime) { + fn(r); + } + } +}; +module.exports.forEachRuntime = forEachRuntime; + +/** + * @template T + * @param {SortableSet} set set + * @returns {string} runtime key + */ +const getRuntimesKey = set => { + set.sort(); + return Array.from(set).join("\n"); +}; + +/** + * @param {RuntimeSpec} runtime runtime(s) + * @returns {string} key of runtimes + */ +const getRuntimeKey = runtime => { + if (runtime === undefined) return "*"; + if (typeof runtime === "string") return runtime; + return runtime.getFromUnorderedCache(getRuntimesKey); +}; +module.exports.getRuntimeKey = getRuntimeKey; + +/** + * @param {string} key key of runtimes + * @returns {RuntimeSpec} runtime(s) + */ +const keyToRuntime = key => { + if (key === "*") return; + const items = key.split("\n"); + if (items.length === 1) return items[0]; + return new SortableSet(items); +}; +module.exports.keyToRuntime = keyToRuntime; + +/** + * @template T + * @param {SortableSet} set set + * @returns {string} runtime string + */ +const getRuntimesString = set => { + set.sort(); + return Array.from(set).join("+"); +}; + +/** + * @param {RuntimeSpec} runtime runtime(s) + * @returns {string} readable version + */ +const runtimeToString = runtime => { + if (runtime === undefined) return "*"; + if (typeof runtime === "string") return runtime; + return runtime.getFromUnorderedCache(getRuntimesString); +}; +module.exports.runtimeToString = runtimeToString; + +/** + * @param {RuntimeCondition} runtimeCondition runtime condition + * @returns {string} readable version + */ +module.exports.runtimeConditionToString = runtimeCondition => { + if (runtimeCondition === true) return "true"; + if (runtimeCondition === false) return "false"; + return runtimeToString(runtimeCondition); +}; + +/** + * @param {RuntimeSpec} a first + * @param {RuntimeSpec} b second + * @returns {boolean} true, when they are equal + */ +const runtimeEqual = (a, b) => { + if (a === b) { + return true; + } else if ( + a === undefined || + b === undefined || + typeof a === "string" || + typeof b === "string" + ) { + return false; + } else if (a.size !== b.size) { + return false; + } + a.sort(); + b.sort(); + const aIt = a[Symbol.iterator](); + const bIt = b[Symbol.iterator](); + for (;;) { + const aV = aIt.next(); + if (aV.done) return true; + const bV = bIt.next(); + if (aV.value !== bV.value) return false; + } +}; +module.exports.runtimeEqual = runtimeEqual; + +/** + * @param {RuntimeSpec} a first + * @param {RuntimeSpec} b second + * @returns {-1|0|1} compare + */ +module.exports.compareRuntime = (a, b) => { + if (a === b) { + return 0; + } else if (a === undefined) { + return -1; + } else if (b === undefined) { + return 1; + } + const aKey = getRuntimeKey(a); + const bKey = getRuntimeKey(b); + if (aKey < bKey) return -1; + if (aKey > bKey) return 1; + return 0; +}; + +/** + * @param {RuntimeSpec} a first + * @param {RuntimeSpec} b second + * @returns {RuntimeSpec} merged + */ +const mergeRuntime = (a, b) => { + if (a === undefined) { + return b; + } else if (b === undefined) { + return a; + } else if (a === b) { + return a; + } else if (typeof a === "string") { + if (typeof b === "string") { + const set = new SortableSet(); + set.add(a); + set.add(b); + return set; + } else if (b.has(a)) { + return b; + } + const set = new SortableSet(b); + set.add(a); + return set; + } + if (typeof b === "string") { + if (a.has(b)) return a; + const set = new SortableSet(a); + set.add(b); + return set; + } + const set = new SortableSet(a); + for (const item of b) set.add(item); + if (set.size === a.size) return a; + return set; +}; +module.exports.mergeRuntime = mergeRuntime; + +/** + * @param {RuntimeCondition} a first + * @param {RuntimeCondition} b second + * @param {RuntimeSpec} runtime full runtime + * @returns {RuntimeCondition} result + */ +module.exports.mergeRuntimeCondition = (a, b, runtime) => { + if (a === false) return b; + if (b === false) return a; + if (a === true || b === true) return true; + const merged = mergeRuntime(a, b); + if (merged === undefined) return; + if (typeof merged === "string") { + if (typeof runtime === "string" && merged === runtime) return true; + return merged; + } + if (typeof runtime === "string" || runtime === undefined) return merged; + if (merged.size === runtime.size) return true; + return merged; +}; + +/** + * @param {RuntimeSpec | true} a first + * @param {RuntimeSpec | true} b second + * @param {RuntimeSpec} runtime full runtime + * @returns {RuntimeSpec | true} result + */ +module.exports.mergeRuntimeConditionNonFalse = (a, b, runtime) => { + if (a === true || b === true) return true; + const merged = mergeRuntime(a, b); + if (merged === undefined) return; + if (typeof merged === "string") { + if (typeof runtime === "string" && merged === runtime) return true; + return merged; + } + if (typeof runtime === "string" || runtime === undefined) return merged; + if (merged.size === runtime.size) return true; + return merged; +}; + +/** + * @param {RuntimeSpec} a first (may be modified) + * @param {RuntimeSpec} b second + * @returns {RuntimeSpec} merged + */ +const mergeRuntimeOwned = (a, b) => { + if (b === undefined) { + return a; + } else if (a === b) { + return a; + } else if (a === undefined) { + if (typeof b === "string") { + return b; + } + return new SortableSet(b); + } else if (typeof a === "string") { + if (typeof b === "string") { + const set = new SortableSet(); + set.add(a); + set.add(b); + return set; + } + const set = new SortableSet(b); + set.add(a); + return set; + } + if (typeof b === "string") { + a.add(b); + return a; + } + for (const item of b) a.add(item); + return a; +}; +module.exports.mergeRuntimeOwned = mergeRuntimeOwned; + +/** + * @param {RuntimeSpec} a first + * @param {RuntimeSpec} b second + * @returns {RuntimeSpec} merged + */ +module.exports.intersectRuntime = (a, b) => { + if (a === undefined) { + return b; + } else if (b === undefined) { + return a; + } else if (a === b) { + return a; + } else if (typeof a === "string") { + if (typeof b === "string") { + return; + } else if (b.has(a)) { + return a; + } + return; + } + if (typeof b === "string") { + if (a.has(b)) return b; + return; + } + const set = new SortableSet(); + for (const item of b) { + if (a.has(item)) set.add(item); + } + if (set.size === 0) return; + if (set.size === 1) { + const [item] = set; + return item; + } + return set; +}; + +/** + * @param {RuntimeSpec} a first + * @param {RuntimeSpec} b second + * @returns {RuntimeSpec} result + */ +const subtractRuntime = (a, b) => { + if (a === undefined) { + return; + } else if (b === undefined) { + return a; + } else if (a === b) { + return; + } else if (typeof a === "string") { + if (typeof b === "string") { + return a; + } else if (b.has(a)) { + return; + } + return a; + } + if (typeof b === "string") { + if (!a.has(b)) return a; + if (a.size === 2) { + for (const item of a) { + if (item !== b) return item; + } + } + const set = new SortableSet(a); + set.delete(b); + return set; + } + const set = new SortableSet(); + for (const item of a) { + if (!b.has(item)) set.add(item); + } + if (set.size === 0) return; + if (set.size === 1) { + const [item] = set; + return item; + } + return set; +}; +module.exports.subtractRuntime = subtractRuntime; + +/** + * @param {RuntimeCondition} a first + * @param {RuntimeCondition} b second + * @param {RuntimeSpec} runtime runtime + * @returns {RuntimeCondition} result + */ +module.exports.subtractRuntimeCondition = (a, b, runtime) => { + if (b === true) return false; + if (b === false) return a; + if (a === false) return false; + const result = subtractRuntime(a === true ? runtime : a, b); + return result === undefined ? false : result; +}; + +/** + * @param {RuntimeSpec} runtime runtime + * @param {function(RuntimeSpec=): boolean} filter filter function + * @returns {boolean | RuntimeSpec} true/false if filter is constant for all runtimes, otherwise runtimes that are active + */ +module.exports.filterRuntime = (runtime, filter) => { + if (runtime === undefined) return filter(); + if (typeof runtime === "string") return filter(runtime); + let some = false; + let every = true; + let result; + for (const r of runtime) { + const v = filter(r); + if (v) { + some = true; + result = mergeRuntimeOwned(result, r); + } else { + every = false; + } + } + if (!some) return false; + if (every) return true; + return result; +}; + +/** + * @template T + * @typedef {Map} RuntimeSpecMapInnerMap + */ + +/** + * @template T + */ +class RuntimeSpecMap { + /** + * @param {RuntimeSpecMap=} clone copy form this + */ + constructor(clone) { + this._mode = clone ? clone._mode : 0; // 0 = empty, 1 = single entry, 2 = map + /** @type {RuntimeSpec} */ + this._singleRuntime = clone ? clone._singleRuntime : undefined; + /** @type {T | undefined} */ + this._singleValue = clone ? clone._singleValue : undefined; + /** @type {RuntimeSpecMapInnerMap | undefined} */ + this._map = clone && clone._map ? new Map(clone._map) : undefined; + } + + /** + * @param {RuntimeSpec} runtime the runtimes + * @returns {T | undefined} value + */ + get(runtime) { + switch (this._mode) { + case 0: + return; + case 1: + return runtimeEqual(this._singleRuntime, runtime) + ? this._singleValue + : undefined; + default: + return /** @type {RuntimeSpecMapInnerMap} */ (this._map).get( + getRuntimeKey(runtime) + ); + } + } + + /** + * @param {RuntimeSpec} runtime the runtimes + * @returns {boolean} true, when the runtime is stored + */ + has(runtime) { + switch (this._mode) { + case 0: + return false; + case 1: + return runtimeEqual(this._singleRuntime, runtime); + default: + return /** @type {RuntimeSpecMapInnerMap} */ (this._map).has( + getRuntimeKey(runtime) + ); + } + } + + /** + * @param {RuntimeSpec} runtime the runtimes + * @param {T} value the value + */ + set(runtime, value) { + switch (this._mode) { + case 0: + this._mode = 1; + this._singleRuntime = runtime; + this._singleValue = value; + break; + case 1: + if (runtimeEqual(this._singleRuntime, runtime)) { + this._singleValue = value; + break; + } + this._mode = 2; + this._map = new Map(); + this._map.set( + getRuntimeKey(this._singleRuntime), + /** @type {T} */ (this._singleValue) + ); + this._singleRuntime = undefined; + this._singleValue = undefined; + /* falls through */ + default: + /** @type {RuntimeSpecMapInnerMap} */ + (this._map).set(getRuntimeKey(runtime), value); + } + } + + /** + * @param {RuntimeSpec} runtime the runtimes + * @param {() => TODO} computer function to compute the value + * @returns {TODO} true, when the runtime was deleted + */ + provide(runtime, computer) { + switch (this._mode) { + case 0: + this._mode = 1; + this._singleRuntime = runtime; + return (this._singleValue = computer()); + case 1: { + if (runtimeEqual(this._singleRuntime, runtime)) { + return /** @type {T} */ (this._singleValue); + } + this._mode = 2; + this._map = new Map(); + this._map.set( + getRuntimeKey(this._singleRuntime), + /** @type {T} */ (this._singleValue) + ); + this._singleRuntime = undefined; + this._singleValue = undefined; + const newValue = computer(); + this._map.set(getRuntimeKey(runtime), newValue); + return newValue; + } + default: { + const key = getRuntimeKey(runtime); + const value = /** @type {Map} */ (this._map).get(key); + if (value !== undefined) return value; + const newValue = computer(); + /** @type {Map} */ + (this._map).set(key, newValue); + return newValue; + } + } + } + + /** + * @param {RuntimeSpec} runtime the runtimes + */ + delete(runtime) { + switch (this._mode) { + case 0: + return; + case 1: + if (runtimeEqual(this._singleRuntime, runtime)) { + this._mode = 0; + this._singleRuntime = undefined; + this._singleValue = undefined; + } + return; + default: + /** @type {RuntimeSpecMapInnerMap} */ + (this._map).delete(getRuntimeKey(runtime)); + } + } + + /** + * @param {RuntimeSpec} runtime the runtimes + * @param {function(T | undefined): T} fn function to update the value + */ + update(runtime, fn) { + switch (this._mode) { + case 0: + throw new Error("runtime passed to update must exist"); + case 1: { + if (runtimeEqual(this._singleRuntime, runtime)) { + this._singleValue = fn(this._singleValue); + break; + } + const newValue = fn(undefined); + if (newValue !== undefined) { + this._mode = 2; + this._map = new Map(); + this._map.set( + getRuntimeKey(this._singleRuntime), + /** @type {T} */ (this._singleValue) + ); + this._singleRuntime = undefined; + this._singleValue = undefined; + this._map.set(getRuntimeKey(runtime), newValue); + } + break; + } + default: { + const key = getRuntimeKey(runtime); + const oldValue = /** @type {Map} */ (this._map).get(key); + const newValue = fn(oldValue); + if (newValue !== oldValue) + /** @type {RuntimeSpecMapInnerMap} */ + (this._map).set(key, newValue); + } + } + } + + keys() { + switch (this._mode) { + case 0: + return []; + case 1: + return [this._singleRuntime]; + default: + return Array.from( + /** @type {RuntimeSpecMapInnerMap} */ + (this._map).keys(), + keyToRuntime + ); + } + } + + /** + * @returns {IterableIterator} values + */ + values() { + switch (this._mode) { + case 0: + return [][Symbol.iterator](); + case 1: + return [/** @type {T} */ (this._singleValue)][Symbol.iterator](); + default: + return /** @type {Map} */ (this._map).values(); + } + } + + get size() { + if (/** @type {number} */ (this._mode) <= 1) { + return /** @type {number} */ (this._mode); + } + + return /** @type {Map} */ (this._map).size; + } +} + +module.exports.RuntimeSpecMap = RuntimeSpecMap; + +class RuntimeSpecSet { + /** + * @param {Iterable=} iterable iterable + */ + constructor(iterable) { + /** @type {Map} */ + this._map = new Map(); + if (iterable) { + for (const item of iterable) { + this.add(item); + } + } + } + + /** + * @param {RuntimeSpec} runtime runtime + */ + add(runtime) { + this._map.set(getRuntimeKey(runtime), runtime); + } + + /** + * @param {RuntimeSpec} runtime runtime + * @returns {boolean} true, when the runtime exists + */ + has(runtime) { + return this._map.has(getRuntimeKey(runtime)); + } + + /** + * @returns {IterableIterator} iterable iterator + */ + [Symbol.iterator]() { + return this._map.values(); + } + + get size() { + return this._map.size; + } +} + +module.exports.RuntimeSpecSet = RuntimeSpecSet; diff --git a/webpack-lib/lib/util/semver.js b/webpack-lib/lib/util/semver.js new file mode 100644 index 00000000000..86628eadd40 --- /dev/null +++ b/webpack-lib/lib/util/semver.js @@ -0,0 +1,602 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {string | number | undefined} SemVerRangeItem */ +/** @typedef {(SemVerRangeItem | SemVerRangeItem[])[]} SemVerRange */ + +/** + * @param {string} str version string + * @returns {SemVerRange} parsed version + */ +const parseVersion = str => { + /** + * @param {str} str str + * @returns {(string | number)[]} result + */ + var splitAndConvert = function (str) { + return str.split(".").map(function (item) { + // eslint-disable-next-line eqeqeq + return +item == /** @type {EXPECTED_ANY} */ (item) ? +item : item; + }); + }; + + var match = + /** @type {RegExpExecArray} */ + (/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str)); + + /** @type {(string | number | undefined | [])[]} */ + var ver = match[1] ? splitAndConvert(match[1]) : []; + + if (match[2]) { + ver.length++; + ver.push.apply(ver, splitAndConvert(match[2])); + } + + if (match[3]) { + ver.push([]); + ver.push.apply(ver, splitAndConvert(match[3])); + } + + return ver; +}; +module.exports.parseVersion = parseVersion; + +/* eslint-disable eqeqeq */ +/** + * @param {string} a version + * @param {string} b version + * @returns {boolean} true, iff a < b + */ +const versionLt = (a, b) => { + // @ts-expect-error + a = parseVersion(a); + // @ts-expect-error + b = parseVersion(b); + var i = 0; + for (;;) { + // a b EOA object undefined number string + // EOA a == b a < b b < a a < b a < b + // object b < a (0) b < a a < b a < b + // undefined a < b a < b (0) a < b a < b + // number b < a b < a b < a (1) a < b + // string b < a b < a b < a b < a (1) + // EOA end of array + // (0) continue on + // (1) compare them via "<" + + // Handles first row in table + if (i >= a.length) return i < b.length && (typeof b[i])[0] != "u"; + + var aValue = a[i]; + var aType = (typeof aValue)[0]; + + // Handles first column in table + if (i >= b.length) return aType == "u"; + + var bValue = b[i]; + var bType = (typeof bValue)[0]; + + if (aType == bType) { + if (aType != "o" && aType != "u" && aValue != bValue) { + return aValue < bValue; + } + i++; + } else { + // Handles remaining cases + if (aType == "o" && bType == "n") return true; + return bType == "s" || aType == "u"; + } + } +}; +/* eslint-enable eqeqeq */ +module.exports.versionLt = versionLt; + +/** + * @param {string} str range string + * @returns {SemVerRange} parsed range + */ +module.exports.parseRange = str => { + /** + * @param {string} str str + * @returns {(string | number)[]} result + */ + const splitAndConvert = str => { + return str + .split(".") + .map(item => (item !== "NaN" && `${+item}` === item ? +item : item)); + }; + + // see https://docs.npmjs.com/misc/semver#range-grammar for grammar + /** + * @param {string} str str + * @returns {SemVerRangeItem[]} + */ + const parsePartial = str => { + const match = + /** @type {RegExpExecArray} */ + (/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str)); + /** @type {SemVerRangeItem[]} */ + const ver = match[1] ? [0, ...splitAndConvert(match[1])] : [0]; + + if (match[2]) { + ver.length++; + ver.push.apply(ver, splitAndConvert(match[2])); + } + + // remove trailing any matchers + let last = ver[ver.length - 1]; + while ( + ver.length && + (last === undefined || /^[*xX]$/.test(/** @type {string} */ (last))) + ) { + ver.pop(); + last = ver[ver.length - 1]; + } + + return ver; + }; + + /** + * + * @param {SemVerRangeItem[]} range range + * @returns {SemVerRangeItem[]} + */ + const toFixed = range => { + if (range.length === 1) { + // Special case for "*" is "x.x.x" instead of "=" + return [0]; + } else if (range.length === 2) { + // Special case for "1" is "1.x.x" instead of "=1" + return [1, ...range.slice(1)]; + } else if (range.length === 3) { + // Special case for "1.2" is "1.2.x" instead of "=1.2" + return [2, ...range.slice(1)]; + } + + return [range.length, ...range.slice(1)]; + }; + + /** + * + * @param {SemVerRangeItem[]} range + * @returns {SemVerRangeItem[]} result + */ + const negate = range => { + return [-(/** @type { [number]} */ (range)[0]) - 1, ...range.slice(1)]; + }; + + /** + * @param {string} str str + * @returns {SemVerRange} + */ + const parseSimple = str => { + // simple ::= primitive | partial | tilde | caret + // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | '!' ) ( ' ' ) * partial + // tilde ::= '~' ( ' ' ) * partial + // caret ::= '^' ( ' ' ) * partial + const match = /^(\^|~|<=|<|>=|>|=|v|!)/.exec(str); + const start = match ? match[0] : ""; + const remainder = parsePartial( + start.length ? str.slice(start.length).trim() : str.trim() + ); + + switch (start) { + case "^": + if (remainder.length > 1 && remainder[1] === 0) { + if (remainder.length > 2 && remainder[2] === 0) { + return [3, ...remainder.slice(1)]; + } + return [2, ...remainder.slice(1)]; + } + return [1, ...remainder.slice(1)]; + case "~": + if (remainder.length === 2 && remainder[0] === 0) { + return [1, ...remainder.slice(1)]; + } + return [2, ...remainder.slice(1)]; + case ">=": + return remainder; + case "=": + case "v": + case "": + return toFixed(remainder); + case "<": + return negate(remainder); + case ">": { + // and( >=, not( = ) ) => >=, =, not, and + const fixed = toFixed(remainder); + // eslint-disable-next-line no-sparse-arrays + return [, fixed, 0, remainder, 2]; + } + case "<=": + // or( <, = ) => <, =, or + // eslint-disable-next-line no-sparse-arrays + return [, toFixed(remainder), negate(remainder), 1]; + case "!": { + // not = + const fixed = toFixed(remainder); + // eslint-disable-next-line no-sparse-arrays + return [, fixed, 0]; + } + default: + throw new Error("Unexpected start value"); + } + }; + + /** + * + * @param {SemVerRangeItem[][]} items items + * @param {number} fn fn + * @returns {SemVerRange} result + */ + const combine = (items, fn) => { + if (items.length === 1) return items[0]; + const arr = []; + for (const item of items.slice().reverse()) { + if (0 in item) { + arr.push(item); + } else { + arr.push(...item.slice(1)); + } + } + + // eslint-disable-next-line no-sparse-arrays + return [, ...arr, ...items.slice(1).map(() => fn)]; + }; + + /** + * @param {string} str str + * @returns {SemVerRange} + */ + const parseRange = str => { + // range ::= hyphen | simple ( ' ' ( ' ' ) * simple ) * | '' + // hyphen ::= partial ( ' ' ) * ' - ' ( ' ' ) * partial + const items = str.split(/\s+-\s+/); + + if (items.length === 1) { + str = str.trim(); + + /** @type {SemVerRangeItem[][]} */ + const items = []; + const r = /[-0-9A-Za-z]\s+/g; + var start = 0; + var match; + while ((match = r.exec(str))) { + const end = match.index + 1; + items.push( + /** @type {SemVerRangeItem[]} */ + (parseSimple(str.slice(start, end).trim())) + ); + start = end; + } + items.push( + /** @type {SemVerRangeItem[]} */ + (parseSimple(str.slice(start).trim())) + ); + return combine(items, 2); + } + + const a = parsePartial(items[0]); + const b = parsePartial(items[1]); + // >=a <=b => and( >=a, or( >=a, { + // range-set ::= range ( logical-or range ) * + // logical-or ::= ( ' ' ) * '||' ( ' ' ) * + const items = + /** @type {SemVerRangeItem[][]} */ + (str.split(/\s*\|\|\s*/).map(parseRange)); + + return combine(items, 1); + }; + + return parseLogicalOr(str); +}; + +/* eslint-disable eqeqeq */ +/** + * @param {SemVerRange} range + * @returns {string} + */ +const rangeToString = range => { + var fixCount = /** @type {number} */ (range[0]); + var str = ""; + if (range.length === 1) { + return "*"; + } else if (fixCount + 0.5) { + str += + fixCount == 0 + ? ">=" + : fixCount == -1 + ? "<" + : fixCount == 1 + ? "^" + : fixCount == 2 + ? "~" + : fixCount > 0 + ? "=" + : "!="; + var needDot = 1; + for (var i = 1; i < range.length; i++) { + var item = range[i]; + var t = (typeof item)[0]; + needDot--; + str += + t == "u" + ? // undefined: prerelease marker, add an "-" + "-" + : // number or string: add the item, set flag to add an "." between two of them + (needDot > 0 ? "." : "") + ((needDot = 2), item); + } + return str; + } + /** @type {string[]} */ + var stack = []; + // eslint-disable-next-line no-redeclare + for (var i = 1; i < range.length; i++) { + // eslint-disable-next-line no-redeclare + var item = range[i]; + stack.push( + item === 0 + ? "not(" + pop() + ")" + : item === 1 + ? "(" + pop() + " || " + pop() + ")" + : item === 2 + ? stack.pop() + " " + stack.pop() + : rangeToString(/** @type {SemVerRange} */ (item)) + ); + } + return pop(); + + function pop() { + return /** @type {string} */ (stack.pop()).replace(/^\((.+)\)$/, "$1"); + } +}; + +module.exports.rangeToString = rangeToString; + +/** + * @param {SemVerRange} range version range + * @param {string} version the version + * @returns {boolean} if version satisfy the range + */ +const satisfy = (range, version) => { + if (0 in range) { + // @ts-expect-error + version = parseVersion(version); + var fixCount = /** @type {number} */ (range[0]); + // when negated is set it swill set for < instead of >= + var negated = fixCount < 0; + if (negated) fixCount = -fixCount - 1; + for (var i = 0, j = 1, isEqual = true; ; j++, i++) { + // cspell:word nequal nequ + + // when isEqual = true: + // range version: EOA/object undefined number string + // EOA equal block big-ver big-ver + // undefined bigger next big-ver big-ver + // number smaller block cmp big-cmp + // fixed number smaller block cmp-fix differ + // string smaller block differ cmp + // fixed string smaller block small-cmp cmp-fix + + // when isEqual = false: + // range version: EOA/object undefined number string + // EOA nequal block next-ver next-ver + // undefined nequal block next-ver next-ver + // number nequal block next next + // fixed number nequal block next next (this never happens) + // string nequal block next next + // fixed string nequal block next next (this never happens) + + // EOA end of array + // equal (version is equal range): + // when !negated: return true, + // when negated: return false + // bigger (version is bigger as range): + // when fixed: return false, + // when !negated: return true, + // when negated: return false, + // smaller (version is smaller as range): + // when !negated: return false, + // when negated: return true + // nequal (version is not equal range (> resp <)): return true + // block (version is in different prerelease area): return false + // differ (version is different from fixed range (string vs. number)): return false + // next: continues to the next items + // next-ver: when fixed: return false, continues to the next item only for the version, sets isEqual=false + // big-ver: when fixed || negated: return false, continues to the next item only for the version, sets isEqual=false + // next-nequ: continues to the next items, sets isEqual=false + // cmp (negated === false): version < range => return false, version > range => next-nequ, else => next + // cmp (negated === true): version > range => return false, version < range => next-nequ, else => next + // cmp-fix: version == range => next, else => return false + // big-cmp: when negated => return false, else => next-nequ + // small-cmp: when negated => next-nequ, else => return false + + var rangeType = + /** @type {"s" | "n" | "u" | ""} */ + (j < range.length ? (typeof range[j])[0] : ""); + + /** @type {number | string | undefined} */ + var versionValue; + /** @type {"n" | "s" | "u" | "o" | undefined} */ + var versionType; + + // Handles first column in both tables (end of version or object) + if ( + i >= version.length || + ((versionValue = version[i]), + (versionType = /** @type {"n" | "s" | "u" | "o"} */ ( + (typeof versionValue)[0] + )) == "o") + ) { + // Handles nequal + if (!isEqual) return true; + // Handles bigger + if (rangeType == "u") return j > fixCount && !negated; + // Handles equal and smaller: (range === EOA) XOR negated + return (rangeType == "") != negated; // equal + smaller + } + + // Handles second column in both tables (version = undefined) + if (versionType == "u") { + if (!isEqual || rangeType != "u") { + return false; + } + } + + // switch between first and second table + else if (isEqual) { + // Handle diagonal + if (rangeType == versionType) { + if (j <= fixCount) { + // Handles "cmp-fix" cases + if (versionValue != range[j]) { + return false; + } + } else { + // Handles "cmp" cases + if ( + negated + ? versionValue > /** @type {(number | string)[]} */ (range)[j] + : versionValue < /** @type {(number | string)[]} */ (range)[j] + ) { + return false; + } + if (versionValue != range[j]) isEqual = false; + } + } + + // Handle big-ver + else if (rangeType != "s" && rangeType != "n") { + if (negated || j <= fixCount) return false; + isEqual = false; + j--; + } + + // Handle differ, big-cmp and small-cmp + else if (j <= fixCount || versionType < rangeType != negated) { + return false; + } else { + isEqual = false; + } + } else { + // Handles all "next-ver" cases in the second table + // eslint-disable-next-line no-lonely-if + if (rangeType != "s" && rangeType != "n") { + isEqual = false; + j--; + } + + // next is applied by default + } + } + } + + /** @type {(boolean | number)[]} */ + var stack = []; + var p = stack.pop.bind(stack); + // eslint-disable-next-line no-redeclare + for (var i = 1; i < range.length; i++) { + var item = /** @type {SemVerRangeItem[] | 0 | 1 | 2} */ (range[i]); + + stack.push( + item == 1 + ? /** @type {() => number} */ (p)() | /** @type {() => number} */ (p)() + : item == 2 + ? /** @type {() => number} */ (p)() & + /** @type {() => number} */ (p)() + : item + ? satisfy(item, version) + : !p() + ); + } + return !!p(); +}; +/* eslint-enable eqeqeq */ +module.exports.satisfy = satisfy; + +/** + * @param {SemVerRange | string | number | false | undefined} json + * @returns {string} + */ +module.exports.stringifyHoley = json => { + switch (typeof json) { + case "undefined": + return ""; + case "object": + if (Array.isArray(json)) { + let str = "["; + for (let i = 0; i < json.length; i++) { + if (i !== 0) str += ","; + str += this.stringifyHoley(json[i]); + } + str += "]"; + return str; + } + + return JSON.stringify(json); + default: + return JSON.stringify(json); + } +}; + +//#region runtime code: parseVersion +/** + * @param {RuntimeTemplate} runtimeTemplate + * @returns {string} + */ +exports.parseVersionRuntimeCode = runtimeTemplate => + `var parseVersion = ${runtimeTemplate.basicFunction("str", [ + "// see webpack/lib/util/semver.js for original code", + `var p=${runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(p)"}{return p.split(".").map((${runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(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;` + ])}`; +//#endregion + +//#region runtime code: versionLt +/** + * @param {RuntimeTemplate} runtimeTemplate + * @returns {string} + */ +exports.versionLtRuntimeCode = runtimeTemplate => + `var versionLt = ${runtimeTemplate.basicFunction("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 + `var rangeToString = ${runtimeTemplate.basicFunction("range", [ + "// 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 + `var satisfy = ${runtimeTemplate.basicFunction("range, version", [ + "// 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 + require("../serialization/BinaryMiddleware") +); +const getObjectMiddleware = memoize(() => + require("../serialization/ObjectMiddleware") +); +const getSingleItemMiddleware = memoize(() => + require("../serialization/SingleItemMiddleware") +); +const getSerializer = memoize(() => require("../serialization/Serializer")); +const getSerializerMiddleware = memoize(() => + require("../serialization/SerializerMiddleware") +); + +const getBinaryMiddlewareInstance = memoize( + () => new (getBinaryMiddleware())() +); + +const registerSerializers = memoize(() => { + require("./registerExternalSerializer"); + + // Load internal paths with a relative require + // This allows bundling all internal serializers + const internalSerializables = require("./internalSerializables"); + getObjectMiddleware().registerLoader(/^webpack\/lib\//, req => { + const loader = + internalSerializables[ + /** @type {keyof import("./internalSerializables")} */ + (req.slice("webpack/lib/".length)) + ]; + if (loader) { + loader(); + } else { + console.warn(`${req} not found in internalSerializables`); + } + return true; + }); +}); + +/** @type {Serializer} */ +let buffersSerializer; + +// Expose serialization API +module.exports = { + get register() { + return getObjectMiddleware().register; + }, + get registerLoader() { + return getObjectMiddleware().registerLoader; + }, + get registerNotSerializable() { + return getObjectMiddleware().registerNotSerializable; + }, + get NOT_SERIALIZABLE() { + return getObjectMiddleware().NOT_SERIALIZABLE; + }, + /** @type {MEASURE_START_OPERATION} */ + get MEASURE_START_OPERATION() { + return getBinaryMiddleware().MEASURE_START_OPERATION; + }, + /** @type {MEASURE_END_OPERATION} */ + get MEASURE_END_OPERATION() { + return getBinaryMiddleware().MEASURE_END_OPERATION; + }, + /** + * @returns {Serializer} buffer serializer + */ + get buffersSerializer() { + if (buffersSerializer !== undefined) return buffersSerializer; + registerSerializers(); + const Serializer = getSerializer(); + const binaryMiddleware = getBinaryMiddlewareInstance(); + const SerializerMiddleware = getSerializerMiddleware(); + const SingleItemMiddleware = getSingleItemMiddleware(); + return (buffersSerializer = new Serializer([ + new SingleItemMiddleware(), + new (getObjectMiddleware())(context => { + if (context.write) { + /** + * @param {any} value value + */ + context.writeLazy = value => { + context.write( + SerializerMiddleware.createLazy(value, binaryMiddleware) + ); + }; + } + }, "md4"), + binaryMiddleware + ])); + }, + /** + * @param {IntermediateFileSystem} fs filesystem + * @param {string | Hash} hashFunction hash function to use + * @returns {Serializer} file serializer + */ + createFileSerializer: (fs, hashFunction) => { + registerSerializers(); + const Serializer = getSerializer(); + const FileMiddleware = require("../serialization/FileMiddleware"); + const fileMiddleware = new FileMiddleware(fs, hashFunction); + const binaryMiddleware = getBinaryMiddlewareInstance(); + const SerializerMiddleware = getSerializerMiddleware(); + const SingleItemMiddleware = getSingleItemMiddleware(); + return new Serializer([ + new SingleItemMiddleware(), + new (getObjectMiddleware())(context => { + if (context.write) { + /** + * @param {any} value value + */ + context.writeLazy = value => { + context.write( + SerializerMiddleware.createLazy(value, binaryMiddleware) + ); + }; + /** + * @param {any} value value + * @param {object=} options lazy options + * @returns {function(): Promise | any} lazy function + */ + context.writeSeparate = (value, options) => { + const lazy = SerializerMiddleware.createLazy( + value, + fileMiddleware, + options + ); + context.write(lazy); + return lazy; + }; + } + }, hashFunction), + binaryMiddleware, + fileMiddleware + ]); + } +}; diff --git a/webpack-lib/lib/util/smartGrouping.js b/webpack-lib/lib/util/smartGrouping.js new file mode 100644 index 00000000000..f75648c45b8 --- /dev/null +++ b/webpack-lib/lib/util/smartGrouping.js @@ -0,0 +1,206 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** + * @typedef {object} GroupOptions + * @property {boolean=} groupChildren + * @property {boolean=} force + * @property {number=} targetGroupCount + */ + +/** + * @template T + * @template R + * @typedef {object} GroupConfig + * @property {function(T): string[] | undefined} getKeys + * @property {function(string, (R | T)[], T[]): R} createGroup + * @property {function(string, T[]): GroupOptions=} getOptions + */ + +/** + * @template T + * @template R + * @typedef {object} ItemWithGroups + * @property {T} item + * @property {Set>} groups + */ + +/** + * @template T + * @template R + * @typedef {{ config: GroupConfig, name: string, alreadyGrouped: boolean, items: Set> | undefined }} Group + */ + +/** + * @template T + * @template R + * @param {T[]} items the list of items + * @param {GroupConfig[]} groupConfigs configuration + * @returns {(R | T)[]} grouped items + */ +const smartGrouping = (items, groupConfigs) => { + /** @type {Set>} */ + const itemsWithGroups = new Set(); + /** @type {Map>} */ + const allGroups = new Map(); + for (const item of items) { + /** @type {Set>} */ + const groups = new Set(); + for (let i = 0; i < groupConfigs.length; i++) { + const groupConfig = groupConfigs[i]; + const keys = groupConfig.getKeys(item); + if (keys) { + for (const name of keys) { + const key = `${i}:${name}`; + let group = allGroups.get(key); + if (group === undefined) { + allGroups.set( + key, + (group = { + config: groupConfig, + name, + alreadyGrouped: false, + items: undefined + }) + ); + } + groups.add(group); + } + } + } + itemsWithGroups.add({ + item, + groups + }); + } + /** + * @param {Set>} itemsWithGroups input items with groups + * @returns {(T | R)[]} groups items + */ + const runGrouping = itemsWithGroups => { + const totalSize = itemsWithGroups.size; + for (const entry of itemsWithGroups) { + for (const group of entry.groups) { + if (group.alreadyGrouped) continue; + const items = group.items; + if (items === undefined) { + group.items = new Set([entry]); + } else { + items.add(entry); + } + } + } + /** @type {Map, { items: Set>, options: GroupOptions | false | undefined, used: boolean }>} */ + const groupMap = new Map(); + for (const group of allGroups.values()) { + if (group.items) { + const items = group.items; + group.items = undefined; + groupMap.set(group, { + items, + options: undefined, + used: false + }); + } + } + /** @type {(T | R)[]} */ + const results = []; + for (;;) { + /** @type {Group | undefined} */ + let bestGroup; + let bestGroupSize = -1; + let bestGroupItems; + let bestGroupOptions; + for (const [group, state] of groupMap) { + const { items, used } = state; + let options = state.options; + if (options === undefined) { + const groupConfig = group.config; + state.options = options = + (groupConfig.getOptions && + groupConfig.getOptions( + group.name, + Array.from(items, ({ item }) => item) + )) || + false; + } + + const force = options && options.force; + if (!force) { + if (bestGroupOptions && bestGroupOptions.force) continue; + if (used) continue; + if (items.size <= 1 || totalSize - items.size <= 1) { + continue; + } + } + const targetGroupCount = (options && options.targetGroupCount) || 4; + const sizeValue = force + ? items.size + : Math.min( + items.size, + (totalSize * 2) / targetGroupCount + + itemsWithGroups.size - + items.size + ); + if ( + sizeValue > bestGroupSize || + (force && (!bestGroupOptions || !bestGroupOptions.force)) + ) { + bestGroup = group; + bestGroupSize = sizeValue; + bestGroupItems = items; + bestGroupOptions = options; + } + } + if (bestGroup === undefined) { + break; + } + const items = new Set(bestGroupItems); + const options = bestGroupOptions; + + const groupChildren = !options || options.groupChildren !== false; + + for (const item of items) { + itemsWithGroups.delete(item); + // Remove all groups that items have from the map to not select them again + for (const group of item.groups) { + const state = groupMap.get(group); + if (state !== undefined) { + state.items.delete(item); + if (state.items.size === 0) { + groupMap.delete(group); + } else { + state.options = undefined; + if (groupChildren) { + state.used = true; + } + } + } + } + } + groupMap.delete(bestGroup); + + const key = bestGroup.name; + const groupConfig = bestGroup.config; + + const allItems = Array.from(items, ({ item }) => item); + + bestGroup.alreadyGrouped = true; + const children = groupChildren ? runGrouping(items) : allItems; + bestGroup.alreadyGrouped = false; + + results.push(groupConfig.createGroup(key, children, allItems)); + } + for (const { item } of itemsWithGroups) { + results.push(item); + } + return results; + }; + return runGrouping(itemsWithGroups); +}; + +module.exports = smartGrouping; diff --git a/webpack-lib/lib/util/source.js b/webpack-lib/lib/util/source.js new file mode 100644 index 00000000000..b9516786ba1 --- /dev/null +++ b/webpack-lib/lib/util/source.js @@ -0,0 +1,61 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("webpack-sources").Source} Source */ + +/** @type {WeakMap>} */ +const equalityCache = new WeakMap(); + +/** + * @param {Source} a a source + * @param {Source} b another source + * @returns {boolean} true, when both sources are equal + */ +const _isSourceEqual = (a, b) => { + // prefer .buffer(), it's called anyway during emit + /** @type {Buffer|string} */ + let aSource = typeof a.buffer === "function" ? a.buffer() : a.source(); + /** @type {Buffer|string} */ + let bSource = typeof b.buffer === "function" ? b.buffer() : b.source(); + if (aSource === bSource) return true; + if (typeof aSource === "string" && typeof bSource === "string") return false; + if (!Buffer.isBuffer(aSource)) aSource = Buffer.from(aSource, "utf-8"); + if (!Buffer.isBuffer(bSource)) bSource = Buffer.from(bSource, "utf-8"); + return aSource.equals(bSource); +}; + +/** + * @param {Source} a a source + * @param {Source} b another source + * @returns {boolean} true, when both sources are equal + */ +const isSourceEqual = (a, b) => { + if (a === b) return true; + const cache1 = equalityCache.get(a); + if (cache1 !== undefined) { + const result = cache1.get(b); + if (result !== undefined) return result; + } + const result = _isSourceEqual(a, b); + if (cache1 !== undefined) { + cache1.set(b, result); + } else { + const map = new WeakMap(); + map.set(b, result); + equalityCache.set(a, map); + } + const cache2 = equalityCache.get(b); + if (cache2 !== undefined) { + cache2.set(a, result); + } else { + const map = new WeakMap(); + map.set(a, result); + equalityCache.set(b, map); + } + return result; +}; +module.exports.isSourceEqual = isSourceEqual; diff --git a/webpack-lib/lib/validateSchema.js b/webpack-lib/lib/validateSchema.js new file mode 100644 index 00000000000..83841b61119 --- /dev/null +++ b/webpack-lib/lib/validateSchema.js @@ -0,0 +1,176 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { validate } = require("schema-utils"); + +/* cSpell:disable */ +const DID_YOU_MEAN = { + rules: "module.rules", + loaders: "module.rules or module.rules.*.use", + query: "module.rules.*.options (BREAKING CHANGE since webpack 5)", + noParse: "module.noParse", + filename: "output.filename or module.rules.*.generator.filename", + file: "output.filename", + chunkFilename: "output.chunkFilename", + chunkfilename: "output.chunkFilename", + ecmaVersion: + "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", + ecmaversion: + "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", + ecma: "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", + path: "output.path", + pathinfo: "output.pathinfo", + pathInfo: "output.pathinfo", + jsonpFunction: "output.chunkLoadingGlobal (BREAKING CHANGE since webpack 5)", + chunkCallbackName: + "output.chunkLoadingGlobal (BREAKING CHANGE since webpack 5)", + jsonpScriptType: "output.scriptType (BREAKING CHANGE since webpack 5)", + hotUpdateFunction: "output.hotUpdateGlobal (BREAKING CHANGE since webpack 5)", + splitChunks: "optimization.splitChunks", + immutablePaths: "snapshot.immutablePaths", + managedPaths: "snapshot.managedPaths", + maxModules: "stats.modulesSpace (BREAKING CHANGE since webpack 5)", + hashedModuleIds: + 'optimization.moduleIds: "hashed" (BREAKING CHANGE since webpack 5)', + namedChunks: + 'optimization.chunkIds: "named" (BREAKING CHANGE since webpack 5)', + namedModules: + 'optimization.moduleIds: "named" (BREAKING CHANGE since webpack 5)', + occurrenceOrder: + 'optimization.chunkIds: "size" and optimization.moduleIds: "size" (BREAKING CHANGE since webpack 5)', + automaticNamePrefix: + "optimization.splitChunks.[cacheGroups.*].idHint (BREAKING CHANGE since webpack 5)", + noEmitOnErrors: + "optimization.emitOnErrors (BREAKING CHANGE since webpack 5: logic is inverted to avoid negative flags)", + Buffer: + "to use the ProvidePlugin to process the Buffer variable to modules as polyfill\n" + + "BREAKING CHANGE: webpack 5 no longer provided Node.js polyfills by default.\n" + + "Note: if you are using 'node.Buffer: false', you can just remove that as this is the default behavior now.\n" + + "To provide a polyfill to modules use:\n" + + 'new ProvidePlugin({ Buffer: ["buffer", "Buffer"] }) and npm install buffer.', + process: + "to use the ProvidePlugin to process the process variable to modules as polyfill\n" + + "BREAKING CHANGE: webpack 5 no longer provided Node.js polyfills by default.\n" + + "Note: if you are using 'node.process: false', you can just remove that as this is the default behavior now.\n" + + "To provide a polyfill to modules use:\n" + + 'new ProvidePlugin({ process: "process" }) and npm install buffer.' +}; + +const REMOVED = { + concord: + "BREAKING CHANGE: resolve.concord has been removed and is no longer available.", + devtoolLineToLine: + "BREAKING CHANGE: output.devtoolLineToLine has been removed and is no longer available." +}; +/* cSpell:enable */ + +/** + * @param {Parameters[0]} schema a json schema + * @param {Parameters[1]} options the options that should be validated + * @param {Parameters[2]=} validationConfiguration configuration for generating errors + * @returns {void} + */ +const validateSchema = (schema, options, validationConfiguration) => { + validate( + schema, + options, + validationConfiguration || { + name: "Webpack", + postFormatter: (formattedError, error) => { + const children = error.children; + if ( + children && + children.some( + child => + child.keyword === "absolutePath" && + child.instancePath === "/output/filename" + ) + ) { + return `${formattedError}\nPlease use output.path to specify absolute path and output.filename for the file name.`; + } + + if ( + children && + children.some( + child => + child.keyword === "pattern" && child.instancePath === "/devtool" + ) + ) { + return ( + `${formattedError}\n` + + "BREAKING CHANGE since webpack 5: The devtool option is more strict.\n" + + "Please strictly follow the order of the keywords in the pattern." + ); + } + + if (error.keyword === "additionalProperties") { + const params = error.params; + if ( + Object.prototype.hasOwnProperty.call( + DID_YOU_MEAN, + params.additionalProperty + ) + ) { + return `${formattedError}\nDid you mean ${ + DID_YOU_MEAN[ + /** @type {keyof DID_YOU_MEAN} */ (params.additionalProperty) + ] + }?`; + } + + if ( + Object.prototype.hasOwnProperty.call( + REMOVED, + params.additionalProperty + ) + ) { + return `${formattedError}\n${ + REMOVED[/** @type {keyof REMOVED} */ (params.additionalProperty)] + }?`; + } + + if (!error.instancePath) { + if (params.additionalProperty === "debug") { + return ( + `${formattedError}\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" + + "]" + ); + } + + if (params.additionalProperty) { + return ( + `${formattedError}\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" + + ` ${params.additionalProperty}: …\n` + + " }\n" + + " })\n" + + " ]" + ); + } + } + } + + return formattedError; + } + } + ); +}; +module.exports = validateSchema; diff --git a/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js b/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js new file mode 100644 index 00000000000..e1f1c3a4b14 --- /dev/null +++ b/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js @@ -0,0 +1,142 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ + +/** + * @typedef {object} AsyncWasmLoadingRuntimeModuleOptions + * @property {(function(string): string)=} generateBeforeLoadBinaryCode + * @property {function(string): string} generateLoadBinaryCode + * @property {(function(): string)=} generateBeforeInstantiateStreaming + * @property {boolean} supportsStreaming + */ + +class AsyncWasmLoadingRuntimeModule extends RuntimeModule { + /** + * @param {AsyncWasmLoadingRuntimeModuleOptions} options options + */ + constructor({ + generateLoadBinaryCode, + generateBeforeLoadBinaryCode, + generateBeforeInstantiateStreaming, + supportsStreaming + }) { + super("wasm loading", RuntimeModule.STAGE_NORMAL); + this.generateLoadBinaryCode = generateLoadBinaryCode; + this.generateBeforeLoadBinaryCode = generateBeforeLoadBinaryCode; + this.generateBeforeInstantiateStreaming = + generateBeforeInstantiateStreaming; + this.supportsStreaming = supportsStreaming; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const chunk = /** @type {Chunk} */ (this.chunk); + const { outputOptions, runtimeTemplate } = compilation; + const fn = RuntimeGlobals.instantiateWasm; + const wasmModuleSrcPath = compilation.getPath( + JSON.stringify(outputOptions.webassemblyModuleFilename), + { + hash: `" + ${RuntimeGlobals.getFullHash}() + "`, + hashWithLength: length => + `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`, + module: { + id: '" + wasmModuleId + "', + hash: '" + wasmModuleHash + "', + hashWithLength(length) { + return `" + wasmModuleHash.slice(0, ${length}) + "`; + } + }, + runtime: chunk.runtime + } + ); + + const loader = this.generateLoadBinaryCode(wasmModuleSrcPath); + const fallback = [ + `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`, + `.then(${runtimeTemplate.returningFunction( + "WebAssembly.instantiate(bytes, importsObj)", + "bytes" + )})`, + `.then(${runtimeTemplate.returningFunction( + "Object.assign(exports, res.instance.exports)", + "res" + )})` + ]; + const getStreaming = () => { + const concat = (/** @type {string[]} */ ...text) => text.join(""); + return [ + this.generateBeforeLoadBinaryCode + ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath) + : "", + `var req = ${loader};`, + `var fallback = ${runtimeTemplate.returningFunction( + Template.asString(["req", Template.indent(fallback)]) + )};`, + concat( + "return req.then(", + runtimeTemplate.basicFunction("res", [ + 'if (typeof WebAssembly.instantiateStreaming === "function") {', + Template.indent( + this.generateBeforeInstantiateStreaming + ? this.generateBeforeInstantiateStreaming() + : "" + ), + Template.indent([ + "return WebAssembly.instantiateStreaming(res, importsObj)", + Template.indent([ + ".then(", + Template.indent([ + `${runtimeTemplate.returningFunction( + "Object.assign(exports, res.instance.exports)", + "res" + )},`, + runtimeTemplate.basicFunction("e", [ + 'if(res.headers.get("Content-Type") !== "application/wasm") {', + Template.indent([ + 'console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n", e);', + "return fallback();" + ]), + "}", + "throw e;" + ]) + ]), + ");" + ]) + ]), + "}", + "return fallback();" + ]), + ");" + ) + ]; + }; + + return `${fn} = ${runtimeTemplate.basicFunction( + "exports, wasmModuleId, wasmModuleHash, importsObj", + this.supportsStreaming + ? getStreaming() + : [ + this.generateBeforeLoadBinaryCode + ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath) + : "", + `return ${loader}`, + `${Template.indent(fallback)};` + ] + )};`; + } +} + +module.exports = AsyncWasmLoadingRuntimeModule; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js new file mode 100644 index 00000000000..558541e0d84 --- /dev/null +++ b/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js @@ -0,0 +1,61 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Generator = require("../Generator"); +const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ + +/** + * @typedef {object} AsyncWebAssemblyGeneratorOptions + * @property {boolean} [mangleImports] mangle imports + */ + +class AsyncWebAssemblyGenerator extends Generator { + /** + * @param {AsyncWebAssemblyGeneratorOptions} options options + */ + constructor(options) { + super(); + this.options = options; + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return WEBASSEMBLY_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + const originalSource = module.originalSource(); + if (!originalSource) { + return 0; + } + return originalSource.size(); + } + + /** + * @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) { + return /** @type {Source} */ (module.originalSource()); + } +} + +module.exports = AsyncWebAssemblyGenerator; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js new file mode 100644 index 00000000000..30f30b0ece4 --- /dev/null +++ b/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js @@ -0,0 +1,206 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const Generator = require("../Generator"); +const InitFragment = require("../InitFragment"); +const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +/** + * @typedef {{ request: string, importVar: string }} ImportObjRequestItem + */ + +class AsyncWebAssemblyJavascriptGenerator extends Generator { + /** + * @param {OutputOptions["webassemblyModuleFilename"]} filenameTemplate template for the WebAssembly module filename + */ + constructor(filenameTemplate) { + super(); + this.filenameTemplate = filenameTemplate; + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return WEBASSEMBLY_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + return 40 + module.dependencies.length * 10; + } + + /** + * @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 { + runtimeTemplate, + chunkGraph, + moduleGraph, + runtimeRequirements, + runtime + } = generateContext; + runtimeRequirements.add(RuntimeGlobals.module); + runtimeRequirements.add(RuntimeGlobals.moduleId); + runtimeRequirements.add(RuntimeGlobals.exports); + runtimeRequirements.add(RuntimeGlobals.instantiateWasm); + /** @type {InitFragment>[]} */ + const initFragments = []; + /** @type {Map} */ + const depModules = new Map(); + /** @type {Map} */ + const wasmDepsByRequest = new Map(); + for (const dep of module.dependencies) { + if (dep instanceof WebAssemblyImportDependency) { + const module = /** @type {Module} */ (moduleGraph.getModule(dep)); + if (!depModules.has(module)) { + depModules.set(module, { + request: dep.request, + importVar: `WEBPACK_IMPORTED_MODULE_${depModules.size}` + }); + } + let list = wasmDepsByRequest.get(dep.request); + if (list === undefined) { + list = []; + wasmDepsByRequest.set(dep.request, list); + } + list.push(dep); + } + } + + /** @type {Array} */ + const promises = []; + + const importStatements = Array.from( + depModules, + ([importedModule, { request, importVar }]) => { + if (moduleGraph.isAsync(importedModule)) { + promises.push(importVar); + } + return runtimeTemplate.importStatement({ + update: false, + module: importedModule, + chunkGraph, + request, + originModule: module, + importVar, + runtimeRequirements + }); + } + ); + const importsCode = importStatements.map(([x]) => x).join(""); + const importsCompatCode = importStatements.map(([_, x]) => x).join(""); + + const importObjRequestItems = Array.from( + wasmDepsByRequest, + ([request, deps]) => { + const exportItems = deps.map(dep => { + const importedModule = + /** @type {Module} */ + (moduleGraph.getModule(dep)); + const importVar = + /** @type {ImportObjRequestItem} */ + (depModules.get(importedModule)).importVar; + return `${JSON.stringify( + dep.name + )}: ${runtimeTemplate.exportFromImport({ + moduleGraph, + module: importedModule, + request, + exportName: dep.name, + originModule: module, + asiSafe: true, + isCall: false, + callContext: false, + defaultInterop: true, + importVar, + initFragments, + runtime, + runtimeRequirements + })}`; + }); + return Template.asString([ + `${JSON.stringify(request)}: {`, + Template.indent(exportItems.join(",\n")), + "}" + ]); + } + ); + + const importsObj = + importObjRequestItems.length > 0 + ? Template.asString([ + "{", + Template.indent(importObjRequestItems.join(",\n")), + "}" + ]) + : undefined; + + const instantiateCall = `${RuntimeGlobals.instantiateWasm}(${module.exportsArgument}, ${ + module.moduleArgument + }.id, ${JSON.stringify( + chunkGraph.getRenderedModuleHash(module, runtime) + )}${importsObj ? `, ${importsObj})` : ")"}`; + + if (promises.length > 0) + runtimeRequirements.add(RuntimeGlobals.asyncModule); + + const source = new RawSource( + promises.length > 0 + ? Template.asString([ + `var __webpack_instantiate__ = ${runtimeTemplate.basicFunction( + `[${promises.join(", ")}]`, + `${importsCompatCode}return ${instantiateCall};` + )}`, + `${RuntimeGlobals.asyncModule}(${ + module.moduleArgument + }, async ${runtimeTemplate.basicFunction( + "__webpack_handle_async_dependencies__, __webpack_async_result__", + [ + "try {", + importsCode, + `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${promises.join( + ", " + )}]);`, + `var [${promises.join( + ", " + )}] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__;`, + `${importsCompatCode}await ${instantiateCall};`, + "__webpack_async_result__();", + "} catch(e) { __webpack_async_result__(e); }" + ] + )}, 1);` + ]) + : `${importsCode}${importsCompatCode}module.exports = ${instantiateCall};` + ); + + return InitFragment.addToSource(source, initFragments, generateContext); + } +} + +module.exports = AsyncWebAssemblyJavascriptGenerator; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js new file mode 100644 index 00000000000..74a612757e9 --- /dev/null +++ b/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js @@ -0,0 +1,218 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const Generator = require("../Generator"); +const { tryRunOrWebpackError } = require("../HookWebpackError"); +const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const memoize = require("../util/memoize"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../Template").RenderManifestEntry} RenderManifestEntry */ +/** @typedef {import("../Template").RenderManifestOptions} RenderManifestOptions */ +/** @typedef {import("../WebpackError")} WebpackError */ + +const getAsyncWebAssemblyGenerator = memoize(() => + require("./AsyncWebAssemblyGenerator") +); +const getAsyncWebAssemblyJavascriptGenerator = memoize(() => + require("./AsyncWebAssemblyJavascriptGenerator") +); +const getAsyncWebAssemblyParser = memoize(() => + require("./AsyncWebAssemblyParser") +); + +/** + * @typedef {object} WebAssemblyRenderContext + * @property {Chunk} chunk the chunk + * @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 {CodeGenerationResults} codeGenerationResults results of code generation + */ + +/** + * @typedef {object} CompilationHooks + * @property {SyncWaterfallHook<[Source, Module, WebAssemblyRenderContext]>} renderModuleContent + */ + +/** + * @typedef {object} AsyncWebAssemblyModulesPluginOptions + * @property {boolean} [mangleImports] mangle imports + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +const PLUGIN_NAME = "AsyncWebAssemblyModulesPlugin"; + +class AsyncWebAssemblyModulesPlugin { + /** + * @param {Compilation} compilation the compilation + * @returns {CompilationHooks} 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 = { + renderModuleContent: new SyncWaterfallHook([ + "source", + "module", + "renderContext" + ]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {AsyncWebAssemblyModulesPluginOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + const hooks = + AsyncWebAssemblyModulesPlugin.getCompilationHooks(compilation); + compilation.dependencyFactories.set( + WebAssemblyImportDependency, + normalModuleFactory + ); + + normalModuleFactory.hooks.createParser + .for(WEBASSEMBLY_MODULE_TYPE_ASYNC) + .tap(PLUGIN_NAME, () => { + const AsyncWebAssemblyParser = getAsyncWebAssemblyParser(); + + return new AsyncWebAssemblyParser(); + }); + normalModuleFactory.hooks.createGenerator + .for(WEBASSEMBLY_MODULE_TYPE_ASYNC) + .tap(PLUGIN_NAME, () => { + const AsyncWebAssemblyJavascriptGenerator = + getAsyncWebAssemblyJavascriptGenerator(); + const AsyncWebAssemblyGenerator = getAsyncWebAssemblyGenerator(); + + return Generator.byType({ + javascript: new AsyncWebAssemblyJavascriptGenerator( + compilation.outputOptions.webassemblyModuleFilename + ), + webassembly: new AsyncWebAssemblyGenerator(this.options) + }); + }); + + compilation.hooks.renderManifest.tap( + "WebAssemblyModulesPlugin", + (result, options) => { + const { moduleGraph, chunkGraph, runtimeTemplate } = compilation; + const { + chunk, + outputOptions, + dependencyTemplates, + codeGenerationResults + } = options; + + for (const module of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesByIdentifier + )) { + if (module.type === WEBASSEMBLY_MODULE_TYPE_ASYNC) { + const filenameTemplate = + /** @type {NonNullable} */ + (outputOptions.webassemblyModuleFilename); + + result.push({ + render: () => + this.renderModule( + module, + { + chunk, + dependencyTemplates, + runtimeTemplate, + moduleGraph, + chunkGraph, + codeGenerationResults + }, + hooks + ), + filenameTemplate, + pathOptions: { + module, + runtime: chunk.runtime, + chunkGraph + }, + auxiliary: true, + identifier: `webassemblyAsyncModule${chunkGraph.getModuleId( + module + )}`, + hash: chunkGraph.getModuleHash(module, chunk.runtime) + }); + } + } + + return result; + } + ); + } + ); + } + + /** + * @param {Module} module the rendered module + * @param {WebAssemblyRenderContext} renderContext options object + * @param {CompilationHooks} hooks hooks + * @returns {Source} the newly generated source from rendering + */ + renderModule(module, renderContext, hooks) { + const { codeGenerationResults, chunk } = renderContext; + try { + const moduleSource = codeGenerationResults.getSource( + module, + chunk.runtime, + "webassembly" + ); + return tryRunOrWebpackError( + () => + hooks.renderModuleContent.call(moduleSource, module, renderContext), + "AsyncWebAssemblyModulesPlugin.getCompilationHooks().renderModuleContent" + ); + } catch (err) { + /** @type {WebpackError} */ (err).module = module; + throw err; + } + } +} + +module.exports = AsyncWebAssemblyModulesPlugin; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js new file mode 100644 index 00000000000..40f1c79eacc --- /dev/null +++ b/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js @@ -0,0 +1,88 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const t = require("@webassemblyjs/ast"); +const { decode } = require("@webassemblyjs/wasm-parser"); +const EnvironmentNotSupportAsyncWarning = require("../EnvironmentNotSupportAsyncWarning"); +const Parser = require("../Parser"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ + +const decoderOpts = { + ignoreCodeSection: true, + ignoreDataSection: true, + + // this will avoid having to lookup with identifiers in the ModuleContext + ignoreCustomNameSection: true +}; + +class WebAssemblyParser extends Parser { + /** + * @param {{}=} options parser options + */ + constructor(options) { + super(); + this.hooks = Object.freeze({}); + this.options = options; + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (!Buffer.isBuffer(source)) { + throw new Error("WebAssemblyParser input must be a Buffer"); + } + + // flag it as async module + const buildInfo = /** @type {BuildInfo} */ (state.module.buildInfo); + buildInfo.strict = true; + const BuildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); + BuildMeta.exportsType = "namespace"; + BuildMeta.async = true; + EnvironmentNotSupportAsyncWarning.check( + state.module, + state.compilation.runtimeTemplate, + "asyncWebAssembly" + ); + + // parse it + const program = decode(source, decoderOpts); + const module = program.body[0]; + /** @type {Array} */ + const exports = []; + t.traverse(module, { + ModuleExport({ node }) { + exports.push(node.name); + }, + + ModuleImport({ node }) { + const dep = new WebAssemblyImportDependency( + node.module, + node.name, + node.descr, + false + ); + + state.module.addDependency(dep); + } + }); + + state.module.addDependency(new StaticExportsDependency(exports, false)); + + return state; + } +} + +module.exports = WebAssemblyParser; diff --git a/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js b/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js new file mode 100644 index 00000000000..34b6341ce0a --- /dev/null +++ b/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js @@ -0,0 +1,103 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Alexander Akait @alexander-akait +*/ + +"use strict"; + +const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +const PLUGIN_NAME = "UniversalCompileAsyncWasmPlugin"; + +class UniversalCompileAsyncWasmPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const globalWasmLoading = compilation.outputOptions.wasmLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const wasmLoading = + options && options.wasmLoading !== undefined + ? options.wasmLoading + : globalWasmLoading; + return wasmLoading === "universal"; + }; + const generateBeforeInstantiateStreaming = () => + Template.asString([ + "if (!useFetch) {", + Template.indent(["return fallback();"]), + "}" + ]); + const generateBeforeLoadBinaryCode = path => + Template.asString([ + "var useFetch = typeof document !== 'undefined' || typeof self !== 'undefined';", + `var wasmUrl = ${path};` + ]); + /** + * @type {(path: string) => string} + */ + const generateLoadBinaryCode = () => + Template.asString([ + "(useFetch", + Template.indent([ + `? fetch(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url))` + ]), + Template.indent([ + ": Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", + Template.indent([ + `readFile(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, + Template.indent([ + "if (err) return reject(err);", + "", + "// Fake fetch response", + "resolve({", + Template.indent(["arrayBuffer() { return buffer; }"]), + "});" + ]), + "});" + ]), + "})))" + ]) + ]); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.instantiateWasm) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC + ) + ) { + return; + } + compilation.addRuntimeModule( + chunk, + new AsyncWasmLoadingRuntimeModule({ + generateBeforeLoadBinaryCode, + generateLoadBinaryCode, + generateBeforeInstantiateStreaming, + supportsStreaming: true + }) + ); + }); + }); + } +} + +module.exports = UniversalCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js b/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js new file mode 100644 index 00000000000..5174862ca5c --- /dev/null +++ b/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js @@ -0,0 +1,16 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); + +module.exports = class UnsupportedWebAssemblyFeatureError extends WebpackError { + /** @param {string} message Error message */ + constructor(message) { + super(message); + this.name = "UnsupportedWebAssemblyFeatureError"; + this.hideStack = true; + } +}; diff --git a/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js b/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..654a6204f63 --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js @@ -0,0 +1,413 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const WebAssemblyUtils = require("./WebAssemblyUtils"); + +/** @typedef {import("@webassemblyjs/ast").Signature} Signature */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ + +// TODO webpack 6 remove the whole folder + +// Get all wasm modules +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Chunk} chunk the chunk + * @returns {Module[]} all wasm modules + */ +const getAllWasmModules = (moduleGraph, chunkGraph, chunk) => { + const wasmModules = chunk.getAllAsyncChunks(); + const array = []; + for (const chunk of wasmModules) { + for (const m of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesByIdentifier + )) { + if (m.type.startsWith("webassembly")) { + array.push(m); + } + } + } + + return array; +}; + +/** + * generates the import object function for a module + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {Module} module the module + * @param {boolean | undefined} mangle mangle imports + * @param {string[]} declarations array where declarations are pushed to + * @param {RuntimeSpec} runtime the runtime + * @returns {string} source code + */ +const generateImportObject = ( + chunkGraph, + module, + mangle, + declarations, + runtime +) => { + const moduleGraph = chunkGraph.moduleGraph; + /** @type {Map} */ + const waitForInstances = new Map(); + const properties = []; + const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies( + moduleGraph, + module, + mangle + ); + for (const usedDep of usedWasmDependencies) { + const dep = usedDep.dependency; + const importedModule = moduleGraph.getModule(dep); + const exportName = dep.name; + const usedName = + importedModule && + moduleGraph + .getExportsInfo(importedModule) + .getUsedName(exportName, runtime); + const description = dep.description; + const direct = dep.onlyDirectImport; + + const module = usedDep.module; + const name = usedDep.name; + + if (direct) { + const instanceVar = `m${waitForInstances.size}`; + waitForInstances.set( + instanceVar, + /** @type {ModuleId} */ + (chunkGraph.getModuleId(/** @type {Module} */ (importedModule))) + ); + properties.push({ + module, + name, + value: `${instanceVar}[${JSON.stringify(usedName)}]` + }); + } else { + const params = + /** @type {Signature} */ + (description.signature).params.map( + (param, k) => `p${k}${param.valtype}` + ); + + const mod = `${RuntimeGlobals.moduleCache}[${JSON.stringify( + chunkGraph.getModuleId(/** @type {Module} */ (importedModule)) + )}]`; + const modExports = `${mod}.exports`; + + const cache = `wasmImportedFuncCache${declarations.length}`; + declarations.push(`var ${cache};`); + + const modCode = + /** @type {Module} */ + (importedModule).type.startsWith("webassembly") + ? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : ` + : ""; + + properties.push({ + module, + name, + value: Template.asString([ + `${modCode}function(${params}) {`, + Template.indent([ + `if(${cache} === undefined) ${cache} = ${modExports};`, + `return ${cache}[${JSON.stringify(usedName)}](${params});` + ]), + "}" + ]) + }); + } + } + + let importObject; + if (mangle) { + importObject = [ + "return {", + Template.indent([ + properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") + ]), + "};" + ]; + } else { + /** @type {Map>} */ + const propertiesByModule = new Map(); + for (const p of properties) { + let list = propertiesByModule.get(p.module); + if (list === undefined) { + propertiesByModule.set(p.module, (list = [])); + } + list.push(p); + } + importObject = [ + "return {", + Template.indent([ + Array.from(propertiesByModule, ([module, list]) => + Template.asString([ + `${JSON.stringify(module)}: {`, + Template.indent([ + list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") + ]), + "}" + ]) + ).join(",\n") + ]), + "};" + ]; + } + + const moduleIdStringified = JSON.stringify(chunkGraph.getModuleId(module)); + if (waitForInstances.size === 1) { + const moduleId = Array.from(waitForInstances.values())[0]; + const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`; + const variable = Array.from(waitForInstances.keys())[0]; + return Template.asString([ + `${moduleIdStringified}: function() {`, + Template.indent([ + `return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`, + Template.indent(importObject), + "});" + ]), + "}," + ]); + } else if (waitForInstances.size > 0) { + const promises = Array.from( + waitForInstances.values(), + id => `installedWasmModules[${JSON.stringify(id)}]` + ).join(", "); + const variables = Array.from( + waitForInstances.keys(), + (name, i) => `${name} = array[${i}]` + ).join(", "); + return Template.asString([ + `${moduleIdStringified}: function() {`, + Template.indent([ + `return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`, + Template.indent([`var ${variables};`, ...importObject]), + "});" + ]), + "}," + ]); + } + return Template.asString([ + `${moduleIdStringified}: function() {`, + Template.indent(importObject), + "}," + ]); +}; + +/** + * @typedef {object} WasmChunkLoadingRuntimeModuleOptions + * @property {(path: string) => string} generateLoadBinaryCode + * @property {boolean} [supportsStreaming] + * @property {boolean} [mangleImports] + * @property {ReadOnlyRuntimeRequirements} runtimeRequirements + */ + +class WasmChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {WasmChunkLoadingRuntimeModuleOptions} options options + */ + constructor({ + generateLoadBinaryCode, + supportsStreaming, + mangleImports, + runtimeRequirements + }) { + super("wasm chunk loading", RuntimeModule.STAGE_ATTACH); + this.generateLoadBinaryCode = generateLoadBinaryCode; + this.supportsStreaming = supportsStreaming; + this.mangleImports = mangleImports; + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const fn = RuntimeGlobals.ensureChunkHandlers; + const withHmr = this._runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const compilation = /** @type {Compilation} */ (this.compilation); + const { moduleGraph, outputOptions } = compilation; + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk); + const { mangleImports } = this; + /** @type {string[]} */ + const declarations = []; + const importObjects = wasmModules.map(module => + generateImportObject( + chunkGraph, + module, + mangleImports, + declarations, + chunk.runtime + ) + ); + const chunkModuleIdMap = chunkGraph.getChunkModuleIdMap(chunk, m => + m.type.startsWith("webassembly") + ); + /** + * @param {string} content content + * @returns {string} created import object + */ + const createImportObject = content => + mangleImports + ? `{ ${JSON.stringify(WebAssemblyUtils.MANGLED_MODULE)}: ${content} }` + : content; + const wasmModuleSrcPath = compilation.getPath( + JSON.stringify(outputOptions.webassemblyModuleFilename), + { + hash: `" + ${RuntimeGlobals.getFullHash}() + "`, + hashWithLength: length => + `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`, + module: { + id: '" + wasmModuleId + "', + hash: `" + ${JSON.stringify( + chunkGraph.getChunkModuleRenderedHashMap(chunk, m => + m.type.startsWith("webassembly") + ) + )}[chunkId][wasmModuleId] + "`, + hashWithLength(length) { + return `" + ${JSON.stringify( + chunkGraph.getChunkModuleRenderedHashMap( + chunk, + m => m.type.startsWith("webassembly"), + length + ) + )}[chunkId][wasmModuleId] + "`; + } + }, + runtime: chunk.runtime + } + ); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_wasm` + : undefined; + + return Template.asString([ + "// object to store loaded and loading wasm modules", + `var installedWasmModules = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{};`, + "", + // This function is used to delay reading the installed wasm module promises + // by a microtask. Sorting them doesn't help because there are edge cases where + // sorting is not possible (modules splitted into different chunks). + // So we not even trying and solve this by a microtask delay. + "function promiseResolve() { return Promise.resolve(); }", + "", + Template.asString(declarations), + "var wasmImportObjects = {", + Template.indent(importObjects), + "};", + "", + `var wasmModuleMap = ${JSON.stringify( + chunkModuleIdMap, + undefined, + "\t" + )};`, + "", + "// object with all WebAssembly.instance exports", + `${RuntimeGlobals.wasmInstances} = {};`, + "", + "// Fetch + compile chunk loading for webassembly", + `${fn}.wasm = function(chunkId, promises) {`, + Template.indent([ + "", + "var wasmModules = wasmModuleMap[chunkId] || [];", + "", + "wasmModules.forEach(function(wasmModuleId, idx) {", + Template.indent([ + "var installedWasmModuleData = installedWasmModules[wasmModuleId];", + "", + '// a Promise means "currently loading" or "already loaded".', + "if(installedWasmModuleData)", + Template.indent(["promises.push(installedWasmModuleData);"]), + "else {", + Template.indent([ + "var importObject = wasmImportObjects[wasmModuleId]();", + `var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`, + "var promise;", + this.supportsStreaming + ? Template.asString([ + "if(importObject && typeof importObject.then === 'function' && typeof WebAssembly.compileStreaming === 'function') {", + Template.indent([ + "promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {", + Template.indent([ + `return WebAssembly.instantiate(items[0], ${createImportObject( + "items[1]" + )});` + ]), + "});" + ]), + "} else if(typeof WebAssembly.instantiateStreaming === 'function') {", + Template.indent([ + `promise = WebAssembly.instantiateStreaming(req, ${createImportObject( + "importObject" + )});` + ]) + ]) + : Template.asString([ + "if(importObject && typeof importObject.then === 'function') {", + Template.indent([ + "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", + "promise = Promise.all([", + Template.indent([ + "bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),", + "importObject" + ]), + "]).then(function(items) {", + Template.indent([ + `return WebAssembly.instantiate(items[0], ${createImportObject( + "items[1]" + )});` + ]), + "});" + ]) + ]), + "} else {", + Template.indent([ + "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", + "promise = bytesPromise.then(function(bytes) {", + Template.indent([ + `return WebAssembly.instantiate(bytes, ${createImportObject( + "importObject" + )});` + ]), + "});" + ]), + "}", + "promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {", + Template.indent([ + `return ${RuntimeGlobals.wasmInstances}[wasmModuleId] = (res.instance || res).exports;` + ]), + "}));" + ]), + "}" + ]), + "});" + ]), + "};" + ]); + } +} + +module.exports = WasmChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js b/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js new file mode 100644 index 00000000000..7e5668798be --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js @@ -0,0 +1,93 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const formatLocation = require("../formatLocation"); +const UnsupportedWebAssemblyFeatureError = require("./UnsupportedWebAssemblyFeatureError"); + +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ + +class WasmFinalizeExportsPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap("WasmFinalizeExportsPlugin", compilation => { + compilation.hooks.finishModules.tap( + "WasmFinalizeExportsPlugin", + modules => { + for (const module of modules) { + // 1. if a WebAssembly module + if (module.type.startsWith("webassembly") === true) { + const jsIncompatibleExports = + /** @type {BuildMeta} */ + (module.buildMeta).jsIncompatibleExports; + + if (jsIncompatibleExports === undefined) { + continue; + } + + for (const connection of compilation.moduleGraph.getIncomingConnections( + module + )) { + // 2. is active and referenced by a non-WebAssembly module + if ( + connection.isTargetActive(undefined) && + /** @type {Module} */ + (connection.originModule).type.startsWith("webassembly") === + false + ) { + const referencedExports = + compilation.getDependencyReferencedExports( + /** @type {Dependency} */ (connection.dependency), + undefined + ); + + for (const info of referencedExports) { + const names = Array.isArray(info) ? info : info.name; + if (names.length === 0) continue; + const name = names[0]; + if (typeof name === "object") continue; + // 3. and uses a func with an incompatible JS signature + if ( + Object.prototype.hasOwnProperty.call( + jsIncompatibleExports, + name + ) + ) { + // 4. error + const error = new UnsupportedWebAssemblyFeatureError( + `Export "${name}" with ${jsIncompatibleExports[name]} can only be used for direct wasm to wasm dependencies\n` + + `It's used from ${ + /** @type {Module} */ + (connection.originModule).readableIdentifier( + compilation.requestShortener + ) + } at ${formatLocation( + /** @type {Dependency} */ (connection.dependency) + .loc + )}.` + ); + error.module = module; + compilation.errors.push(error); + } + } + } + } + } + } + } + ); + }); + } +} + +module.exports = WasmFinalizeExportsPlugin; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js b/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js new file mode 100644 index 00000000000..d315539a755 --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js @@ -0,0 +1,520 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const t = require("@webassemblyjs/ast"); +const { moduleContextFromModuleAST } = require("@webassemblyjs/ast"); +const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit"); +const { decode } = require("@webassemblyjs/wasm-parser"); +const { RawSource } = require("webpack-sources"); +const Generator = require("../Generator"); +const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); +const WebAssemblyUtils = require("./WebAssemblyUtils"); + +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ +/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ +/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */ +/** @typedef {import("@webassemblyjs/ast").Instruction} Instruction */ +/** @typedef {import("@webassemblyjs/ast").ModuleImport} ModuleImport */ +/** @typedef {import("@webassemblyjs/ast").ModuleExport} ModuleExport */ +/** @typedef {import("@webassemblyjs/ast").Global} Global */ +/** + * @template T + * @typedef {import("@webassemblyjs/ast").NodePath} NodePath + */ + +/** + * @typedef {(buf: ArrayBuffer) => ArrayBuffer} ArrayBufferTransform + */ + +/** + * @template T + * @param {((prev: ArrayBuffer) => ArrayBuffer)[]} fns transforms + * @returns {Function} composed transform + */ +const compose = (...fns) => + fns.reduce( + (prevFn, nextFn) => value => nextFn(prevFn(value)), + value => value + ); + +/** + * Removes the start instruction + * @param {object} state state + * @param {object} state.ast Module's ast + * @returns {ArrayBufferTransform} transform + */ +const removeStartFunc = state => bin => + editWithAST(state.ast, bin, { + Start(path) { + path.remove(); + } + }); + +/** + * Get imported globals + * @param {object} ast Module's AST + * @returns {t.ModuleImport[]} - nodes + */ +const getImportedGlobals = ast => { + /** @type {t.ModuleImport[]} */ + const importedGlobals = []; + + t.traverse(ast, { + ModuleImport({ node }) { + if (t.isGlobalType(node.descr)) { + importedGlobals.push(node); + } + } + }); + + return importedGlobals; +}; + +/** + * Get the count for imported func + * @param {object} ast Module's AST + * @returns {number} - count + */ +const getCountImportedFunc = ast => { + let count = 0; + + t.traverse(ast, { + ModuleImport({ node }) { + if (t.isFuncImportDescr(node.descr)) { + count++; + } + } + }); + + return count; +}; + +/** + * Get next type index + * @param {object} ast Module's AST + * @returns {t.Index} - index + */ +const getNextTypeIndex = ast => { + const typeSectionMetadata = t.getSectionMetadata(ast, "type"); + + if (typeSectionMetadata === undefined) { + return t.indexLiteral(0); + } + + return t.indexLiteral(typeSectionMetadata.vectorOfSize.value); +}; + +/** + * Get next func index + * The Func section metadata provide information for implemented funcs + * in order to have the correct index we shift the index by number of external + * functions. + * @param {object} ast Module's AST + * @param {number} countImportedFunc number of imported funcs + * @returns {t.Index} - index + */ +const getNextFuncIndex = (ast, countImportedFunc) => { + const funcSectionMetadata = t.getSectionMetadata(ast, "func"); + + if (funcSectionMetadata === undefined) { + return t.indexLiteral(0 + countImportedFunc); + } + + const vectorOfSize = funcSectionMetadata.vectorOfSize.value; + + return t.indexLiteral(vectorOfSize + countImportedFunc); +}; + +/** + * Creates an init instruction for a global type + * @param {t.GlobalType} globalType the global type + * @returns {t.Instruction} init expression + */ +const createDefaultInitForGlobal = globalType => { + if (globalType.valtype[0] === "i") { + // create NumberLiteral global initializer + return t.objectInstruction("const", globalType.valtype, [ + t.numberLiteralFromRaw(66) + ]); + } else if (globalType.valtype[0] === "f") { + // create FloatLiteral global initializer + return t.objectInstruction("const", globalType.valtype, [ + t.floatLiteral(66, false, false, "66") + ]); + } + throw new Error(`unknown type: ${globalType.valtype}`); +}; + +/** + * Rewrite the import globals: + * - removes the ModuleImport instruction + * - injects at the same offset a mutable global of the same type + * + * Since the imported globals are before the other global declarations, our + * indices will be preserved. + * + * Note that globals will become mutable. + * @param {object} state transformation state + * @param {object} state.ast Module's ast + * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function + * @returns {ArrayBufferTransform} transform + */ +const rewriteImportedGlobals = state => bin => { + const additionalInitCode = state.additionalInitCode; + /** @type {Array} */ + const newGlobals = []; + + bin = editWithAST(state.ast, bin, { + ModuleImport(path) { + if (t.isGlobalType(path.node.descr)) { + const globalType = /** @type {TODO} */ (path.node.descr); + + globalType.mutability = "var"; + + const init = [ + createDefaultInitForGlobal(globalType), + t.instruction("end") + ]; + + newGlobals.push(t.global(globalType, init)); + + path.remove(); + } + }, + + // in order to preserve non-imported global's order we need to re-inject + // those as well + /** + * @param {NodePath} path path + */ + Global(path) { + const { node } = path; + const [init] = node.init; + + if (init.id === "get_global") { + node.globalType.mutability = "var"; + + const initialGlobalIdx = init.args[0]; + + node.init = [ + createDefaultInitForGlobal(node.globalType), + t.instruction("end") + ]; + + additionalInitCode.push( + /** + * get_global in global initializer only works for imported globals. + * They have the same indices as the init params, so use the + * same index. + */ + t.instruction("get_local", [initialGlobalIdx]), + t.instruction("set_global", [t.indexLiteral(newGlobals.length)]) + ); + } + + newGlobals.push(node); + + path.remove(); + } + }); + + // Add global declaration instructions + return addWithAST(state.ast, bin, newGlobals); +}; + +/** + * Rewrite the export names + * @param {object} state state + * @param {object} state.ast Module's ast + * @param {Module} state.module Module + * @param {ModuleGraph} state.moduleGraph module graph + * @param {Set} state.externalExports Module + * @param {RuntimeSpec} state.runtime runtime + * @returns {ArrayBufferTransform} transform + */ +const rewriteExportNames = + ({ ast, moduleGraph, module, externalExports, runtime }) => + bin => + editWithAST(ast, bin, { + /** + * @param {NodePath} path path + */ + ModuleExport(path) { + const isExternal = externalExports.has(path.node.name); + if (isExternal) { + path.remove(); + return; + } + const usedName = moduleGraph + .getExportsInfo(module) + .getUsedName(path.node.name, runtime); + if (!usedName) { + path.remove(); + return; + } + path.node.name = /** @type {string} */ (usedName); + } + }); + +/** + * Mangle import names and modules + * @param {object} state state + * @param {object} state.ast Module's ast + * @param {Map} state.usedDependencyMap mappings to mangle names + * @returns {ArrayBufferTransform} transform + */ +const rewriteImports = + ({ ast, usedDependencyMap }) => + bin => + editWithAST(ast, bin, { + /** + * @param {NodePath} path path + */ + ModuleImport(path) { + const result = usedDependencyMap.get( + `${path.node.module}:${path.node.name}` + ); + + if (result !== undefined) { + path.node.module = result.module; + path.node.name = result.name; + } + } + }); + +/** + * Add an init function. + * + * The init function fills the globals given input arguments. + * @param {object} state transformation state + * @param {object} state.ast Module's ast + * @param {t.Identifier} state.initFuncId identifier of the init function + * @param {t.Index} state.startAtFuncOffset index of the start function + * @param {t.ModuleImport[]} state.importedGlobals list of imported globals + * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function + * @param {t.Index} state.nextFuncIndex index of the next function + * @param {t.Index} state.nextTypeIndex index of the next type + * @returns {ArrayBufferTransform} transform + */ +const addInitFunction = + ({ + ast, + initFuncId, + startAtFuncOffset, + importedGlobals, + additionalInitCode, + nextFuncIndex, + nextTypeIndex + }) => + bin => { + const funcParams = importedGlobals.map(importedGlobal => { + // used for debugging + const id = t.identifier( + `${importedGlobal.module}.${importedGlobal.name}` + ); + + return t.funcParam( + /** @type {string} */ (importedGlobal.descr.valtype), + id + ); + }); + + /** @type {Instruction[]} */ + const funcBody = []; + for (const [index, _importedGlobal] of importedGlobals.entries()) { + const args = [t.indexLiteral(index)]; + const body = [ + t.instruction("get_local", args), + t.instruction("set_global", args) + ]; + + funcBody.push(...body); + } + + if (typeof startAtFuncOffset === "number") { + funcBody.push( + t.callInstruction(t.numberLiteralFromRaw(startAtFuncOffset)) + ); + } + + for (const instr of additionalInitCode) { + funcBody.push(instr); + } + + funcBody.push(t.instruction("end")); + + /** @type {string[]} */ + const funcResults = []; + + // Code section + const funcSignature = t.signature(funcParams, funcResults); + const func = t.func(initFuncId, funcSignature, funcBody); + + // Type section + const functype = t.typeInstruction(undefined, funcSignature); + + // Func section + const funcindex = t.indexInFuncSection(nextTypeIndex); + + // Export section + const moduleExport = t.moduleExport( + initFuncId.value, + t.moduleExportDescr("Func", nextFuncIndex) + ); + + return addWithAST(ast, bin, [func, moduleExport, funcindex, functype]); + }; + +/** + * Extract mangle mappings from module + * @param {ModuleGraph} moduleGraph module graph + * @param {Module} module current module + * @param {boolean | undefined} mangle mangle imports + * @returns {Map} mappings to mangled names + */ +const getUsedDependencyMap = (moduleGraph, module, mangle) => { + /** @type {Map} */ + const map = new Map(); + for (const usedDep of WebAssemblyUtils.getUsedDependencies( + moduleGraph, + module, + mangle + )) { + const dep = usedDep.dependency; + const request = dep.request; + const exportName = dep.name; + map.set(`${request}:${exportName}`, usedDep); + } + return map; +}; + +/** + * @typedef {object} WebAssemblyGeneratorOptions + * @property {boolean} [mangleImports] mangle imports + */ + +class WebAssemblyGenerator extends Generator { + /** + * @param {WebAssemblyGeneratorOptions} options options + */ + constructor(options) { + super(); + this.options = options; + } + + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return WEBASSEMBLY_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + const originalSource = module.originalSource(); + if (!originalSource) { + return 0; + } + return originalSource.size(); + } + + /** + * @param {NormalModule} module module for which the code should be generated + * @param {GenerateContext} generateContext context for generate + * @returns {Source | null} generated code + */ + generate(module, { moduleGraph, runtime }) { + const bin = /** @type {Source} */ (module.originalSource()).source(); + + const initFuncId = t.identifier(""); + + // parse it + const ast = decode(bin, { + ignoreDataSection: true, + ignoreCodeSection: true, + ignoreCustomNameSection: true + }); + + const moduleContext = moduleContextFromModuleAST(ast.body[0]); + + const importedGlobals = getImportedGlobals(ast); + const countImportedFunc = getCountImportedFunc(ast); + const startAtFuncOffset = moduleContext.getStart(); + const nextFuncIndex = getNextFuncIndex(ast, countImportedFunc); + const nextTypeIndex = getNextTypeIndex(ast); + + const usedDependencyMap = getUsedDependencyMap( + moduleGraph, + module, + this.options.mangleImports + ); + const externalExports = new Set( + module.dependencies + .filter(d => d instanceof WebAssemblyExportImportedDependency) + .map(d => { + const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ ( + d + ); + return wasmDep.exportName; + }) + ); + + /** @type {t.Instruction[]} */ + const additionalInitCode = []; + + const transform = compose( + rewriteExportNames({ + ast, + moduleGraph, + module, + externalExports, + runtime + }), + + removeStartFunc({ ast }), + + rewriteImportedGlobals({ ast, additionalInitCode }), + + rewriteImports({ + ast, + usedDependencyMap + }), + + addInitFunction({ + ast, + initFuncId, + importedGlobals, + additionalInitCode, + startAtFuncOffset, + nextFuncIndex, + nextTypeIndex + }) + ); + + const newBin = transform(bin); + + const newBuf = Buffer.from(newBin); + + return new RawSource(newBuf); + } +} + +module.exports = WebAssemblyGenerator; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js b/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js new file mode 100644 index 00000000000..9d78ed205f4 --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js @@ -0,0 +1,106 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const WebpackError = require("../WebpackError"); + +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ +/** @typedef {import("../RequestShortener")} RequestShortener */ + +/** + * @param {Module} module module to get chains from + * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {RequestShortener} requestShortener to make readable identifiers + * @returns {string[]} all chains to the module + */ +const getInitialModuleChains = ( + module, + moduleGraph, + chunkGraph, + requestShortener +) => { + const queue = [ + { head: module, message: module.readableIdentifier(requestShortener) } + ]; + /** @type {Set} */ + const results = new Set(); + /** @type {Set} */ + const incompleteResults = new Set(); + /** @type {Set} */ + const visitedModules = new Set(); + + for (const chain of queue) { + const { head, message } = chain; + let final = true; + /** @type {Set} */ + const alreadyReferencedModules = new Set(); + for (const connection of moduleGraph.getIncomingConnections(head)) { + const newHead = connection.originModule; + if (newHead) { + if (!chunkGraph.getModuleChunks(newHead).some(c => c.canBeInitial())) + continue; + final = false; + if (alreadyReferencedModules.has(newHead)) continue; + alreadyReferencedModules.add(newHead); + const moduleName = newHead.readableIdentifier(requestShortener); + const detail = connection.explanation + ? ` (${connection.explanation})` + : ""; + const newMessage = `${moduleName}${detail} --> ${message}`; + if (visitedModules.has(newHead)) { + incompleteResults.add(`... --> ${newMessage}`); + continue; + } + visitedModules.add(newHead); + queue.push({ + head: newHead, + message: newMessage + }); + } else { + final = false; + const newMessage = connection.explanation + ? `(${connection.explanation}) --> ${message}` + : message; + results.add(newMessage); + } + } + if (final) { + results.add(message); + } + } + for (const result of incompleteResults) { + results.add(result); + } + return Array.from(results); +}; + +module.exports = class WebAssemblyInInitialChunkError extends WebpackError { + /** + * @param {Module} module WASM module + * @param {ModuleGraph} moduleGraph the module graph + * @param {ChunkGraph} chunkGraph the chunk graph + * @param {RequestShortener} requestShortener request shortener + */ + constructor(module, moduleGraph, chunkGraph, requestShortener) { + const moduleChains = getInitialModuleChains( + module, + moduleGraph, + chunkGraph, + requestShortener + ); + const message = `WebAssembly module is included in initial chunk. +This is not allowed, because WebAssembly download and compilation must happen asynchronous. +Add an async split point (i. e. import()) somewhere between your entrypoint and the WebAssembly module: +${moduleChains.map(s => `* ${s}`).join("\n")}`; + + super(message); + this.name = "WebAssemblyInInitialChunkError"; + this.hideStack = true; + this.module = module; + } +}; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js b/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js new file mode 100644 index 00000000000..7b4353a08c6 --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js @@ -0,0 +1,217 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { RawSource } = require("webpack-sources"); +const { UsageState } = require("../ExportsInfo"); +const Generator = require("../Generator"); +const InitFragment = require("../InitFragment"); +const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const Template = require("../Template"); +const ModuleDependency = require("../dependencies/ModuleDependency"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ +/** @typedef {import("../Generator").GenerateContext} GenerateContext */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").SourceTypes} SourceTypes */ +/** @typedef {import("../NormalModule")} NormalModule */ +/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ + +class WebAssemblyJavascriptGenerator extends Generator { + /** + * @param {NormalModule} module fresh module + * @returns {SourceTypes} available types (do not mutate) + */ + getTypes(module) { + return WEBASSEMBLY_TYPES; + } + + /** + * @param {NormalModule} module the module + * @param {string=} type source type + * @returns {number} estimate size of the module + */ + getSize(module, type) { + return 95 + module.dependencies.length * 5; + } + + /** + * @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 { + runtimeTemplate, + moduleGraph, + chunkGraph, + runtimeRequirements, + runtime + } = generateContext; + /** @type {InitFragment>[]} */ + const initFragments = []; + + const exportsInfo = moduleGraph.getExportsInfo(module); + + let needExportsCopy = false; + const importedModules = new Map(); + const initParams = []; + let index = 0; + for (const dep of module.dependencies) { + const moduleDep = + dep && dep instanceof ModuleDependency ? dep : undefined; + if (moduleGraph.getModule(dep)) { + let importData = importedModules.get(moduleGraph.getModule(dep)); + if (importData === undefined) { + importedModules.set( + moduleGraph.getModule(dep), + (importData = { + importVar: `m${index}`, + index, + request: (moduleDep && moduleDep.userRequest) || undefined, + names: new Set(), + reexports: [] + }) + ); + index++; + } + if (dep instanceof WebAssemblyImportDependency) { + importData.names.add(dep.name); + if (dep.description.type === "GlobalType") { + const exportName = dep.name; + const importedModule = moduleGraph.getModule(dep); + + if (importedModule) { + const usedName = moduleGraph + .getExportsInfo(importedModule) + .getUsedName(exportName, runtime); + if (usedName) { + initParams.push( + runtimeTemplate.exportFromImport({ + moduleGraph, + module: importedModule, + request: dep.request, + importVar: importData.importVar, + originModule: module, + exportName: dep.name, + asiSafe: true, + isCall: false, + callContext: null, + defaultInterop: true, + initFragments, + runtime, + runtimeRequirements + }) + ); + } + } + } + } + if (dep instanceof WebAssemblyExportImportedDependency) { + importData.names.add(dep.name); + const usedName = moduleGraph + .getExportsInfo(module) + .getUsedName(dep.exportName, runtime); + if (usedName) { + runtimeRequirements.add(RuntimeGlobals.exports); + const exportProp = `${module.exportsArgument}[${JSON.stringify( + usedName + )}]`; + const defineStatement = Template.asString([ + `${exportProp} = ${runtimeTemplate.exportFromImport({ + moduleGraph, + module: /** @type {Module} */ (moduleGraph.getModule(dep)), + request: dep.request, + importVar: importData.importVar, + originModule: module, + exportName: dep.name, + asiSafe: true, + isCall: false, + callContext: null, + defaultInterop: true, + initFragments, + runtime, + runtimeRequirements + })};`, + `if(WebAssembly.Global) ${exportProp} = ` + + `new WebAssembly.Global({ value: ${JSON.stringify( + dep.valueType + )} }, ${exportProp});` + ]); + importData.reexports.push(defineStatement); + needExportsCopy = true; + } + } + } + } + const importsCode = Template.asString( + Array.from( + importedModules, + ([module, { importVar, request, reexports }]) => { + const importStatement = runtimeTemplate.importStatement({ + module, + chunkGraph, + request, + importVar, + originModule: module, + runtimeRequirements + }); + return importStatement[0] + importStatement[1] + reexports.join("\n"); + } + ) + ); + + const copyAllExports = + exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused && + !needExportsCopy; + + // need these globals + runtimeRequirements.add(RuntimeGlobals.module); + runtimeRequirements.add(RuntimeGlobals.moduleId); + runtimeRequirements.add(RuntimeGlobals.wasmInstances); + if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) { + runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); + runtimeRequirements.add(RuntimeGlobals.exports); + } + if (!copyAllExports) { + runtimeRequirements.add(RuntimeGlobals.exports); + } + + // create source + const source = new RawSource( + [ + '"use strict";', + "// Instantiate WebAssembly module", + `var wasmExports = ${RuntimeGlobals.wasmInstances}[${module.moduleArgument}.id];`, + + exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused + ? `${RuntimeGlobals.makeNamespaceObject}(${module.exportsArgument});` + : "", + + // this must be before import for circular dependencies + "// export exports from WebAssembly module", + copyAllExports + ? `${module.moduleArgument}.exports = wasmExports;` + : "for(var name in wasmExports) " + + "if(name) " + + `${module.exportsArgument}[name] = wasmExports[name];`, + "// exec imports from WebAssembly module (for esm order)", + importsCode, + "", + "// exec wasm module", + `wasmExports[""](${initParams.join(", ")})` + ].join("\n") + ); + return InitFragment.addToSource(source, initFragments, generateContext); + } +} + +module.exports = WebAssemblyJavascriptGenerator; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js b/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js new file mode 100644 index 00000000000..dc3ff32ef5f --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js @@ -0,0 +1,152 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Generator = require("../Generator"); +const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); +const { compareModulesByIdentifier } = require("../util/comparators"); +const memoize = require("../util/memoize"); +const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleTemplate")} ModuleTemplate */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ + +const getWebAssemblyGenerator = memoize(() => + require("./WebAssemblyGenerator") +); +const getWebAssemblyJavascriptGenerator = memoize(() => + require("./WebAssemblyJavascriptGenerator") +); +const getWebAssemblyParser = memoize(() => require("./WebAssemblyParser")); + +const PLUGIN_NAME = "WebAssemblyModulesPlugin"; + +/** + * @typedef {object} WebAssemblyModulesPluginOptions + * @property {boolean} [mangleImports] mangle imports + */ + +class WebAssemblyModulesPlugin { + /** + * @param {WebAssemblyModulesPluginOptions} options options + */ + constructor(options) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.compilation.tap( + PLUGIN_NAME, + (compilation, { normalModuleFactory }) => { + compilation.dependencyFactories.set( + WebAssemblyImportDependency, + normalModuleFactory + ); + + compilation.dependencyFactories.set( + WebAssemblyExportImportedDependency, + normalModuleFactory + ); + + normalModuleFactory.hooks.createParser + .for(WEBASSEMBLY_MODULE_TYPE_SYNC) + .tap(PLUGIN_NAME, () => { + const WebAssemblyParser = getWebAssemblyParser(); + + return new WebAssemblyParser(); + }); + + normalModuleFactory.hooks.createGenerator + .for(WEBASSEMBLY_MODULE_TYPE_SYNC) + .tap(PLUGIN_NAME, () => { + const WebAssemblyJavascriptGenerator = + getWebAssemblyJavascriptGenerator(); + const WebAssemblyGenerator = getWebAssemblyGenerator(); + + return Generator.byType({ + javascript: new WebAssemblyJavascriptGenerator(), + webassembly: new WebAssemblyGenerator(this.options) + }); + }); + + compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { + const { chunkGraph } = compilation; + const { chunk, outputOptions, codeGenerationResults } = options; + + for (const module of chunkGraph.getOrderedChunkModulesIterable( + chunk, + compareModulesByIdentifier + )) { + if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) { + const filenameTemplate = + /** @type {NonNullable} */ + (outputOptions.webassemblyModuleFilename); + + result.push({ + render: () => + codeGenerationResults.getSource( + module, + chunk.runtime, + "webassembly" + ), + filenameTemplate, + pathOptions: { + module, + runtime: chunk.runtime, + chunkGraph + }, + auxiliary: true, + identifier: `webassemblyModule${chunkGraph.getModuleId( + module + )}`, + hash: chunkGraph.getModuleHash(module, chunk.runtime) + }); + } + } + + return result; + }); + + compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => { + const chunkGraph = compilation.chunkGraph; + const initialWasmModules = new Set(); + for (const chunk of compilation.chunks) { + if (chunk.canBeInitial()) { + for (const module of chunkGraph.getChunkModulesIterable(chunk)) { + if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) { + initialWasmModules.add(module); + } + } + } + } + for (const module of initialWasmModules) { + compilation.errors.push( + new WebAssemblyInInitialChunkError( + module, + compilation.moduleGraph, + compilation.chunkGraph, + compilation.requestShortener + ) + ); + } + }); + } + ); + } +} + +module.exports = WebAssemblyModulesPlugin; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyParser.js b/webpack-lib/lib/wasm-sync/WebAssemblyParser.js new file mode 100644 index 00000000000..72210b88aba --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyParser.js @@ -0,0 +1,206 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const t = require("@webassemblyjs/ast"); +const { moduleContextFromModuleAST } = require("@webassemblyjs/ast"); +const { decode } = require("@webassemblyjs/wasm-parser"); +const Parser = require("../Parser"); +const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); +const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Module").BuildInfo} BuildInfo */ +/** @typedef {import("../Module").BuildMeta} BuildMeta */ +/** @typedef {import("../Parser").ParserState} ParserState */ +/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ + +const JS_COMPAT_TYPES = new Set(["i32", "i64", "f32", "f64", "externref"]); + +/** + * @param {t.Signature} signature the func signature + * @returns {null | string} the type incompatible with js types + */ +const getJsIncompatibleType = signature => { + for (const param of signature.params) { + if (!JS_COMPAT_TYPES.has(param.valtype)) { + return `${param.valtype} as parameter`; + } + } + for (const type of signature.results) { + if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; + } + return null; +}; + +/** + * TODO why are there two different Signature types? + * @param {t.FuncSignature} signature the func signature + * @returns {null | string} the type incompatible with js types + */ +const getJsIncompatibleTypeOfFuncSignature = signature => { + for (const param of signature.args) { + if (!JS_COMPAT_TYPES.has(param)) { + return `${param} as parameter`; + } + } + for (const type of signature.result) { + if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; + } + return null; +}; + +const decoderOpts = { + ignoreCodeSection: true, + ignoreDataSection: true, + + // this will avoid having to lookup with identifiers in the ModuleContext + ignoreCustomNameSection: true +}; + +class WebAssemblyParser extends Parser { + /** + * @param {{}=} options parser options + */ + constructor(options) { + super(); + this.hooks = Object.freeze({}); + this.options = options; + } + + /** + * @param {string | Buffer | PreparsedAst} source the source to parse + * @param {ParserState} state the parser state + * @returns {ParserState} the parser state + */ + parse(source, state) { + if (!Buffer.isBuffer(source)) { + throw new Error("WebAssemblyParser input must be a Buffer"); + } + + // flag it as ESM + /** @type {BuildInfo} */ + (state.module.buildInfo).strict = true; + /** @type {BuildMeta} */ + (state.module.buildMeta).exportsType = "namespace"; + + // parse it + const program = decode(source, decoderOpts); + const module = program.body[0]; + + const moduleContext = moduleContextFromModuleAST(module); + + // extract imports and exports + /** @type {string[]} */ + const exports = []; + const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); + /** @type {Record | undefined} */ + let jsIncompatibleExports = (buildMeta.jsIncompatibleExports = undefined); + + /** @type {TODO[]} */ + const importedGlobals = []; + t.traverse(module, { + ModuleExport({ node }) { + const descriptor = node.descr; + + if (descriptor.exportType === "Func") { + const funcIdx = descriptor.id.value; + + /** @type {t.FuncSignature} */ + const funcSignature = moduleContext.getFunction(funcIdx); + + const incompatibleType = + getJsIncompatibleTypeOfFuncSignature(funcSignature); + + if (incompatibleType) { + if (jsIncompatibleExports === undefined) { + jsIncompatibleExports = + /** @type {BuildMeta} */ + (state.module.buildMeta).jsIncompatibleExports = {}; + } + jsIncompatibleExports[node.name] = incompatibleType; + } + } + + exports.push(node.name); + + if (node.descr && node.descr.exportType === "Global") { + const refNode = + importedGlobals[/** @type {TODO} */ (node.descr.id.value)]; + if (refNode) { + const dep = new WebAssemblyExportImportedDependency( + node.name, + refNode.module, + refNode.name, + refNode.descr.valtype + ); + + state.module.addDependency(dep); + } + } + }, + + Global({ node }) { + const init = node.init[0]; + + let importNode = null; + + if (init.id === "get_global") { + const globalIdx = init.args[0].value; + + if (globalIdx < importedGlobals.length) { + importNode = importedGlobals[globalIdx]; + } + } + + importedGlobals.push(importNode); + }, + + ModuleImport({ node }) { + /** @type {false | string} */ + let onlyDirectImport = false; + + if (t.isMemory(node.descr) === true) { + onlyDirectImport = "Memory"; + } else if (t.isTable(node.descr) === true) { + onlyDirectImport = "Table"; + } else if (t.isFuncImportDescr(node.descr) === true) { + const incompatibleType = getJsIncompatibleType( + /** @type {t.Signature} */ (node.descr.signature) + ); + if (incompatibleType) { + onlyDirectImport = `Non-JS-compatible Func Signature (${incompatibleType})`; + } + } else if (t.isGlobalType(node.descr) === true) { + const type = /** @type {string} */ (node.descr.valtype); + if (!JS_COMPAT_TYPES.has(type)) { + onlyDirectImport = `Non-JS-compatible Global Type (${type})`; + } + } + + const dep = new WebAssemblyImportDependency( + node.module, + node.name, + node.descr, + onlyDirectImport + ); + + state.module.addDependency(dep); + + if (t.isGlobalType(node.descr)) { + importedGlobals.push(node); + } + } + }); + + state.module.addDependency(new StaticExportsDependency(exports, false)); + + return state; + } +} + +module.exports = WebAssemblyParser; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js b/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js new file mode 100644 index 00000000000..a67f3557268 --- /dev/null +++ b/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js @@ -0,0 +1,66 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const Template = require("../Template"); +const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); + +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../ModuleGraph")} ModuleGraph */ + +/** + * @typedef {object} UsedWasmDependency + * @property {WebAssemblyImportDependency} dependency the dependency + * @property {string} name the export name + * @property {string} module the module name + */ + +const MANGLED_MODULE = "a"; + +/** + * @param {ModuleGraph} moduleGraph the module graph + * @param {Module} module the module + * @param {boolean | undefined} mangle mangle module and export names + * @returns {UsedWasmDependency[]} used dependencies and (mangled) name + */ +const getUsedDependencies = (moduleGraph, module, mangle) => { + /** @type {UsedWasmDependency[]} */ + const array = []; + let importIndex = 0; + for (const dep of module.dependencies) { + if (dep instanceof WebAssemblyImportDependency) { + if ( + dep.description.type === "GlobalType" || + moduleGraph.getModule(dep) === null + ) { + continue; + } + + const exportName = dep.name; + // TODO add the following 3 lines when removing of ModuleExport is possible + // const importedModule = moduleGraph.getModule(dep); + // const usedName = importedModule && moduleGraph.getExportsInfo(importedModule).getUsedName(exportName, runtime); + // if (usedName !== false) { + if (mangle) { + array.push({ + dependency: dep, + name: Template.numberToIdentifier(importIndex++), + module: MANGLED_MODULE + }); + } else { + array.push({ + dependency: dep, + name: exportName, + module: dep.request + }); + } + } + } + return array; +}; + +module.exports.getUsedDependencies = getUsedDependencies; +module.exports.MANGLED_MODULE = MANGLED_MODULE; diff --git a/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js b/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js new file mode 100644 index 00000000000..250dd0a2d71 --- /dev/null +++ b/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js @@ -0,0 +1,134 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").WasmLoadingType} WasmLoadingType */ +/** @typedef {import("../Compiler")} Compiler */ + +/** @type {WeakMap>} */ +const enabledTypes = new WeakMap(); + +/** + * @param {Compiler} compiler compiler instance + * @returns {Set} enabled types + */ +const getEnabledTypes = compiler => { + let set = enabledTypes.get(compiler); + if (set === undefined) { + set = new Set(); + enabledTypes.set(compiler, set); + } + return set; +}; + +class EnableWasmLoadingPlugin { + /** + * @param {WasmLoadingType} type library type that should be available + */ + constructor(type) { + this.type = type; + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {WasmLoadingType} type type of library + * @returns {void} + */ + static setEnabled(compiler, type) { + getEnabledTypes(compiler).add(type); + } + + /** + * @param {Compiler} compiler the compiler instance + * @param {WasmLoadingType} type type of library + * @returns {void} + */ + static checkEnabled(compiler, type) { + if (!getEnabledTypes(compiler).has(type)) { + throw new Error( + `Library type "${type}" is not enabled. ` + + "EnableWasmLoadingPlugin need to be used to enable this type of wasm loading. " + + 'This usually happens through the "output.enabledWasmLoadingTypes" option. ' + + 'If you are using a function as entry which sets "wasmLoading", you need to add all potential library types to "output.enabledWasmLoadingTypes". ' + + `These types are enabled: ${Array.from( + getEnabledTypes(compiler) + ).join(", ")}` + ); + } + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + const { type } = this; + + // Only enable once + const enabled = getEnabledTypes(compiler); + if (enabled.has(type)) return; + enabled.add(type); + + if (typeof type === "string") { + switch (type) { + case "fetch": { + if (compiler.options.experiments.syncWebAssembly) { + // TODO webpack 6 remove FetchCompileWasmPlugin + const FetchCompileWasmPlugin = require("../web/FetchCompileWasmPlugin"); + new FetchCompileWasmPlugin({ + mangleImports: compiler.options.optimization.mangleWasmImports + }).apply(compiler); + } + + if (compiler.options.experiments.asyncWebAssembly) { + const FetchCompileAsyncWasmPlugin = require("../web/FetchCompileAsyncWasmPlugin"); + new FetchCompileAsyncWasmPlugin().apply(compiler); + } + + break; + } + case "async-node": { + if (compiler.options.experiments.syncWebAssembly) { + // TODO webpack 6 remove ReadFileCompileWasmPlugin + const ReadFileCompileWasmPlugin = require("../node/ReadFileCompileWasmPlugin"); + new ReadFileCompileWasmPlugin({ + mangleImports: compiler.options.optimization.mangleWasmImports, + import: + compiler.options.output.environment.module && + compiler.options.output.environment.dynamicImport + }).apply(compiler); + } + + if (compiler.options.experiments.asyncWebAssembly) { + const ReadFileCompileAsyncWasmPlugin = require("../node/ReadFileCompileAsyncWasmPlugin"); + new ReadFileCompileAsyncWasmPlugin({ + import: + compiler.options.output.environment.module && + compiler.options.output.environment.dynamicImport + }).apply(compiler); + } + + break; + } + case "universal": { + const UniversalCompileAsyncWasmPlugin = require("../wasm-async/UniversalCompileAsyncWasmPlugin"); + new UniversalCompileAsyncWasmPlugin().apply(compiler); + break; + } + default: + throw new Error(`Unsupported wasm loading type ${type}. +Plugins which provide custom wasm loading types must call EnableWasmLoadingPlugin.setEnabled(compiler, type) to disable this error.`); + } + } else { + // TODO support plugin instances here + // apply them to the compiler + } + } +} + +module.exports = EnableWasmLoadingPlugin; diff --git a/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js b/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js new file mode 100644 index 00000000000..dca39338c2b --- /dev/null +++ b/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js @@ -0,0 +1,70 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +const PLUGIN_NAME = "FetchCompileAsyncWasmPlugin"; + +class FetchCompileAsyncWasmPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const globalWasmLoading = compilation.outputOptions.wasmLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const wasmLoading = + options && options.wasmLoading !== undefined + ? options.wasmLoading + : globalWasmLoading; + return wasmLoading === "fetch"; + }; + /** + * @param {string} path path to the wasm file + * @returns {string} code to load the wasm file + */ + const generateLoadBinaryCode = path => + `fetch(${RuntimeGlobals.publicPath} + ${path})`; + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.instantiateWasm) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC + ) + ) { + return; + } + set.add(RuntimeGlobals.publicPath); + compilation.addRuntimeModule( + chunk, + new AsyncWasmLoadingRuntimeModule({ + generateLoadBinaryCode, + supportsStreaming: true + }) + ); + }); + }); + } +} + +module.exports = FetchCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/web/FetchCompileWasmPlugin.js b/webpack-lib/lib/web/FetchCompileWasmPlugin.js new file mode 100644 index 00000000000..a4b5dbcf79d --- /dev/null +++ b/webpack-lib/lib/web/FetchCompileWasmPlugin.js @@ -0,0 +1,87 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const WasmChunkLoadingRuntimeModule = require("../wasm-sync/WasmChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +/** + * @typedef {object} FetchCompileWasmPluginOptions + * @property {boolean} [mangleImports] mangle imports + */ + +// TODO webpack 6 remove + +const PLUGIN_NAME = "FetchCompileWasmPlugin"; + +class FetchCompileWasmPlugin { + /** + * @param {FetchCompileWasmPluginOptions} [options] options + */ + constructor(options = {}) { + this.options = options; + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { + const globalWasmLoading = compilation.outputOptions.wasmLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const wasmLoading = + options && options.wasmLoading !== undefined + ? options.wasmLoading + : globalWasmLoading; + return wasmLoading === "fetch"; + }; + /** + * @param {string} path path to the wasm file + * @returns {string} code to load the wasm file + */ + const generateLoadBinaryCode = path => + `fetch(${RuntimeGlobals.publicPath} + ${path})`; + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { + if (!isEnabledForChunk(chunk)) return; + if ( + !chunkGraph.hasModuleInGraph( + chunk, + m => m.type === WEBASSEMBLY_MODULE_TYPE_SYNC + ) + ) { + return; + } + set.add(RuntimeGlobals.moduleCache); + set.add(RuntimeGlobals.publicPath); + compilation.addRuntimeModule( + chunk, + new WasmChunkLoadingRuntimeModule({ + generateLoadBinaryCode, + supportsStreaming: true, + mangleImports: this.options.mangleImports, + runtimeRequirements: set + }) + ); + }); + }); + } +} + +module.exports = FetchCompileWasmPlugin; diff --git a/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js b/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js new file mode 100644 index 00000000000..57b75f81f40 --- /dev/null +++ b/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js @@ -0,0 +1,100 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const JsonpChunkLoadingRuntimeModule = require("./JsonpChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +class JsonpChunkLoadingPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "JsonpChunkLoadingPlugin", + compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === "jsonp"; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk + * @param {Set} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.hasOwnProperty); + compilation.addRuntimeModule( + chunk, + new JsonpChunkLoadingRuntimeModule(set) + ); + }; + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("JsonpChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("JsonpChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("JsonpChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.baseURI) + .tap("JsonpChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.onChunksLoaded) + .tap("JsonpChunkLoadingPlugin", handler); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("JsonpChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.loadScript); + set.add(RuntimeGlobals.getChunkScriptFilename); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("JsonpChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.loadScript); + set.add(RuntimeGlobals.getChunkUpdateScriptFilename); + set.add(RuntimeGlobals.moduleCache); + set.add(RuntimeGlobals.hmrModuleData); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("JsonpChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getUpdateManifestFilename); + }); + } + ); + } +} + +module.exports = JsonpChunkLoadingPlugin; diff --git a/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js b/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..efc4ac0e463 --- /dev/null +++ b/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js @@ -0,0 +1,470 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const { SyncWaterfallHook } = require("tapable"); +const Compilation = require("../Compilation"); +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const chunkHasJs = require("../javascript/JavascriptModulesPlugin").chunkHasJs; +const { getInitialChunkIds } = require("../javascript/StartupHelpers"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +/** + * @typedef {object} JsonpCompilationPluginHooks + * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload + * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +class JsonpChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {Compilation} compilation the compilation + * @returns {JsonpCompilationPluginHooks} 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 = { + linkPreload: new SyncWaterfallHook(["source", "chunk"]), + linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; + } + + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + */ + constructor(runtimeRequirements) { + super("jsonp chunk loading", RuntimeModule.STAGE_ATTACH); + this._runtimeRequirements = runtimeRequirements; + } + + /** + * @private + * @param {Chunk} chunk chunk + * @returns {string} generated code + */ + _generateBaseUri(chunk) { + const options = chunk.getEntryOptions(); + if (options && options.baseUri) { + return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; + } + return `${RuntimeGlobals.baseURI} = document.baseURI || self.location.href;`; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const { + runtimeTemplate, + outputOptions: { + chunkLoadingGlobal, + hotUpdateGlobal, + crossOriginLoading, + scriptType + } + } = compilation; + const globalObject = runtimeTemplate.globalObject; + const { linkPreload, linkPrefetch } = + JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation); + const fn = RuntimeGlobals.ensureChunkHandlers; + const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI); + const withLoading = this._runtimeRequirements.has( + RuntimeGlobals.ensureChunkHandlers + ); + const withCallback = this._runtimeRequirements.has( + RuntimeGlobals.chunkCallback + ); + const withOnChunkLoad = this._runtimeRequirements.has( + RuntimeGlobals.onChunksLoaded + ); + const withHmr = this._runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const withHmrManifest = this._runtimeRequirements.has( + RuntimeGlobals.hmrDownloadManifest + ); + const withFetchPriority = this._runtimeRequirements.has( + RuntimeGlobals.hasFetchPriority + ); + const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( + chunkLoadingGlobal + )}]`; + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const withPrefetch = + this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && + chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs); + const withPreload = + this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && + chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs); + const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); + const hasJsMatcher = compileBooleanMatcher(conditionMap); + const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_jsonp` + : undefined; + + return Template.asString([ + withBaseURI ? this._generateBaseUri(chunk) : "// 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", + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( + ",\n" + ) + ), + "};", + "", + withLoading + ? Template.asString([ + `${fn}.j = ${runtimeTemplate.basicFunction( + `chunkId, promises${withFetchPriority ? ", fetchPriority" : ""}`, + hasJsMatcher !== false + ? Template.indent([ + "// JSONP chunk loading for javascript", + `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, + 'if(installedChunkData !== 0) { // 0 means "already installed".', + Template.indent([ + "", + '// a Promise means "currently loading".', + "if(installedChunkData) {", + Template.indent([ + "promises.push(installedChunkData[2]);" + ]), + "} else {", + Template.indent([ + hasJsMatcher === true + ? "if(true) { // all chunks have JS" + : `if(${hasJsMatcher("chunkId")}) {`, + Template.indent([ + "// setup Promise in chunk cache", + `var promise = new Promise(${runtimeTemplate.expressionFunction( + "installedChunkData = installedChunks[chunkId] = [resolve, reject]", + "resolve, reject" + )});`, + "promises.push(installedChunkData[2] = promise);", + "", + "// start chunk loading", + `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, + "// create error before stack unwound to get useful stacktrace later", + "var error = new Error();", + `var loadingEnded = ${runtimeTemplate.basicFunction( + "event", + [ + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, + Template.indent([ + "installedChunkData = installedChunks[chunkId];", + "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", + "if(installedChunkData) {", + Template.indent([ + "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", + "var 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);" + ]), + "}" + ]), + "}" + ] + )};`, + `${ + RuntimeGlobals.loadScript + }(url, loadingEnded, "chunk-" + chunkId, chunkId${ + withFetchPriority ? ", fetchPriority" : "" + });` + ]), + hasJsMatcher === true + ? "}" + : "} else installedChunks[chunkId] = 0;" + ]), + "}" + ]), + "}" + ]) + : Template.indent(["installedChunks[chunkId] = 0;"]) + )};` + ]) + : "// no chunk on demand loading", + "", + withPrefetch && hasJsMatcher !== false + ? `${ + RuntimeGlobals.prefetchChunkHandlers + }.j = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + linkPrefetch.call( + Template.asString([ + "var link = document.createElement('link');", + crossOriginLoading + ? `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + : "", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + 'link.rel = "prefetch";', + 'link.as = "script";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);` + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no prefetching", + "", + withPreload && hasJsMatcher !== false + ? `${ + RuntimeGlobals.preloadChunkHandlers + }.j = ${runtimeTemplate.basicFunction("chunkId", [ + `if((!${ + RuntimeGlobals.hasOwnProperty + }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ + hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") + }) {`, + Template.indent([ + "installedChunks[chunkId] = null;", + linkPreload.call( + Template.asString([ + "var link = document.createElement('link');", + scriptType && scriptType !== "module" + ? `link.type = ${JSON.stringify(scriptType)};` + : "", + "link.charset = 'utf-8';", + `if (${RuntimeGlobals.scriptNonce}) {`, + Template.indent( + `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` + ), + "}", + scriptType === "module" + ? 'link.rel = "modulepreload";' + : 'link.rel = "preload";', + scriptType === "module" ? "" : 'link.as = "script";', + `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, + crossOriginLoading + ? crossOriginLoading === "use-credentials" + ? 'link.crossOrigin = "use-credentials";' + : Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify( + crossOriginLoading + )};` + ), + "}" + ]) + : "" + ]), + chunk + ), + "document.head.appendChild(link);" + ]), + "}" + ])};` + : "// no preloaded", + "", + withHmr + ? Template.asString([ + "var currentUpdatedModulesList;", + "var waitingUpdateResolves = {};", + "function loadUpdateChunk(chunkId, updatedModulesList) {", + Template.indent([ + "currentUpdatedModulesList = updatedModulesList;", + `return new Promise(${runtimeTemplate.basicFunction( + "resolve, reject", + [ + "waitingUpdateResolves[chunkId] = resolve;", + "// start update chunk loading", + `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`, + "// create error before stack unwound to get useful stacktrace later", + "var error = new Error();", + `var loadingEnded = ${runtimeTemplate.basicFunction("event", [ + "if(waitingUpdateResolves[chunkId]) {", + Template.indent([ + "waitingUpdateResolves[chunkId] = undefined", + "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", + "var realSrc = event && event.target && event.target.src;", + "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';", + "error.name = 'ChunkLoadError';", + "error.type = errorType;", + "error.request = realSrc;", + "reject(error);" + ]), + "}" + ])};`, + `${RuntimeGlobals.loadScript}(url, loadingEnded);` + ] + )});` + ]), + "}", + "", + `${globalObject}[${JSON.stringify( + hotUpdateGlobal + )}] = ${runtimeTemplate.basicFunction( + "chunkId, moreModules, runtime", + [ + "for(var moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent([ + "currentUpdate[moduleId] = moreModules[moduleId];", + "if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);" + ]), + "}" + ]), + "}", + "if(runtime) currentUpdateRuntime.push(runtime);", + "if(waitingUpdateResolves[chunkId]) {", + Template.indent([ + "waitingUpdateResolves[chunkId]();", + "waitingUpdateResolves[chunkId] = undefined;" + ]), + "}" + ] + )};`, + "", + Template.getFunctionContent( + require("../hmr/JavascriptHotModuleReplacement.runtime.js") + ) + .replace(/\$key\$/g, "jsonp") + .replace(/\$installedChunks\$/g, "installedChunks") + .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") + .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) + .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) + .replace( + /\$ensureChunkHandlers\$/g, + RuntimeGlobals.ensureChunkHandlers + ) + .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) + .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) + .replace( + /\$hmrDownloadUpdateHandlers\$/g, + RuntimeGlobals.hmrDownloadUpdateHandlers + ) + .replace( + /\$hmrInvalidateModuleHandlers\$/g, + RuntimeGlobals.hmrInvalidateModuleHandlers + ) + ]) + : "// no HMR", + "", + withHmrManifest + ? Template.asString([ + `${ + RuntimeGlobals.hmrDownloadManifest + } = ${runtimeTemplate.basicFunction("", [ + 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");', + `return fetch(${RuntimeGlobals.publicPath} + ${ + RuntimeGlobals.getUpdateManifestFilename + }()).then(${runtimeTemplate.basicFunction("response", [ + "if(response.status === 404) return; // no update available", + 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);', + "return response.json();" + ])});` + ])};` + ]) + : "// no HMR manifest", + "", + withOnChunkLoad + ? `${ + RuntimeGlobals.onChunksLoaded + }.j = ${runtimeTemplate.returningFunction( + "installedChunks[chunkId] === 0", + "chunkId" + )};` + : "// no on chunks loaded", + "", + withCallback || withLoading + ? Template.asString([ + "// install a JSONP callback for chunk loading", + `var webpackJsonpCallback = ${runtimeTemplate.basicFunction( + "parentChunkLoadingFunction, data", + [ + runtimeTemplate.destructureArray( + ["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(${runtimeTemplate.returningFunction( + "installedChunks[id] !== 0", + "id" + )})) {`, + Template.indent([ + "for(moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent( + `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` + ), + "}" + ]), + "}", + `if(runtime) var result = runtime(${RuntimeGlobals.require});` + ]), + "}", + "if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);", + "for(;i < chunkIds.length; i++) {", + Template.indent([ + "chunkId = chunkIds[i];", + `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`, + Template.indent("installedChunks[chunkId][0]();"), + "}", + "installedChunks[chunkId] = 0;" + ]), + "}", + withOnChunkLoad + ? `return ${RuntimeGlobals.onChunksLoaded}(result);` + : "" + ] + )}`, + "", + `var chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, + "chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));", + "chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));" + ]) + : "// no jsonp function" + ]); + } +} + +module.exports = JsonpChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/web/JsonpTemplatePlugin.js b/webpack-lib/lib/web/JsonpTemplatePlugin.js new file mode 100644 index 00000000000..eeed68a28ba --- /dev/null +++ b/webpack-lib/lib/web/JsonpTemplatePlugin.js @@ -0,0 +1,38 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ArrayPushCallbackChunkFormatPlugin = require("../javascript/ArrayPushCallbackChunkFormatPlugin"); +const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); +const JsonpChunkLoadingRuntimeModule = require("./JsonpChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Compiler")} Compiler */ + +class JsonpTemplatePlugin { + /** + * @deprecated use JsonpChunkLoadingRuntimeModule.getCompilationHooks instead + * @param {Compilation} compilation the compilation + * @returns {JsonpChunkLoadingRuntimeModule.JsonpCompilationPluginHooks} hooks + */ + static getCompilationHooks(compilation) { + return JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation); + } + + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.options.output.chunkLoading = "jsonp"; + new ArrayPushCallbackChunkFormatPlugin().apply(compiler); + new EnableChunkLoadingPlugin("jsonp").apply(compiler); + } +} + +module.exports = JsonpTemplatePlugin; diff --git a/webpack-lib/lib/webpack.js b/webpack-lib/lib/webpack.js new file mode 100644 index 00000000000..7396300a0b9 --- /dev/null +++ b/webpack-lib/lib/webpack.js @@ -0,0 +1,195 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const util = require("util"); +const webpackOptionsSchemaCheck = require("../schemas/WebpackOptions.check.js"); +const webpackOptionsSchema = require("../schemas/WebpackOptions.json"); +const Compiler = require("./Compiler"); +const MultiCompiler = require("./MultiCompiler"); +const WebpackOptionsApply = require("./WebpackOptionsApply"); +const { + applyWebpackOptionsDefaults, + applyWebpackOptionsBaseDefaults +} = require("./config/defaults"); +const { getNormalizedWebpackOptions } = require("./config/normalization"); +const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin"); +const memoize = require("./util/memoize"); + +/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ +/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ +/** @typedef {import("./Compiler").WatchOptions} WatchOptions */ +/** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ +/** @typedef {import("./MultiStats")} MultiStats */ +/** @typedef {import("./Stats")} Stats */ + +const getValidateSchema = memoize(() => require("./validateSchema")); + +/** + * @template T + * @callback Callback + * @param {Error | null} err + * @param {T=} stats + * @returns {void} + */ + +/** + * @param {ReadonlyArray} childOptions options array + * @param {MultiCompilerOptions} options options + * @returns {MultiCompiler} a multi-compiler + */ +const createMultiCompiler = (childOptions, options) => { + const compilers = childOptions.map((options, index) => + createCompiler(options, index) + ); + const compiler = new MultiCompiler(compilers, options); + for (const childCompiler of compilers) { + if (childCompiler.options.dependencies) { + compiler.setDependencies( + childCompiler, + childCompiler.options.dependencies + ); + } + } + return compiler; +}; + +/** + * @param {WebpackOptions} rawOptions options object + * @param {number} [compilerIndex] index of compiler + * @returns {Compiler} a compiler + */ +const createCompiler = (rawOptions, compilerIndex) => { + const options = getNormalizedWebpackOptions(rawOptions); + applyWebpackOptionsBaseDefaults(options); + const compiler = new Compiler( + /** @type {string} */ (options.context), + options + ); + new NodeEnvironmentPlugin({ + infrastructureLogging: options.infrastructureLogging + }).apply(compiler); + if (Array.isArray(options.plugins)) { + for (const plugin of options.plugins) { + if (typeof plugin === "function") { + /** @type {WebpackPluginFunction} */ + (plugin).call(compiler, compiler); + } else if (plugin) { + plugin.apply(compiler); + } + } + } + const resolvedDefaultOptions = applyWebpackOptionsDefaults( + options, + compilerIndex + ); + if (resolvedDefaultOptions.platform) { + compiler.platform = resolvedDefaultOptions.platform; + } + compiler.hooks.environment.call(); + compiler.hooks.afterEnvironment.call(); + new WebpackOptionsApply().process(options, compiler); + compiler.hooks.initialize.call(); + return compiler; +}; + +/** + * @callback WebpackFunctionSingle + * @param {WebpackOptions} options options object + * @param {Callback=} callback callback + * @returns {Compiler} the compiler object + */ + +/** + * @callback WebpackFunctionMulti + * @param {ReadonlyArray & MultiCompilerOptions} options options objects + * @param {Callback=} callback callback + * @returns {MultiCompiler} the multi compiler object + */ + +/** + * @template T + * @param {Array | T} options options + * @returns {Array} array of options + */ +const asArray = options => + Array.isArray(options) ? Array.from(options) : [options]; + +const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ ( + /** + * @param {WebpackOptions | (ReadonlyArray & MultiCompilerOptions)} options options + * @param {Callback & Callback=} callback callback + * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler + */ + (options, callback) => { + const create = () => { + if (!asArray(options).every(webpackOptionsSchemaCheck)) { + getValidateSchema()(webpackOptionsSchema, options); + util.deprecate( + () => {}, + "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.", + "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID" + )(); + } + /** @type {MultiCompiler|Compiler} */ + let compiler; + /** @type {boolean | undefined} */ + let watch = false; + /** @type {WatchOptions|WatchOptions[]} */ + let watchOptions; + if (Array.isArray(options)) { + /** @type {MultiCompiler} */ + compiler = createMultiCompiler( + options, + /** @type {MultiCompilerOptions} */ (options) + ); + watch = options.some(options => options.watch); + watchOptions = options.map(options => options.watchOptions || {}); + } else { + const webpackOptions = /** @type {WebpackOptions} */ (options); + /** @type {Compiler} */ + compiler = createCompiler(webpackOptions); + watch = webpackOptions.watch; + watchOptions = webpackOptions.watchOptions || {}; + } + return { compiler, watch, watchOptions }; + }; + if (callback) { + try { + const { compiler, watch, watchOptions } = create(); + if (watch) { + compiler.watch(watchOptions, callback); + } else { + compiler.run((err, stats) => { + compiler.close(err2 => { + callback( + err || err2, + /** @type {options extends WebpackOptions ? Stats : MultiStats} */ + (stats) + ); + }); + }); + } + return compiler; + } catch (err) { + process.nextTick(() => callback(/** @type {Error} */ (err))); + return null; + } + } else { + const { compiler, watch } = create(); + if (watch) { + util.deprecate( + () => {}, + "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.", + "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK" + )(); + } + return compiler; + } + } +); + +module.exports = webpack; diff --git a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js new file mode 100644 index 00000000000..ddb6cf51a7d --- /dev/null +++ b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js @@ -0,0 +1,105 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const StartupChunkDependenciesPlugin = require("../runtime/StartupChunkDependenciesPlugin"); +const ImportScriptsChunkLoadingRuntimeModule = require("./ImportScriptsChunkLoadingRuntimeModule"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compiler")} Compiler */ + +class ImportScriptsChunkLoadingPlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + new StartupChunkDependenciesPlugin({ + chunkLoading: "import-scripts", + asyncChunkLoading: true + }).apply(compiler); + compiler.hooks.thisCompilation.tap( + "ImportScriptsChunkLoadingPlugin", + compilation => { + const globalChunkLoading = compilation.outputOptions.chunkLoading; + /** + * @param {Chunk} chunk chunk + * @returns {boolean} true, if wasm loading is enabled for the chunk + */ + const isEnabledForChunk = chunk => { + const options = chunk.getEntryOptions(); + const chunkLoading = + options && options.chunkLoading !== undefined + ? options.chunkLoading + : globalChunkLoading; + return chunkLoading === "import-scripts"; + }; + const onceForChunkSet = new WeakSet(); + /** + * @param {Chunk} chunk chunk + * @param {Set} set runtime requirements + */ + const handler = (chunk, set) => { + if (onceForChunkSet.has(chunk)) return; + onceForChunkSet.add(chunk); + if (!isEnabledForChunk(chunk)) return; + const withCreateScriptUrl = Boolean( + compilation.outputOptions.trustedTypes + ); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + set.add(RuntimeGlobals.hasOwnProperty); + if (withCreateScriptUrl) { + set.add(RuntimeGlobals.createScriptUrl); + } + compilation.addRuntimeModule( + chunk, + new ImportScriptsChunkLoadingRuntimeModule(set, withCreateScriptUrl) + ); + }; + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("ImportScriptsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("ImportScriptsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("ImportScriptsChunkLoadingPlugin", handler); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.baseURI) + .tap("ImportScriptsChunkLoadingPlugin", handler); + + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.ensureChunkHandlers) + .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getChunkScriptFilename); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadUpdateHandlers) + .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getChunkUpdateScriptFilename); + set.add(RuntimeGlobals.moduleCache); + set.add(RuntimeGlobals.hmrModuleData); + set.add(RuntimeGlobals.moduleFactoriesAddOnly); + }); + compilation.hooks.runtimeRequirementInTree + .for(RuntimeGlobals.hmrDownloadManifest) + .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { + if (!isEnabledForChunk(chunk)) return; + set.add(RuntimeGlobals.publicPath); + set.add(RuntimeGlobals.getUpdateManifestFilename); + }); + } + ); + } +} +module.exports = ImportScriptsChunkLoadingPlugin; diff --git a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js new file mode 100644 index 00000000000..7d2ae3a3d61 --- /dev/null +++ b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js @@ -0,0 +1,242 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const RuntimeGlobals = require("../RuntimeGlobals"); +const RuntimeModule = require("../RuntimeModule"); +const Template = require("../Template"); +const { + getChunkFilenameTemplate, + chunkHasJs +} = require("../javascript/JavascriptModulesPlugin"); +const { getInitialChunkIds } = require("../javascript/StartupHelpers"); +const compileBooleanMatcher = require("../util/compileBooleanMatcher"); +const { getUndoPath } = require("../util/identifier"); + +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../ChunkGraph")} ChunkGraph */ +/** @typedef {import("../Compilation")} Compilation */ +/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ + +class ImportScriptsChunkLoadingRuntimeModule extends RuntimeModule { + /** + * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements + * @param {boolean} withCreateScriptUrl with createScriptUrl support + */ + constructor(runtimeRequirements, withCreateScriptUrl) { + super("importScripts chunk loading", RuntimeModule.STAGE_ATTACH); + this.runtimeRequirements = runtimeRequirements; + this._withCreateScriptUrl = withCreateScriptUrl; + } + + /** + * @private + * @param {Chunk} chunk chunk + * @returns {string} generated code + */ + _generateBaseUri(chunk) { + const options = chunk.getEntryOptions(); + if (options && options.baseUri) { + return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; + } + const compilation = /** @type {Compilation} */ (this.compilation); + const outputName = compilation.getPath( + getChunkFilenameTemplate(chunk, compilation.outputOptions), + { + chunk, + contentHashType: "javascript" + } + ); + const rootOutputDir = getUndoPath( + outputName, + /** @type {string} */ (compilation.outputOptions.path), + false + ); + return `${RuntimeGlobals.baseURI} = self.location + ${JSON.stringify( + rootOutputDir ? `/../${rootOutputDir}` : "" + )};`; + } + + /** + * @returns {string | null} runtime code + */ + generate() { + const compilation = /** @type {Compilation} */ (this.compilation); + const fn = RuntimeGlobals.ensureChunkHandlers; + const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); + const withLoading = this.runtimeRequirements.has( + RuntimeGlobals.ensureChunkHandlers + ); + const withHmr = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadUpdateHandlers + ); + const withHmrManifest = this.runtimeRequirements.has( + RuntimeGlobals.hmrDownloadManifest + ); + const globalObject = compilation.runtimeTemplate.globalObject; + const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( + compilation.outputOptions.chunkLoadingGlobal + )}]`; + const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); + const chunk = /** @type {Chunk} */ (this.chunk); + const hasJsMatcher = compileBooleanMatcher( + chunkGraph.getChunkConditionMap(chunk, chunkHasJs) + ); + const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); + + const stateExpression = withHmr + ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_importScripts` + : undefined; + const runtimeTemplate = compilation.runtimeTemplate; + const { _withCreateScriptUrl: withCreateScriptUrl } = this; + + return Template.asString([ + withBaseURI ? this._generateBaseUri(chunk) : "// no baseURI", + "", + "// object to store loaded chunks", + '// "1" means "already loaded"', + `var installedChunks = ${ + stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" + }{`, + Template.indent( + Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join( + ",\n" + ) + ), + "};", + "", + withLoading + ? Template.asString([ + "// importScripts chunk loading", + `var installChunk = ${runtimeTemplate.basicFunction("data", [ + runtimeTemplate.destructureArray( + ["chunkIds", "moreModules", "runtime"], + "data" + ), + "for(var moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent( + `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` + ), + "}" + ]), + "}", + `if(runtime) runtime(${RuntimeGlobals.require});`, + "while(chunkIds.length)", + Template.indent("installedChunks[chunkIds.pop()] = 1;"), + "parentChunkLoadingFunction(data);" + ])};` + ]) + : "// no chunk install function needed", + withLoading + ? Template.asString([ + `${fn}.i = ${runtimeTemplate.basicFunction( + "chunkId, promises", + hasJsMatcher !== false + ? [ + '// "1" is the signal for "already loaded"', + "if(!installedChunks[chunkId]) {", + Template.indent([ + hasJsMatcher === true + ? "if(true) { // all chunks have JS" + : `if(${hasJsMatcher("chunkId")}) {`, + Template.indent( + `importScripts(${ + withCreateScriptUrl + ? `${RuntimeGlobals.createScriptUrl}(${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId))` + : `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId)` + });` + ), + "}" + ]), + "}" + ] + : "installedChunks[chunkId] = 1;" + )};`, + "", + `var chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, + "var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);", + "chunkLoadingGlobal.push = installChunk;" + ]) + : "// no chunk loading", + "", + withHmr + ? Template.asString([ + "function loadUpdateChunk(chunkId, updatedModulesList) {", + Template.indent([ + "var success = false;", + `${globalObject}[${JSON.stringify( + compilation.outputOptions.hotUpdateGlobal + )}] = ${runtimeTemplate.basicFunction("_, moreModules, runtime", [ + "for(var moduleId in moreModules) {", + Template.indent([ + `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, + Template.indent([ + "currentUpdate[moduleId] = moreModules[moduleId];", + "if(updatedModulesList) updatedModulesList.push(moduleId);" + ]), + "}" + ]), + "}", + "if(runtime) currentUpdateRuntime.push(runtime);", + "success = true;" + ])};`, + "// start update chunk loading", + `importScripts(${ + withCreateScriptUrl + ? `${RuntimeGlobals.createScriptUrl}(${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId))` + : `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId)` + });`, + 'if(!success) throw new Error("Loading update chunk failed for unknown reason");' + ]), + "}", + "", + Template.getFunctionContent( + require("../hmr/JavascriptHotModuleReplacement.runtime.js") + ) + .replace(/\$key\$/g, "importScripts") + .replace(/\$installedChunks\$/g, "installedChunks") + .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") + .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) + .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) + .replace( + /\$ensureChunkHandlers\$/g, + RuntimeGlobals.ensureChunkHandlers + ) + .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) + .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) + .replace( + /\$hmrDownloadUpdateHandlers\$/g, + RuntimeGlobals.hmrDownloadUpdateHandlers + ) + .replace( + /\$hmrInvalidateModuleHandlers\$/g, + RuntimeGlobals.hmrInvalidateModuleHandlers + ) + ]) + : "// no HMR", + "", + withHmrManifest + ? Template.asString([ + `${ + RuntimeGlobals.hmrDownloadManifest + } = ${runtimeTemplate.basicFunction("", [ + 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");', + `return fetch(${RuntimeGlobals.publicPath} + ${ + RuntimeGlobals.getUpdateManifestFilename + }()).then(${runtimeTemplate.basicFunction("response", [ + "if(response.status === 404) return; // no update available", + 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);', + "return response.json();" + ])});` + ])};` + ]) + : "// no HMR manifest" + ]); + } +} + +module.exports = ImportScriptsChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js b/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js new file mode 100644 index 00000000000..382c81243e8 --- /dev/null +++ b/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js @@ -0,0 +1,25 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const ArrayPushCallbackChunkFormatPlugin = require("../javascript/ArrayPushCallbackChunkFormatPlugin"); +const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); + +/** @typedef {import("../Compiler")} Compiler */ + +class WebWorkerTemplatePlugin { + /** + * Apply the plugin + * @param {Compiler} compiler the compiler instance + * @returns {void} + */ + apply(compiler) { + compiler.options.output.chunkLoading = "import-scripts"; + new ArrayPushCallbackChunkFormatPlugin().apply(compiler); + new EnableChunkLoadingPlugin("import-scripts").apply(compiler); + } +} +module.exports = WebWorkerTemplatePlugin; From cd9c448473ae8996a07299c914eeffb7f63abcff Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 23 Jan 2025 21:23:25 -0800 Subject: [PATCH 07/21] fix(enhanced): use startup to apply install --- .gitignore | 1 + .../runtime/EmbedFederationRuntimePlugin.ts | 121 +- webpack-lib/lib/APIPlugin.js | 324 - webpack-lib/lib/AbstractMethodError.js | 54 - webpack-lib/lib/AsyncDependenciesBlock.js | 114 - .../lib/AsyncDependencyToInitialChunkError.js | 31 - webpack-lib/lib/AutomaticPrefetchPlugin.js | 66 - webpack-lib/lib/BannerPlugin.js | 139 - webpack-lib/lib/Cache.js | 165 - webpack-lib/lib/CacheFacade.js | 349 -- .../lib/CaseSensitiveModulesWarning.js | 71 - webpack-lib/lib/Chunk.js | 874 --- webpack-lib/lib/ChunkGraph.js | 1868 ------ webpack-lib/lib/ChunkGroup.js | 604 -- webpack-lib/lib/ChunkRenderError.js | 31 - webpack-lib/lib/ChunkTemplate.js | 181 - webpack-lib/lib/CleanPlugin.js | 443 -- webpack-lib/lib/CodeGenerationError.js | 29 - webpack-lib/lib/CodeGenerationResults.js | 157 - webpack-lib/lib/CommentCompilationWarning.js | 32 - webpack-lib/lib/CompatibilityPlugin.js | 191 - webpack-lib/lib/Compilation.js | 5549 ----------------- webpack-lib/lib/Compiler.js | 1384 ---- webpack-lib/lib/ConcatenationScope.js | 143 - webpack-lib/lib/ConcurrentCompilationError.js | 18 - webpack-lib/lib/ConditionalInitFragment.js | 120 - webpack-lib/lib/ConstPlugin.js | 537 -- webpack-lib/lib/ContextExclusionPlugin.js | 32 - webpack-lib/lib/ContextModule.js | 1253 ---- webpack-lib/lib/ContextModuleFactory.js | 481 -- webpack-lib/lib/ContextReplacementPlugin.js | 172 - webpack-lib/lib/CssModule.js | 175 - webpack-lib/lib/DefinePlugin.js | 719 --- webpack-lib/lib/DelegatedModule.js | 262 - .../lib/DelegatedModuleFactoryPlugin.js | 111 - webpack-lib/lib/DelegatedPlugin.js | 47 - webpack-lib/lib/DependenciesBlock.js | 122 - webpack-lib/lib/Dependency.js | 367 -- webpack-lib/lib/DependencyTemplate.js | 69 - webpack-lib/lib/DependencyTemplates.js | 67 - webpack-lib/lib/DllEntryPlugin.js | 72 - webpack-lib/lib/DllModule.js | 174 - webpack-lib/lib/DllModuleFactory.js | 38 - webpack-lib/lib/DllPlugin.js | 70 - webpack-lib/lib/DllReferencePlugin.js | 193 - webpack-lib/lib/DynamicEntryPlugin.js | 86 - webpack-lib/lib/EntryOptionPlugin.js | 96 - webpack-lib/lib/EntryPlugin.js | 70 - webpack-lib/lib/Entrypoint.js | 101 - .../lib/EnvironmentNotSupportAsyncWarning.js | 52 - webpack-lib/lib/EnvironmentPlugin.js | 67 - webpack-lib/lib/ErrorHelpers.js | 100 - webpack-lib/lib/EvalDevToolModulePlugin.js | 129 - webpack-lib/lib/EvalSourceMapDevToolPlugin.js | 227 - webpack-lib/lib/ExportsInfo.js | 1624 ----- webpack-lib/lib/ExportsInfoApiPlugin.js | 87 - webpack-lib/lib/ExternalModule.js | 999 --- .../lib/ExternalModuleFactoryPlugin.js | 326 - webpack-lib/lib/ExternalsPlugin.js | 37 - webpack-lib/lib/FalseIIFEUmdWarning.js | 19 - webpack-lib/lib/FileSystemInfo.js | 4041 ------------ webpack-lib/lib/FlagAllModulesAsUsedPlugin.js | 55 - .../lib/FlagDependencyExportsPlugin.js | 420 -- webpack-lib/lib/FlagDependencyUsagePlugin.js | 347 -- .../lib/FlagEntryExportAsUsedPlugin.js | 56 - webpack-lib/lib/Generator.js | 155 - webpack-lib/lib/GraphHelpers.js | 38 - webpack-lib/lib/HarmonyLinkingError.js | 16 - webpack-lib/lib/HookWebpackError.js | 91 - webpack-lib/lib/HotModuleReplacementPlugin.js | 875 --- webpack-lib/lib/HotUpdateChunk.js | 19 - webpack-lib/lib/IgnoreErrorModuleFactory.js | 39 - webpack-lib/lib/IgnorePlugin.js | 102 - webpack-lib/lib/IgnoreWarningsPlugin.js | 36 - webpack-lib/lib/InitFragment.js | 185 - .../lib/InvalidDependenciesModuleWarning.js | 44 - webpack-lib/lib/JavascriptMetaInfoPlugin.js | 80 - webpack-lib/lib/LibManifestPlugin.js | 144 - webpack-lib/lib/LibraryTemplatePlugin.js | 48 - webpack-lib/lib/LoaderOptionsPlugin.js | 83 - webpack-lib/lib/LoaderTargetPlugin.js | 37 - webpack-lib/lib/MainTemplate.js | 382 -- webpack-lib/lib/Module.js | 1198 ---- webpack-lib/lib/ModuleBuildError.js | 79 - webpack-lib/lib/ModuleDependencyError.js | 42 - webpack-lib/lib/ModuleDependencyWarning.js | 47 - webpack-lib/lib/ModuleError.js | 66 - webpack-lib/lib/ModuleFactory.js | 50 - webpack-lib/lib/ModuleFilenameHelpers.js | 383 -- webpack-lib/lib/ModuleGraph.js | 896 --- webpack-lib/lib/ModuleGraphConnection.js | 205 - webpack-lib/lib/ModuleHashingError.js | 29 - webpack-lib/lib/ModuleInfoHeaderPlugin.js | 314 - webpack-lib/lib/ModuleNotFoundError.js | 89 - webpack-lib/lib/ModuleParseError.js | 117 - webpack-lib/lib/ModuleProfile.js | 108 - webpack-lib/lib/ModuleRestoreError.js | 44 - webpack-lib/lib/ModuleSourceTypesConstants.js | 112 - webpack-lib/lib/ModuleStoreError.js | 43 - webpack-lib/lib/ModuleTemplate.js | 174 - webpack-lib/lib/ModuleTypeConstants.js | 168 - webpack-lib/lib/ModuleWarning.js | 66 - webpack-lib/lib/MultiCompiler.js | 659 -- webpack-lib/lib/MultiStats.js | 208 - webpack-lib/lib/MultiWatching.js | 81 - webpack-lib/lib/NoEmitOnErrorsPlugin.js | 28 - webpack-lib/lib/NoModeWarning.js | 22 - webpack-lib/lib/NodeStuffInWebError.js | 34 - webpack-lib/lib/NodeStuffPlugin.js | 275 - webpack-lib/lib/NormalModule.js | 1654 ----- webpack-lib/lib/NormalModuleFactory.js | 1326 ---- .../lib/NormalModuleReplacementPlugin.js | 77 - webpack-lib/lib/NullFactory.js | 23 - webpack-lib/lib/OptimizationStages.js | 10 - webpack-lib/lib/OptionsApply.js | 22 - webpack-lib/lib/Parser.js | 38 - webpack-lib/lib/PlatformPlugin.js | 39 - webpack-lib/lib/PrefetchPlugin.js | 54 - webpack-lib/lib/ProgressPlugin.js | 709 --- webpack-lib/lib/ProvidePlugin.js | 119 - webpack-lib/lib/RawModule.js | 164 - webpack-lib/lib/RecordIdsPlugin.js | 238 - webpack-lib/lib/RequestShortener.js | 34 - webpack-lib/lib/RequireJsStuffPlugin.js | 84 - webpack-lib/lib/ResolverFactory.js | 155 - webpack-lib/lib/RuntimeGlobals.js | 387 -- webpack-lib/lib/RuntimeModule.js | 214 - webpack-lib/lib/RuntimePlugin.js | 496 -- webpack-lib/lib/RuntimeTemplate.js | 1109 ---- webpack-lib/lib/SelfModuleFactory.js | 33 - webpack-lib/lib/SingleEntryPlugin.js | 8 - webpack-lib/lib/SizeFormatHelpers.js | 25 - .../SourceMapDevToolModuleOptionsPlugin.js | 61 - webpack-lib/lib/SourceMapDevToolPlugin.js | 618 -- webpack-lib/lib/Stats.js | 89 - webpack-lib/lib/Template.js | 415 -- webpack-lib/lib/TemplatedPathPlugin.js | 395 -- webpack-lib/lib/UnhandledSchemeError.js | 33 - webpack-lib/lib/UnsupportedFeatureWarning.js | 32 - webpack-lib/lib/UseStrictPlugin.js | 81 - .../lib/WarnCaseSensitiveModulesPlugin.js | 65 - webpack-lib/lib/WarnDeprecatedOptionPlugin.js | 60 - webpack-lib/lib/WarnNoModeSetPlugin.js | 25 - webpack-lib/lib/WatchIgnorePlugin.js | 153 - webpack-lib/lib/Watching.js | 528 -- webpack-lib/lib/WebpackError.js | 70 - webpack-lib/lib/WebpackIsIncludedPlugin.js | 94 - webpack-lib/lib/WebpackOptionsApply.js | 783 --- webpack-lib/lib/WebpackOptionsDefaulter.js | 26 - webpack-lib/lib/asset/AssetGenerator.js | 739 --- webpack-lib/lib/asset/AssetModulesPlugin.js | 250 - webpack-lib/lib/asset/AssetParser.js | 65 - webpack-lib/lib/asset/AssetSourceGenerator.js | 146 - webpack-lib/lib/asset/AssetSourceParser.js | 37 - webpack-lib/lib/asset/RawDataUrlModule.js | 163 - .../AwaitDependenciesInitFragment.js | 72 - .../async-modules/InferAsyncModulesPlugin.js | 55 - webpack-lib/lib/buildChunkGraph.js | 1344 ---- webpack-lib/lib/cli.js | 699 --- .../lib/config/browserslistTargetHandler.js | 363 -- webpack-lib/lib/config/defaults.js | 1673 ----- webpack-lib/lib/config/normalization.js | 558 -- webpack-lib/lib/config/target.js | 377 -- .../lib/container/ContainerEntryDependency.js | 48 - .../lib/container/ContainerEntryModule.js | 295 - .../container/ContainerEntryModuleFactory.js | 27 - .../container/ContainerExposedDependency.js | 61 - webpack-lib/lib/container/ContainerPlugin.js | 119 - .../lib/container/ContainerReferencePlugin.js | 142 - .../lib/container/FallbackDependency.js | 64 - .../lib/container/FallbackItemDependency.js | 33 - webpack-lib/lib/container/FallbackModule.js | 184 - .../lib/container/FallbackModuleFactory.js | 27 - .../HoistContainerReferencesPlugin.js | 250 - .../lib/container/ModuleFederationPlugin.js | 131 - webpack-lib/lib/container/RemoteModule.js | 184 - .../lib/container/RemoteRuntimeModule.js | 144 - .../container/RemoteToExternalDependency.js | 33 - webpack-lib/lib/container/options.js | 105 - webpack-lib/lib/css/CssGenerator.js | 261 - .../lib/css/CssLoadingRuntimeModule.js | 503 -- webpack-lib/lib/css/CssModulesPlugin.js | 887 --- webpack-lib/lib/css/CssParser.js | 1602 ----- webpack-lib/lib/css/walkCssTokens.js | 1627 ----- webpack-lib/lib/debug/ProfilingPlugin.js | 504 -- .../lib/dependencies/AMDDefineDependency.js | 265 - .../AMDDefineDependencyParserPlugin.js | 497 -- webpack-lib/lib/dependencies/AMDPlugin.js | 236 - .../dependencies/AMDRequireArrayDependency.js | 123 - .../AMDRequireContextDependency.js | 68 - .../AMDRequireDependenciesBlock.js | 28 - ...AMDRequireDependenciesBlockParserPlugin.js | 410 -- .../lib/dependencies/AMDRequireDependency.js | 189 - .../dependencies/AMDRequireItemDependency.js | 41 - .../lib/dependencies/AMDRuntimeModules.js | 48 - .../lib/dependencies/CachedConstDependency.js | 128 - .../dependencies/CommonJsDependencyHelpers.js | 63 - .../CommonJsExportRequireDependency.js | 404 -- .../dependencies/CommonJsExportsDependency.js | 183 - .../CommonJsExportsParserPlugin.js | 411 -- .../CommonJsFullRequireDependency.js | 166 - .../CommonJsImportsParserPlugin.js | 783 --- .../lib/dependencies/CommonJsPlugin.js | 305 - .../CommonJsRequireContextDependency.js | 72 - .../dependencies/CommonJsRequireDependency.js | 42 - .../CommonJsSelfReferenceDependency.js | 155 - .../lib/dependencies/ConstDependency.js | 117 - .../lib/dependencies/ContextDependency.js | 178 - .../dependencies/ContextDependencyHelpers.js | 262 - .../ContextDependencyTemplateAsId.js | 62 - .../ContextDependencyTemplateAsRequireCall.js | 59 - .../dependencies/ContextElementDependency.js | 135 - .../dependencies/CreateScriptUrlDependency.js | 75 - .../dependencies/CriticalDependencyWarning.js | 28 - .../dependencies/CssIcssExportDependency.js | 156 - .../dependencies/CssIcssImportDependency.js | 118 - .../dependencies/CssIcssSymbolDependency.js | 132 - .../lib/dependencies/CssImportDependency.js | 117 - .../CssLocalIdentifierDependency.js | 250 - .../CssSelfLocalIdentifierDependency.js | 111 - .../lib/dependencies/CssUrlDependency.js | 195 - .../dependencies/DelegatedSourceDependency.js | 33 - .../lib/dependencies/DllEntryDependency.js | 61 - .../lib/dependencies/DynamicExports.js | 73 - .../lib/dependencies/EntryDependency.js | 30 - .../lib/dependencies/ExportsInfoDependency.js | 158 - .../dependencies/ExternalModuleDependency.js | 109 - .../ExternalModuleInitFragment.js | 133 - .../dependencies/HarmonyAcceptDependency.js | 143 - .../HarmonyAcceptImportDependency.js | 40 - .../HarmonyCompatibilityDependency.js | 92 - .../HarmonyDetectionParserPlugin.js | 123 - ...rmonyEvaluatedImportSpecifierDependency.js | 152 - .../HarmonyExportDependencyParserPlugin.js | 221 - .../HarmonyExportExpressionDependency.js | 207 - .../HarmonyExportHeaderDependency.js | 78 - ...armonyExportImportedSpecifierDependency.js | 1396 ----- .../dependencies/HarmonyExportInitFragment.js | 177 - .../HarmonyExportSpecifierDependency.js | 123 - .../lib/dependencies/HarmonyExports.js | 46 - .../dependencies/HarmonyImportDependency.js | 382 -- .../HarmonyImportDependencyParserPlugin.js | 370 -- .../HarmonyImportSideEffectDependency.js | 86 - .../HarmonyImportSpecifierDependency.js | 468 -- .../lib/dependencies/HarmonyModulesPlugin.js | 149 - .../HarmonyTopLevelThisParserPlugin.js | 39 - .../dependencies/ImportContextDependency.js | 67 - .../lib/dependencies/ImportDependency.js | 139 - .../lib/dependencies/ImportEagerDependency.js | 74 - .../ImportMetaContextDependency.js | 42 - ...ImportMetaContextDependencyParserPlugin.js | 301 - .../dependencies/ImportMetaContextPlugin.js | 72 - .../ImportMetaHotAcceptDependency.js | 41 - .../ImportMetaHotDeclineDependency.js | 42 - .../lib/dependencies/ImportMetaPlugin.js | 253 - .../lib/dependencies/ImportParserPlugin.js | 339 - webpack-lib/lib/dependencies/ImportPlugin.js | 96 - .../lib/dependencies/ImportWeakDependency.js | 72 - .../lib/dependencies/JsonExportsDependency.js | 117 - .../lib/dependencies/LoaderDependency.js | 41 - .../dependencies/LoaderImportDependency.js | 42 - webpack-lib/lib/dependencies/LoaderPlugin.js | 292 - webpack-lib/lib/dependencies/LocalModule.js | 60 - .../lib/dependencies/LocalModuleDependency.js | 84 - .../lib/dependencies/LocalModulesHelpers.js | 68 - .../dependencies/ModuleDecoratorDependency.js | 137 - .../lib/dependencies/ModuleDependency.js | 98 - .../ModuleDependencyTemplateAsId.js | 35 - .../ModuleDependencyTemplateAsRequireId.js | 38 - .../dependencies/ModuleHotAcceptDependency.js | 41 - .../ModuleHotDeclineDependency.js | 42 - .../lib/dependencies/NullDependency.js | 40 - .../lib/dependencies/PrefetchDependency.js | 27 - .../lib/dependencies/ProvidedDependency.js | 157 - .../dependencies/PureExpressionDependency.js | 162 - .../dependencies/RequireContextDependency.js | 37 - .../RequireContextDependencyParserPlugin.js | 66 - .../lib/dependencies/RequireContextPlugin.js | 163 - .../RequireEnsureDependenciesBlock.js | 29 - ...uireEnsureDependenciesBlockParserPlugin.js | 138 - .../dependencies/RequireEnsureDependency.js | 115 - .../RequireEnsureItemDependency.js | 36 - .../lib/dependencies/RequireEnsurePlugin.js | 86 - .../dependencies/RequireHeaderDependency.js | 70 - .../dependencies/RequireIncludeDependency.js | 79 - .../RequireIncludeDependencyParserPlugin.js | 101 - .../lib/dependencies/RequireIncludePlugin.js | 62 - .../RequireResolveContextDependency.js | 67 - .../dependencies/RequireResolveDependency.js | 58 - .../RequireResolveHeaderDependency.js | 81 - .../RuntimeRequirementsDependency.js | 85 - .../dependencies/StaticExportsDependency.js | 74 - webpack-lib/lib/dependencies/SystemPlugin.js | 168 - .../lib/dependencies/SystemRuntimeModule.js | 35 - webpack-lib/lib/dependencies/URLDependency.js | 170 - webpack-lib/lib/dependencies/URLPlugin.js | 208 - .../lib/dependencies/UnsupportedDependency.js | 82 - .../WebAssemblyExportImportedDependency.js | 93 - .../WebAssemblyImportDependency.js | 108 - .../WebpackIsIncludedDependency.js | 85 - .../lib/dependencies/WorkerDependency.js | 133 - webpack-lib/lib/dependencies/WorkerPlugin.js | 500 -- .../lib/dependencies/getFunctionExpression.js | 64 - .../lib/dependencies/processExportInfo.js | 67 - .../lib/electron/ElectronTargetPlugin.js | 69 - webpack-lib/lib/errors/BuildCycleError.js | 27 - .../esm/ExportWebpackRequireRuntimeModule.js | 30 - .../lib/esm/ModuleChunkFormatPlugin.js | 218 - .../lib/esm/ModuleChunkLoadingPlugin.js | 87 - .../esm/ModuleChunkLoadingRuntimeModule.js | 351 -- webpack-lib/lib/formatLocation.js | 67 - .../lib/hmr/HotModuleReplacement.runtime.js | 407 -- .../hmr/HotModuleReplacementRuntimeModule.js | 42 - .../JavascriptHotModuleReplacement.runtime.js | 461 -- webpack-lib/lib/hmr/LazyCompilationPlugin.js | 455 -- webpack-lib/lib/hmr/lazyCompilationBackend.js | 161 - .../lib/ids/ChunkModuleIdRangePlugin.js | 89 - .../lib/ids/DeterministicChunkIdsPlugin.js | 77 - .../lib/ids/DeterministicModuleIdsPlugin.js | 97 - webpack-lib/lib/ids/HashedModuleIdsPlugin.js | 88 - webpack-lib/lib/ids/IdHelpers.js | 473 -- webpack-lib/lib/ids/NamedChunkIdsPlugin.js | 93 - webpack-lib/lib/ids/NamedModuleIdsPlugin.js | 69 - webpack-lib/lib/ids/NaturalChunkIdsPlugin.js | 33 - webpack-lib/lib/ids/NaturalModuleIdsPlugin.js | 39 - .../lib/ids/OccurrenceChunkIdsPlugin.js | 84 - .../lib/ids/OccurrenceModuleIdsPlugin.js | 159 - webpack-lib/lib/ids/SyncModuleIdsPlugin.js | 146 - webpack-lib/lib/index.js | 641 -- .../ArrayPushCallbackChunkFormatPlugin.js | 156 - .../javascript/BasicEvaluatedExpression.js | 594 -- webpack-lib/lib/javascript/ChunkHelpers.js | 33 - .../javascript/CommonJsChunkFormatPlugin.js | 175 - .../javascript/EnableChunkLoadingPlugin.js | 121 - .../lib/javascript/JavascriptGenerator.js | 255 - .../lib/javascript/JavascriptModulesPlugin.js | 1651 ----- .../lib/javascript/JavascriptParser.js | 5007 --------------- .../lib/javascript/JavascriptParserHelpers.js | 128 - webpack-lib/lib/javascript/StartupHelpers.js | 177 - webpack-lib/lib/json/JsonData.js | 74 - webpack-lib/lib/json/JsonGenerator.js | 199 - webpack-lib/lib/json/JsonModulesPlugin.js | 55 - webpack-lib/lib/json/JsonParser.js | 73 - .../lib/library/AbstractLibraryPlugin.js | 301 - webpack-lib/lib/library/AmdLibraryPlugin.js | 178 - .../lib/library/AssignLibraryPlugin.js | 387 -- .../lib/library/EnableLibraryPlugin.js | 279 - .../library/ExportPropertyLibraryPlugin.js | 122 - webpack-lib/lib/library/JsonpLibraryPlugin.js | 89 - .../lib/library/ModernModuleLibraryPlugin.js | 144 - .../lib/library/ModuleLibraryPlugin.js | 109 - .../lib/library/SystemLibraryPlugin.js | 235 - webpack-lib/lib/library/UmdLibraryPlugin.js | 351 -- webpack-lib/lib/logging/Logger.js | 216 - .../lib/logging/createConsoleLogger.js | 213 - webpack-lib/lib/logging/runtime.js | 45 - webpack-lib/lib/logging/truncateArgs.js | 83 - .../lib/node/CommonJsChunkLoadingPlugin.js | 121 - webpack-lib/lib/node/NodeEnvironmentPlugin.js | 66 - webpack-lib/lib/node/NodeSourcePlugin.js | 19 - webpack-lib/lib/node/NodeTargetPlugin.js | 85 - webpack-lib/lib/node/NodeTemplatePlugin.js | 41 - webpack-lib/lib/node/NodeWatchFileSystem.js | 192 - .../node/ReadFileChunkLoadingRuntimeModule.js | 301 - .../node/ReadFileCompileAsyncWasmPlugin.js | 123 - .../lib/node/ReadFileCompileWasmPlugin.js | 129 - .../node/RequireChunkLoadingRuntimeModule.js | 246 - webpack-lib/lib/node/nodeConsole.js | 163 - .../lib/optimize/AggressiveMergingPlugin.js | 98 - .../lib/optimize/AggressiveSplittingPlugin.js | 348 -- .../lib/optimize/ConcatenatedModule.js | 1904 ------ .../optimize/EnsureChunkConditionsPlugin.js | 88 - .../lib/optimize/FlagIncludedChunksPlugin.js | 130 - webpack-lib/lib/optimize/InnerGraph.js | 351 -- webpack-lib/lib/optimize/InnerGraphPlugin.js | 450 -- .../lib/optimize/LimitChunkCountPlugin.js | 277 - .../lib/optimize/MangleExportsPlugin.js | 182 - .../optimize/MergeDuplicateChunksPlugin.js | 135 - .../lib/optimize/MinChunkSizePlugin.js | 113 - webpack-lib/lib/optimize/MinMaxSizeWarning.js | 35 - .../lib/optimize/ModuleConcatenationPlugin.js | 932 --- .../lib/optimize/RealContentHashPlugin.js | 464 -- .../lib/optimize/RemoveEmptyChunksPlugin.js | 57 - .../lib/optimize/RemoveParentModulesPlugin.js | 204 - .../lib/optimize/RuntimeChunkPlugin.js | 57 - .../lib/optimize/SideEffectsFlagPlugin.js | 396 -- webpack-lib/lib/optimize/SplitChunksPlugin.js | 1767 ------ .../performance/AssetsOverSizeLimitWarning.js | 32 - .../EntrypointsOverSizeLimitWarning.js | 35 - .../lib/performance/NoAsyncChunksWarning.js | 20 - .../lib/performance/SizeLimitsPlugin.js | 179 - .../ChunkPrefetchFunctionRuntimeModule.js | 46 - .../prefetch/ChunkPrefetchPreloadPlugin.js | 98 - .../ChunkPrefetchStartupRuntimeModule.js | 55 - .../ChunkPrefetchTriggerRuntimeModule.js | 51 - .../ChunkPreloadTriggerRuntimeModule.js | 45 - .../lib/rules/BasicEffectRulePlugin.js | 45 - .../lib/rules/BasicMatcherRulePlugin.js | 54 - .../lib/rules/ObjectMatcherRulePlugin.js | 64 - webpack-lib/lib/rules/RuleSetCompiler.js | 400 -- webpack-lib/lib/rules/UseEffectRulePlugin.js | 200 - .../lib/runtime/AsyncModuleRuntimeModule.js | 133 - .../runtime/AutoPublicPathRuntimeModule.js | 85 - .../lib/runtime/BaseUriRuntimeModule.js | 35 - .../lib/runtime/ChunkNameRuntimeModule.js | 27 - .../CompatGetDefaultExportRuntimeModule.js | 40 - .../lib/runtime/CompatRuntimeModule.js | 83 - .../CreateFakeNamespaceObjectRuntimeModule.js | 69 - .../lib/runtime/CreateScriptRuntimeModule.js | 38 - .../runtime/CreateScriptUrlRuntimeModule.js | 38 - .../DefinePropertyGettersRuntimeModule.js | 42 - .../lib/runtime/EnsureChunkRuntimeModule.js | 68 - .../runtime/GetChunkFilenameRuntimeModule.js | 295 - .../lib/runtime/GetFullHashRuntimeModule.js | 30 - .../runtime/GetMainFilenameRuntimeModule.js | 47 - .../GetTrustedTypesPolicyRuntimeModule.js | 98 - .../lib/runtime/GlobalRuntimeModule.js | 47 - .../runtime/HasOwnPropertyRuntimeModule.js | 35 - .../lib/runtime/HelperRuntimeModule.js | 18 - .../lib/runtime/LoadScriptRuntimeModule.js | 174 - .../MakeNamespaceObjectRuntimeModule.js | 39 - webpack-lib/lib/runtime/NonceRuntimeModule.js | 24 - .../runtime/OnChunksLoadedRuntimeModule.js | 78 - .../lib/runtime/PublicPathRuntimeModule.js | 37 - .../lib/runtime/RelativeUrlRuntimeModule.js | 44 - .../lib/runtime/RuntimeIdRuntimeModule.js | 32 - .../runtime/StartupChunkDependenciesPlugin.js | 89 - .../StartupChunkDependenciesRuntimeModule.js | 75 - .../runtime/StartupEntrypointRuntimeModule.js | 54 - .../lib/runtime/SystemContextRuntimeModule.js | 25 - webpack-lib/lib/schemes/DataUriPlugin.js | 69 - webpack-lib/lib/schemes/FileUriPlugin.js | 49 - webpack-lib/lib/schemes/HttpUriPlugin.js | 1271 ---- .../lib/serialization/ArraySerializer.js | 38 - .../lib/serialization/BinaryMiddleware.js | 1142 ---- .../lib/serialization/DateObjectSerializer.js | 28 - .../serialization/ErrorObjectSerializer.js | 44 - .../lib/serialization/FileMiddleware.js | 731 --- .../lib/serialization/MapObjectSerializer.js | 48 - .../NullPrototypeObjectSerializer.js | 51 - .../lib/serialization/ObjectMiddleware.js | 778 --- .../serialization/PlainObjectSerializer.js | 117 - .../serialization/RegExpObjectSerializer.js | 29 - webpack-lib/lib/serialization/Serializer.js | 64 - .../lib/serialization/SerializerMiddleware.js | 154 - .../lib/serialization/SetObjectSerializer.js | 40 - .../lib/serialization/SingleItemMiddleware.js | 34 - webpack-lib/lib/serialization/types.js | 13 - .../ConsumeSharedFallbackDependency.js | 33 - .../lib/sharing/ConsumeSharedModule.js | 267 - .../lib/sharing/ConsumeSharedPlugin.js | 379 -- .../lib/sharing/ConsumeSharedRuntimeModule.js | 355 -- .../lib/sharing/ProvideForSharedDependency.js | 33 - .../lib/sharing/ProvideSharedDependency.js | 80 - .../lib/sharing/ProvideSharedModule.js | 194 - .../lib/sharing/ProvideSharedModuleFactory.js | 35 - .../lib/sharing/ProvideSharedPlugin.js | 244 - webpack-lib/lib/sharing/SharePlugin.js | 92 - webpack-lib/lib/sharing/ShareRuntimeModule.js | 151 - .../lib/sharing/resolveMatchedConfigs.js | 92 - webpack-lib/lib/sharing/utils.js | 425 -- .../lib/stats/DefaultStatsFactoryPlugin.js | 2614 -------- .../lib/stats/DefaultStatsPresetPlugin.js | 386 -- .../lib/stats/DefaultStatsPrinterPlugin.js | 1695 ----- webpack-lib/lib/stats/StatsFactory.js | 363 -- webpack-lib/lib/stats/StatsPrinter.js | 280 - webpack-lib/lib/util/ArrayHelpers.js | 48 - webpack-lib/lib/util/ArrayQueue.js | 104 - webpack-lib/lib/util/AsyncQueue.js | 410 -- webpack-lib/lib/util/Hash.js | 35 - webpack-lib/lib/util/IterableHelpers.js | 45 - webpack-lib/lib/util/LazyBucketSortedSet.js | 252 - webpack-lib/lib/util/LazySet.js | 229 - webpack-lib/lib/util/MapHelpers.js | 34 - .../lib/util/ParallelismFactorCalculator.js | 69 - webpack-lib/lib/util/Queue.js | 52 - webpack-lib/lib/util/Semaphore.js | 51 - webpack-lib/lib/util/SetHelpers.js | 94 - webpack-lib/lib/util/SortableSet.js | 173 - webpack-lib/lib/util/StackedCacheMap.js | 140 - webpack-lib/lib/util/StackedMap.js | 164 - webpack-lib/lib/util/StringXor.js | 101 - webpack-lib/lib/util/TupleQueue.js | 67 - webpack-lib/lib/util/TupleSet.js | 160 - webpack-lib/lib/util/URLAbsoluteSpecifier.js | 87 - webpack-lib/lib/util/WeakTupleMap.js | 213 - webpack-lib/lib/util/binarySearchBounds.js | 128 - webpack-lib/lib/util/chainedImports.js | 97 - webpack-lib/lib/util/cleverMerge.js | 607 -- webpack-lib/lib/util/comparators.js | 522 -- webpack-lib/lib/util/compileBooleanMatcher.js | 234 - webpack-lib/lib/util/concatenate.js | 227 - webpack-lib/lib/util/conventions.js | 126 - .../lib/util/create-schema-validation.js | 41 - webpack-lib/lib/util/createHash.js | 194 - webpack-lib/lib/util/deprecation.js | 347 -- webpack-lib/lib/util/deterministicGrouping.js | 540 -- webpack-lib/lib/util/extractUrlAndGlobal.js | 18 - webpack-lib/lib/util/findGraphRoots.js | 231 - webpack-lib/lib/util/fs.js | 651 -- webpack-lib/lib/util/generateDebugId.js | 33 - webpack-lib/lib/util/hash/BatchedHash.js | 71 - webpack-lib/lib/util/hash/md4.js | 20 - webpack-lib/lib/util/hash/wasm-hash.js | 174 - webpack-lib/lib/util/hash/xxhash64.js | 20 - webpack-lib/lib/util/identifier.js | 403 -- webpack-lib/lib/util/internalSerializables.js | 224 - webpack-lib/lib/util/magicComment.js | 21 - webpack-lib/lib/util/makeSerializable.js | 60 - webpack-lib/lib/util/memoize.js | 33 - webpack-lib/lib/util/nonNumericOnlyHash.js | 22 - webpack-lib/lib/util/numberHash.js | 95 - webpack-lib/lib/util/objectToMap.js | 14 - webpack-lib/lib/util/processAsyncTree.js | 68 - webpack-lib/lib/util/propertyAccess.js | 30 - webpack-lib/lib/util/propertyName.js | 77 - .../lib/util/registerExternalSerializer.js | 337 - webpack-lib/lib/util/runtime.js | 687 -- webpack-lib/lib/util/semver.js | 602 -- webpack-lib/lib/util/serialization.js | 153 - webpack-lib/lib/util/smartGrouping.js | 206 - webpack-lib/lib/util/source.js | 61 - webpack-lib/lib/validateSchema.js | 176 - .../AsyncWasmLoadingRuntimeModule.js | 142 - .../wasm-async/AsyncWebAssemblyGenerator.js | 61 - .../AsyncWebAssemblyJavascriptGenerator.js | 206 - .../AsyncWebAssemblyModulesPlugin.js | 218 - .../lib/wasm-async/AsyncWebAssemblyParser.js | 88 - .../UniversalCompileAsyncWasmPlugin.js | 103 - .../UnsupportedWebAssemblyFeatureError.js | 16 - .../WasmChunkLoadingRuntimeModule.js | 413 -- .../wasm-sync/WasmFinalizeExportsPlugin.js | 93 - .../lib/wasm-sync/WebAssemblyGenerator.js | 520 -- .../WebAssemblyInInitialChunkError.js | 106 - .../WebAssemblyJavascriptGenerator.js | 217 - .../lib/wasm-sync/WebAssemblyModulesPlugin.js | 152 - .../lib/wasm-sync/WebAssemblyParser.js | 206 - webpack-lib/lib/wasm-sync/WebAssemblyUtils.js | 66 - .../lib/wasm/EnableWasmLoadingPlugin.js | 134 - .../lib/web/FetchCompileAsyncWasmPlugin.js | 70 - webpack-lib/lib/web/FetchCompileWasmPlugin.js | 87 - .../lib/web/JsonpChunkLoadingPlugin.js | 100 - .../lib/web/JsonpChunkLoadingRuntimeModule.js | 470 -- webpack-lib/lib/web/JsonpTemplatePlugin.js | 38 - webpack-lib/lib/webpack.js | 195 - .../ImportScriptsChunkLoadingPlugin.js | 105 - .../ImportScriptsChunkLoadingRuntimeModule.js | 242 - .../lib/webworker/WebWorkerTemplatePlugin.js | 25 - 548 files changed, 118 insertions(+), 131829 deletions(-) delete mode 100644 webpack-lib/lib/APIPlugin.js delete mode 100644 webpack-lib/lib/AbstractMethodError.js delete mode 100644 webpack-lib/lib/AsyncDependenciesBlock.js delete mode 100644 webpack-lib/lib/AsyncDependencyToInitialChunkError.js delete mode 100644 webpack-lib/lib/AutomaticPrefetchPlugin.js delete mode 100644 webpack-lib/lib/BannerPlugin.js delete mode 100644 webpack-lib/lib/Cache.js delete mode 100644 webpack-lib/lib/CacheFacade.js delete mode 100644 webpack-lib/lib/CaseSensitiveModulesWarning.js delete mode 100644 webpack-lib/lib/Chunk.js delete mode 100644 webpack-lib/lib/ChunkGraph.js delete mode 100644 webpack-lib/lib/ChunkGroup.js delete mode 100644 webpack-lib/lib/ChunkRenderError.js delete mode 100644 webpack-lib/lib/ChunkTemplate.js delete mode 100644 webpack-lib/lib/CleanPlugin.js delete mode 100644 webpack-lib/lib/CodeGenerationError.js delete mode 100644 webpack-lib/lib/CodeGenerationResults.js delete mode 100644 webpack-lib/lib/CommentCompilationWarning.js delete mode 100644 webpack-lib/lib/CompatibilityPlugin.js delete mode 100644 webpack-lib/lib/Compilation.js delete mode 100644 webpack-lib/lib/Compiler.js delete mode 100644 webpack-lib/lib/ConcatenationScope.js delete mode 100644 webpack-lib/lib/ConcurrentCompilationError.js delete mode 100644 webpack-lib/lib/ConditionalInitFragment.js delete mode 100644 webpack-lib/lib/ConstPlugin.js delete mode 100644 webpack-lib/lib/ContextExclusionPlugin.js delete mode 100644 webpack-lib/lib/ContextModule.js delete mode 100644 webpack-lib/lib/ContextModuleFactory.js delete mode 100644 webpack-lib/lib/ContextReplacementPlugin.js delete mode 100644 webpack-lib/lib/CssModule.js delete mode 100644 webpack-lib/lib/DefinePlugin.js delete mode 100644 webpack-lib/lib/DelegatedModule.js delete mode 100644 webpack-lib/lib/DelegatedModuleFactoryPlugin.js delete mode 100644 webpack-lib/lib/DelegatedPlugin.js delete mode 100644 webpack-lib/lib/DependenciesBlock.js delete mode 100644 webpack-lib/lib/Dependency.js delete mode 100644 webpack-lib/lib/DependencyTemplate.js delete mode 100644 webpack-lib/lib/DependencyTemplates.js delete mode 100644 webpack-lib/lib/DllEntryPlugin.js delete mode 100644 webpack-lib/lib/DllModule.js delete mode 100644 webpack-lib/lib/DllModuleFactory.js delete mode 100644 webpack-lib/lib/DllPlugin.js delete mode 100644 webpack-lib/lib/DllReferencePlugin.js delete mode 100644 webpack-lib/lib/DynamicEntryPlugin.js delete mode 100644 webpack-lib/lib/EntryOptionPlugin.js delete mode 100644 webpack-lib/lib/EntryPlugin.js delete mode 100644 webpack-lib/lib/Entrypoint.js delete mode 100644 webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js delete mode 100644 webpack-lib/lib/EnvironmentPlugin.js delete mode 100644 webpack-lib/lib/ErrorHelpers.js delete mode 100644 webpack-lib/lib/EvalDevToolModulePlugin.js delete mode 100644 webpack-lib/lib/EvalSourceMapDevToolPlugin.js delete mode 100644 webpack-lib/lib/ExportsInfo.js delete mode 100644 webpack-lib/lib/ExportsInfoApiPlugin.js delete mode 100644 webpack-lib/lib/ExternalModule.js delete mode 100644 webpack-lib/lib/ExternalModuleFactoryPlugin.js delete mode 100644 webpack-lib/lib/ExternalsPlugin.js delete mode 100644 webpack-lib/lib/FalseIIFEUmdWarning.js delete mode 100644 webpack-lib/lib/FileSystemInfo.js delete mode 100644 webpack-lib/lib/FlagAllModulesAsUsedPlugin.js delete mode 100644 webpack-lib/lib/FlagDependencyExportsPlugin.js delete mode 100644 webpack-lib/lib/FlagDependencyUsagePlugin.js delete mode 100644 webpack-lib/lib/FlagEntryExportAsUsedPlugin.js delete mode 100644 webpack-lib/lib/Generator.js delete mode 100644 webpack-lib/lib/GraphHelpers.js delete mode 100644 webpack-lib/lib/HarmonyLinkingError.js delete mode 100644 webpack-lib/lib/HookWebpackError.js delete mode 100644 webpack-lib/lib/HotModuleReplacementPlugin.js delete mode 100644 webpack-lib/lib/HotUpdateChunk.js delete mode 100644 webpack-lib/lib/IgnoreErrorModuleFactory.js delete mode 100644 webpack-lib/lib/IgnorePlugin.js delete mode 100644 webpack-lib/lib/IgnoreWarningsPlugin.js delete mode 100644 webpack-lib/lib/InitFragment.js delete mode 100644 webpack-lib/lib/InvalidDependenciesModuleWarning.js delete mode 100644 webpack-lib/lib/JavascriptMetaInfoPlugin.js delete mode 100644 webpack-lib/lib/LibManifestPlugin.js delete mode 100644 webpack-lib/lib/LibraryTemplatePlugin.js delete mode 100644 webpack-lib/lib/LoaderOptionsPlugin.js delete mode 100644 webpack-lib/lib/LoaderTargetPlugin.js delete mode 100644 webpack-lib/lib/MainTemplate.js delete mode 100644 webpack-lib/lib/Module.js delete mode 100644 webpack-lib/lib/ModuleBuildError.js delete mode 100644 webpack-lib/lib/ModuleDependencyError.js delete mode 100644 webpack-lib/lib/ModuleDependencyWarning.js delete mode 100644 webpack-lib/lib/ModuleError.js delete mode 100644 webpack-lib/lib/ModuleFactory.js delete mode 100644 webpack-lib/lib/ModuleFilenameHelpers.js delete mode 100644 webpack-lib/lib/ModuleGraph.js delete mode 100644 webpack-lib/lib/ModuleGraphConnection.js delete mode 100644 webpack-lib/lib/ModuleHashingError.js delete mode 100644 webpack-lib/lib/ModuleInfoHeaderPlugin.js delete mode 100644 webpack-lib/lib/ModuleNotFoundError.js delete mode 100644 webpack-lib/lib/ModuleParseError.js delete mode 100644 webpack-lib/lib/ModuleProfile.js delete mode 100644 webpack-lib/lib/ModuleRestoreError.js delete mode 100644 webpack-lib/lib/ModuleSourceTypesConstants.js delete mode 100644 webpack-lib/lib/ModuleStoreError.js delete mode 100644 webpack-lib/lib/ModuleTemplate.js delete mode 100644 webpack-lib/lib/ModuleTypeConstants.js delete mode 100644 webpack-lib/lib/ModuleWarning.js delete mode 100644 webpack-lib/lib/MultiCompiler.js delete mode 100644 webpack-lib/lib/MultiStats.js delete mode 100644 webpack-lib/lib/MultiWatching.js delete mode 100644 webpack-lib/lib/NoEmitOnErrorsPlugin.js delete mode 100644 webpack-lib/lib/NoModeWarning.js delete mode 100644 webpack-lib/lib/NodeStuffInWebError.js delete mode 100644 webpack-lib/lib/NodeStuffPlugin.js delete mode 100644 webpack-lib/lib/NormalModule.js delete mode 100644 webpack-lib/lib/NormalModuleFactory.js delete mode 100644 webpack-lib/lib/NormalModuleReplacementPlugin.js delete mode 100644 webpack-lib/lib/NullFactory.js delete mode 100644 webpack-lib/lib/OptimizationStages.js delete mode 100644 webpack-lib/lib/OptionsApply.js delete mode 100644 webpack-lib/lib/Parser.js delete mode 100644 webpack-lib/lib/PlatformPlugin.js delete mode 100644 webpack-lib/lib/PrefetchPlugin.js delete mode 100644 webpack-lib/lib/ProgressPlugin.js delete mode 100644 webpack-lib/lib/ProvidePlugin.js delete mode 100644 webpack-lib/lib/RawModule.js delete mode 100644 webpack-lib/lib/RecordIdsPlugin.js delete mode 100644 webpack-lib/lib/RequestShortener.js delete mode 100644 webpack-lib/lib/RequireJsStuffPlugin.js delete mode 100644 webpack-lib/lib/ResolverFactory.js delete mode 100644 webpack-lib/lib/RuntimeGlobals.js delete mode 100644 webpack-lib/lib/RuntimeModule.js delete mode 100644 webpack-lib/lib/RuntimePlugin.js delete mode 100644 webpack-lib/lib/RuntimeTemplate.js delete mode 100644 webpack-lib/lib/SelfModuleFactory.js delete mode 100644 webpack-lib/lib/SingleEntryPlugin.js delete mode 100644 webpack-lib/lib/SizeFormatHelpers.js delete mode 100644 webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js delete mode 100644 webpack-lib/lib/SourceMapDevToolPlugin.js delete mode 100644 webpack-lib/lib/Stats.js delete mode 100644 webpack-lib/lib/Template.js delete mode 100644 webpack-lib/lib/TemplatedPathPlugin.js delete mode 100644 webpack-lib/lib/UnhandledSchemeError.js delete mode 100644 webpack-lib/lib/UnsupportedFeatureWarning.js delete mode 100644 webpack-lib/lib/UseStrictPlugin.js delete mode 100644 webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js delete mode 100644 webpack-lib/lib/WarnDeprecatedOptionPlugin.js delete mode 100644 webpack-lib/lib/WarnNoModeSetPlugin.js delete mode 100644 webpack-lib/lib/WatchIgnorePlugin.js delete mode 100644 webpack-lib/lib/Watching.js delete mode 100644 webpack-lib/lib/WebpackError.js delete mode 100644 webpack-lib/lib/WebpackIsIncludedPlugin.js delete mode 100644 webpack-lib/lib/WebpackOptionsApply.js delete mode 100644 webpack-lib/lib/WebpackOptionsDefaulter.js delete mode 100644 webpack-lib/lib/asset/AssetGenerator.js delete mode 100644 webpack-lib/lib/asset/AssetModulesPlugin.js delete mode 100644 webpack-lib/lib/asset/AssetParser.js delete mode 100644 webpack-lib/lib/asset/AssetSourceGenerator.js delete mode 100644 webpack-lib/lib/asset/AssetSourceParser.js delete mode 100644 webpack-lib/lib/asset/RawDataUrlModule.js delete mode 100644 webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js delete mode 100644 webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js delete mode 100644 webpack-lib/lib/buildChunkGraph.js delete mode 100644 webpack-lib/lib/cli.js delete mode 100644 webpack-lib/lib/config/browserslistTargetHandler.js delete mode 100644 webpack-lib/lib/config/defaults.js delete mode 100644 webpack-lib/lib/config/normalization.js delete mode 100644 webpack-lib/lib/config/target.js delete mode 100644 webpack-lib/lib/container/ContainerEntryDependency.js delete mode 100644 webpack-lib/lib/container/ContainerEntryModule.js delete mode 100644 webpack-lib/lib/container/ContainerEntryModuleFactory.js delete mode 100644 webpack-lib/lib/container/ContainerExposedDependency.js delete mode 100644 webpack-lib/lib/container/ContainerPlugin.js delete mode 100644 webpack-lib/lib/container/ContainerReferencePlugin.js delete mode 100644 webpack-lib/lib/container/FallbackDependency.js delete mode 100644 webpack-lib/lib/container/FallbackItemDependency.js delete mode 100644 webpack-lib/lib/container/FallbackModule.js delete mode 100644 webpack-lib/lib/container/FallbackModuleFactory.js delete mode 100644 webpack-lib/lib/container/HoistContainerReferencesPlugin.js delete mode 100644 webpack-lib/lib/container/ModuleFederationPlugin.js delete mode 100644 webpack-lib/lib/container/RemoteModule.js delete mode 100644 webpack-lib/lib/container/RemoteRuntimeModule.js delete mode 100644 webpack-lib/lib/container/RemoteToExternalDependency.js delete mode 100644 webpack-lib/lib/container/options.js delete mode 100644 webpack-lib/lib/css/CssGenerator.js delete mode 100644 webpack-lib/lib/css/CssLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/css/CssModulesPlugin.js delete mode 100644 webpack-lib/lib/css/CssParser.js delete mode 100644 webpack-lib/lib/css/walkCssTokens.js delete mode 100644 webpack-lib/lib/debug/ProfilingPlugin.js delete mode 100644 webpack-lib/lib/dependencies/AMDDefineDependency.js delete mode 100644 webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/AMDPlugin.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireArrayDependency.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireDependency.js delete mode 100644 webpack-lib/lib/dependencies/AMDRequireItemDependency.js delete mode 100644 webpack-lib/lib/dependencies/AMDRuntimeModules.js delete mode 100644 webpack-lib/lib/dependencies/CachedConstDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsExportsDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsPlugin.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsRequireDependency.js delete mode 100644 webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js delete mode 100644 webpack-lib/lib/dependencies/ConstDependency.js delete mode 100644 webpack-lib/lib/dependencies/ContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/ContextDependencyHelpers.js delete mode 100644 webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js delete mode 100644 webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js delete mode 100644 webpack-lib/lib/dependencies/ContextElementDependency.js delete mode 100644 webpack-lib/lib/dependencies/CreateScriptUrlDependency.js delete mode 100644 webpack-lib/lib/dependencies/CriticalDependencyWarning.js delete mode 100644 webpack-lib/lib/dependencies/CssIcssExportDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssIcssImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssIcssSymbolDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/CssUrlDependency.js delete mode 100644 webpack-lib/lib/dependencies/DelegatedSourceDependency.js delete mode 100644 webpack-lib/lib/dependencies/DllEntryDependency.js delete mode 100644 webpack-lib/lib/dependencies/DynamicExports.js delete mode 100644 webpack-lib/lib/dependencies/EntryDependency.js delete mode 100644 webpack-lib/lib/dependencies/ExportsInfoDependency.js delete mode 100644 webpack-lib/lib/dependencies/ExternalModuleDependency.js delete mode 100644 webpack-lib/lib/dependencies/ExternalModuleInitFragment.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyAcceptDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportInitFragment.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyExports.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyModulesPlugin.js delete mode 100644 webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportEagerDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaContextPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js delete mode 100644 webpack-lib/lib/dependencies/ImportMetaPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportPlugin.js delete mode 100644 webpack-lib/lib/dependencies/ImportWeakDependency.js delete mode 100644 webpack-lib/lib/dependencies/JsonExportsDependency.js delete mode 100644 webpack-lib/lib/dependencies/LoaderDependency.js delete mode 100644 webpack-lib/lib/dependencies/LoaderImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/LoaderPlugin.js delete mode 100644 webpack-lib/lib/dependencies/LocalModule.js delete mode 100644 webpack-lib/lib/dependencies/LocalModuleDependency.js delete mode 100644 webpack-lib/lib/dependencies/LocalModulesHelpers.js delete mode 100644 webpack-lib/lib/dependencies/ModuleDecoratorDependency.js delete mode 100644 webpack-lib/lib/dependencies/ModuleDependency.js delete mode 100644 webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js delete mode 100644 webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js delete mode 100644 webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js delete mode 100644 webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js delete mode 100644 webpack-lib/lib/dependencies/NullDependency.js delete mode 100644 webpack-lib/lib/dependencies/PrefetchDependency.js delete mode 100644 webpack-lib/lib/dependencies/ProvidedDependency.js delete mode 100644 webpack-lib/lib/dependencies/PureExpressionDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireContextPlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js delete mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireEnsureDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireEnsureItemDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireEnsurePlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireHeaderDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireIncludeDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireIncludePlugin.js delete mode 100644 webpack-lib/lib/dependencies/RequireResolveContextDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireResolveDependency.js delete mode 100644 webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js delete mode 100644 webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js delete mode 100644 webpack-lib/lib/dependencies/StaticExportsDependency.js delete mode 100644 webpack-lib/lib/dependencies/SystemPlugin.js delete mode 100644 webpack-lib/lib/dependencies/SystemRuntimeModule.js delete mode 100644 webpack-lib/lib/dependencies/URLDependency.js delete mode 100644 webpack-lib/lib/dependencies/URLPlugin.js delete mode 100644 webpack-lib/lib/dependencies/UnsupportedDependency.js delete mode 100644 webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js delete mode 100644 webpack-lib/lib/dependencies/WebAssemblyImportDependency.js delete mode 100644 webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js delete mode 100644 webpack-lib/lib/dependencies/WorkerDependency.js delete mode 100644 webpack-lib/lib/dependencies/WorkerPlugin.js delete mode 100644 webpack-lib/lib/dependencies/getFunctionExpression.js delete mode 100644 webpack-lib/lib/dependencies/processExportInfo.js delete mode 100644 webpack-lib/lib/electron/ElectronTargetPlugin.js delete mode 100644 webpack-lib/lib/errors/BuildCycleError.js delete mode 100644 webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js delete mode 100644 webpack-lib/lib/esm/ModuleChunkFormatPlugin.js delete mode 100644 webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js delete mode 100644 webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/formatLocation.js delete mode 100644 webpack-lib/lib/hmr/HotModuleReplacement.runtime.js delete mode 100644 webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js delete mode 100644 webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js delete mode 100644 webpack-lib/lib/hmr/LazyCompilationPlugin.js delete mode 100644 webpack-lib/lib/hmr/lazyCompilationBackend.js delete mode 100644 webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js delete mode 100644 webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/HashedModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/IdHelpers.js delete mode 100644 webpack-lib/lib/ids/NamedChunkIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/NamedModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/NaturalChunkIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/NaturalModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/ids/SyncModuleIdsPlugin.js delete mode 100644 webpack-lib/lib/index.js delete mode 100644 webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js delete mode 100644 webpack-lib/lib/javascript/BasicEvaluatedExpression.js delete mode 100644 webpack-lib/lib/javascript/ChunkHelpers.js delete mode 100644 webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js delete mode 100644 webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js delete mode 100644 webpack-lib/lib/javascript/JavascriptGenerator.js delete mode 100644 webpack-lib/lib/javascript/JavascriptModulesPlugin.js delete mode 100644 webpack-lib/lib/javascript/JavascriptParser.js delete mode 100644 webpack-lib/lib/javascript/JavascriptParserHelpers.js delete mode 100644 webpack-lib/lib/javascript/StartupHelpers.js delete mode 100644 webpack-lib/lib/json/JsonData.js delete mode 100644 webpack-lib/lib/json/JsonGenerator.js delete mode 100644 webpack-lib/lib/json/JsonModulesPlugin.js delete mode 100644 webpack-lib/lib/json/JsonParser.js delete mode 100644 webpack-lib/lib/library/AbstractLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/AmdLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/AssignLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/EnableLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/ExportPropertyLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/JsonpLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/ModernModuleLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/ModuleLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/SystemLibraryPlugin.js delete mode 100644 webpack-lib/lib/library/UmdLibraryPlugin.js delete mode 100644 webpack-lib/lib/logging/Logger.js delete mode 100644 webpack-lib/lib/logging/createConsoleLogger.js delete mode 100644 webpack-lib/lib/logging/runtime.js delete mode 100644 webpack-lib/lib/logging/truncateArgs.js delete mode 100644 webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js delete mode 100644 webpack-lib/lib/node/NodeEnvironmentPlugin.js delete mode 100644 webpack-lib/lib/node/NodeSourcePlugin.js delete mode 100644 webpack-lib/lib/node/NodeTargetPlugin.js delete mode 100644 webpack-lib/lib/node/NodeTemplatePlugin.js delete mode 100644 webpack-lib/lib/node/NodeWatchFileSystem.js delete mode 100644 webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js delete mode 100644 webpack-lib/lib/node/ReadFileCompileWasmPlugin.js delete mode 100644 webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/node/nodeConsole.js delete mode 100644 webpack-lib/lib/optimize/AggressiveMergingPlugin.js delete mode 100644 webpack-lib/lib/optimize/AggressiveSplittingPlugin.js delete mode 100644 webpack-lib/lib/optimize/ConcatenatedModule.js delete mode 100644 webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js delete mode 100644 webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js delete mode 100644 webpack-lib/lib/optimize/InnerGraph.js delete mode 100644 webpack-lib/lib/optimize/InnerGraphPlugin.js delete mode 100644 webpack-lib/lib/optimize/LimitChunkCountPlugin.js delete mode 100644 webpack-lib/lib/optimize/MangleExportsPlugin.js delete mode 100644 webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js delete mode 100644 webpack-lib/lib/optimize/MinChunkSizePlugin.js delete mode 100644 webpack-lib/lib/optimize/MinMaxSizeWarning.js delete mode 100644 webpack-lib/lib/optimize/ModuleConcatenationPlugin.js delete mode 100644 webpack-lib/lib/optimize/RealContentHashPlugin.js delete mode 100644 webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js delete mode 100644 webpack-lib/lib/optimize/RemoveParentModulesPlugin.js delete mode 100644 webpack-lib/lib/optimize/RuntimeChunkPlugin.js delete mode 100644 webpack-lib/lib/optimize/SideEffectsFlagPlugin.js delete mode 100644 webpack-lib/lib/optimize/SplitChunksPlugin.js delete mode 100644 webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js delete mode 100644 webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js delete mode 100644 webpack-lib/lib/performance/NoAsyncChunksWarning.js delete mode 100644 webpack-lib/lib/performance/SizeLimitsPlugin.js delete mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js delete mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js delete mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js delete mode 100644 webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js delete mode 100644 webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js delete mode 100644 webpack-lib/lib/rules/BasicEffectRulePlugin.js delete mode 100644 webpack-lib/lib/rules/BasicMatcherRulePlugin.js delete mode 100644 webpack-lib/lib/rules/ObjectMatcherRulePlugin.js delete mode 100644 webpack-lib/lib/rules/RuleSetCompiler.js delete mode 100644 webpack-lib/lib/rules/UseEffectRulePlugin.js delete mode 100644 webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/BaseUriRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/ChunkNameRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/CompatRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/CreateScriptRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/GetFullHashRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/GlobalRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/HelperRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/LoadScriptRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/NonceRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/PublicPathRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js delete mode 100644 webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js delete mode 100644 webpack-lib/lib/runtime/SystemContextRuntimeModule.js delete mode 100644 webpack-lib/lib/schemes/DataUriPlugin.js delete mode 100644 webpack-lib/lib/schemes/FileUriPlugin.js delete mode 100644 webpack-lib/lib/schemes/HttpUriPlugin.js delete mode 100644 webpack-lib/lib/serialization/ArraySerializer.js delete mode 100644 webpack-lib/lib/serialization/BinaryMiddleware.js delete mode 100644 webpack-lib/lib/serialization/DateObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/ErrorObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/FileMiddleware.js delete mode 100644 webpack-lib/lib/serialization/MapObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/ObjectMiddleware.js delete mode 100644 webpack-lib/lib/serialization/PlainObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/RegExpObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/Serializer.js delete mode 100644 webpack-lib/lib/serialization/SerializerMiddleware.js delete mode 100644 webpack-lib/lib/serialization/SetObjectSerializer.js delete mode 100644 webpack-lib/lib/serialization/SingleItemMiddleware.js delete mode 100644 webpack-lib/lib/serialization/types.js delete mode 100644 webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js delete mode 100644 webpack-lib/lib/sharing/ConsumeSharedModule.js delete mode 100644 webpack-lib/lib/sharing/ConsumeSharedPlugin.js delete mode 100644 webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js delete mode 100644 webpack-lib/lib/sharing/ProvideForSharedDependency.js delete mode 100644 webpack-lib/lib/sharing/ProvideSharedDependency.js delete mode 100644 webpack-lib/lib/sharing/ProvideSharedModule.js delete mode 100644 webpack-lib/lib/sharing/ProvideSharedModuleFactory.js delete mode 100644 webpack-lib/lib/sharing/ProvideSharedPlugin.js delete mode 100644 webpack-lib/lib/sharing/SharePlugin.js delete mode 100644 webpack-lib/lib/sharing/ShareRuntimeModule.js delete mode 100644 webpack-lib/lib/sharing/resolveMatchedConfigs.js delete mode 100644 webpack-lib/lib/sharing/utils.js delete mode 100644 webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js delete mode 100644 webpack-lib/lib/stats/DefaultStatsPresetPlugin.js delete mode 100644 webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js delete mode 100644 webpack-lib/lib/stats/StatsFactory.js delete mode 100644 webpack-lib/lib/stats/StatsPrinter.js delete mode 100644 webpack-lib/lib/util/ArrayHelpers.js delete mode 100644 webpack-lib/lib/util/ArrayQueue.js delete mode 100644 webpack-lib/lib/util/AsyncQueue.js delete mode 100644 webpack-lib/lib/util/Hash.js delete mode 100644 webpack-lib/lib/util/IterableHelpers.js delete mode 100644 webpack-lib/lib/util/LazyBucketSortedSet.js delete mode 100644 webpack-lib/lib/util/LazySet.js delete mode 100644 webpack-lib/lib/util/MapHelpers.js delete mode 100644 webpack-lib/lib/util/ParallelismFactorCalculator.js delete mode 100644 webpack-lib/lib/util/Queue.js delete mode 100644 webpack-lib/lib/util/Semaphore.js delete mode 100644 webpack-lib/lib/util/SetHelpers.js delete mode 100644 webpack-lib/lib/util/SortableSet.js delete mode 100644 webpack-lib/lib/util/StackedCacheMap.js delete mode 100644 webpack-lib/lib/util/StackedMap.js delete mode 100644 webpack-lib/lib/util/StringXor.js delete mode 100644 webpack-lib/lib/util/TupleQueue.js delete mode 100644 webpack-lib/lib/util/TupleSet.js delete mode 100644 webpack-lib/lib/util/URLAbsoluteSpecifier.js delete mode 100644 webpack-lib/lib/util/WeakTupleMap.js delete mode 100644 webpack-lib/lib/util/binarySearchBounds.js delete mode 100644 webpack-lib/lib/util/chainedImports.js delete mode 100644 webpack-lib/lib/util/cleverMerge.js delete mode 100644 webpack-lib/lib/util/comparators.js delete mode 100644 webpack-lib/lib/util/compileBooleanMatcher.js delete mode 100644 webpack-lib/lib/util/concatenate.js delete mode 100644 webpack-lib/lib/util/conventions.js delete mode 100644 webpack-lib/lib/util/create-schema-validation.js delete mode 100644 webpack-lib/lib/util/createHash.js delete mode 100644 webpack-lib/lib/util/deprecation.js delete mode 100644 webpack-lib/lib/util/deterministicGrouping.js delete mode 100644 webpack-lib/lib/util/extractUrlAndGlobal.js delete mode 100644 webpack-lib/lib/util/findGraphRoots.js delete mode 100644 webpack-lib/lib/util/fs.js delete mode 100644 webpack-lib/lib/util/generateDebugId.js delete mode 100644 webpack-lib/lib/util/hash/BatchedHash.js delete mode 100644 webpack-lib/lib/util/hash/md4.js delete mode 100644 webpack-lib/lib/util/hash/wasm-hash.js delete mode 100644 webpack-lib/lib/util/hash/xxhash64.js delete mode 100644 webpack-lib/lib/util/identifier.js delete mode 100644 webpack-lib/lib/util/internalSerializables.js delete mode 100644 webpack-lib/lib/util/magicComment.js delete mode 100644 webpack-lib/lib/util/makeSerializable.js delete mode 100644 webpack-lib/lib/util/memoize.js delete mode 100644 webpack-lib/lib/util/nonNumericOnlyHash.js delete mode 100644 webpack-lib/lib/util/numberHash.js delete mode 100644 webpack-lib/lib/util/objectToMap.js delete mode 100644 webpack-lib/lib/util/processAsyncTree.js delete mode 100644 webpack-lib/lib/util/propertyAccess.js delete mode 100644 webpack-lib/lib/util/propertyName.js delete mode 100644 webpack-lib/lib/util/registerExternalSerializer.js delete mode 100644 webpack-lib/lib/util/runtime.js delete mode 100644 webpack-lib/lib/util/semver.js delete mode 100644 webpack-lib/lib/util/serialization.js delete mode 100644 webpack-lib/lib/util/smartGrouping.js delete mode 100644 webpack-lib/lib/util/source.js delete mode 100644 webpack-lib/lib/validateSchema.js delete mode 100644 webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js delete mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js delete mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js delete mode 100644 webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js delete mode 100644 webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js delete mode 100644 webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js delete mode 100644 webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyParser.js delete mode 100644 webpack-lib/lib/wasm-sync/WebAssemblyUtils.js delete mode 100644 webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js delete mode 100644 webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js delete mode 100644 webpack-lib/lib/web/FetchCompileWasmPlugin.js delete mode 100644 webpack-lib/lib/web/JsonpChunkLoadingPlugin.js delete mode 100644 webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/web/JsonpTemplatePlugin.js delete mode 100644 webpack-lib/lib/webpack.js delete mode 100644 webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js delete mode 100644 webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js delete mode 100644 webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js diff --git a/.gitignore b/.gitignore index cb335f13a5f..43681fa51bc 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,4 @@ packages/enhanced/test/js !apps/rslib-module/@mf-types/** **/vite.config.{js,ts,mjs,mts,cjs,cts}.timestamp* +/webpack-lib/ diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index 1399607d199..ee5fe6428f3 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -1,12 +1,18 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; import EmbedFederationRuntimeModule from './EmbedFederationRuntimeModule'; import FederationModulesPlugin from './FederationModulesPlugin'; -import type { Compiler, Chunk } from 'webpack'; +import type { Compiler, Chunk, Compilation } from 'webpack'; import { getFederationGlobalScope } from './utils'; import ContainerEntryDependency from '../ContainerEntryDependency'; import FederationRuntimeDependency from './FederationRuntimeDependency'; +import { + federationStartup, + generateEntryStartup, + generateESMEntryStartup, +} from '../../startup/StartupHelpers'; +import { ConcatSource } from 'webpack-sources'; -const { RuntimeGlobals, Compilation } = require( +const { RuntimeGlobals } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); @@ -22,6 +28,7 @@ interface EmbedFederationRuntimePluginOptions { class EmbedFederationRuntimePlugin { private readonly options: EmbedFederationRuntimePluginOptions; + private readonly processedChunks = new WeakMap(); constructor(options: EmbedFederationRuntimePluginOptions = {}) { this.options = { @@ -31,9 +38,101 @@ class EmbedFederationRuntimePlugin { } apply(compiler: Compiler): void { + // Check if plugin is already applied + const compilationTaps = compiler.hooks.thisCompilation.taps || []; + if ( + compilationTaps.find((tap) => tap.name === 'EmbedFederationRuntimePlugin') + ) { + return; + } + compiler.hooks.thisCompilation.tap( 'EmbedFederationRuntimePlugin', - (compilation: InstanceType) => { + (compilation: Compilation) => { + const { renderStartup } = + compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( + compilation, + ); + + // Check if renderStartup hook is already tapped + const startupTaps = renderStartup.taps || []; + if ( + startupTaps.find( + (tap) => tap.name === 'MfStartupChunkDependenciesPlugin', + ) + ) { + return; + } + + renderStartup.tap( + 'MfStartupChunkDependenciesPlugin', + (startupSource, lastInlinedModule, renderContext) => { + const { chunk, chunkGraph, runtimeTemplate } = renderContext; + + const isEnabledForChunk = (chunk: Chunk) => { + if (this.options.enableForAllChunks) return true; + if (chunk.id === 'build time chunk') return false; + return chunk.hasRuntime(); + }; + + if (!isEnabledForChunk(chunk)) { + return startupSource; + } + + const treeRuntimeRequirements = + chunkGraph.getTreeRuntimeRequirements(chunk); + const chunkRuntimeRequirements = + chunkGraph.getChunkRuntimeRequirements(chunk); + + const federation = + chunkRuntimeRequirements.has(federationGlobal) || + treeRuntimeRequirements.has(federationGlobal); + + if (!federation) { + return startupSource; + } + + // Check if we've already processed this chunk + if (this.processedChunks.get(chunk)) { + return startupSource; + } + + const entryModules = Array.from( + chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), + ); + + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { + // Mark chunk as processed + this.processedChunks.set(chunk, true); + return new ConcatSource( + startupSource, + `${RuntimeGlobals.startup}();\n`, + ); + } + + // Mark chunk as processed + this.processedChunks.set(chunk, true); + const entryGeneration = runtimeTemplate.outputOptions.module + ? generateESMEntryStartup + : generateEntryStartup; + + return new compiler.webpack.sources.ConcatSource( + entryGeneration( + compilation, + chunkGraph, + runtimeTemplate, + entryModules, + chunk, + false, + ), + ); + }, + ); + }, + ); + compiler.hooks.thisCompilation.tap( + 'EmbedFederationRuntimePlugin', + (compilation: Compilation) => { const hooks = FederationModulesPlugin.getCompilationHooks(compilation); const containerEntrySet: Set< ContainerEntryDependency | FederationRuntimeDependency @@ -56,6 +155,7 @@ class EmbedFederationRuntimePlugin { chunk: Chunk, runtimeRequirements: Set, ) => { + console.log(runtimeRequirements); if (!isEnabledForChunk(chunk)) { return; } @@ -65,7 +165,11 @@ class EmbedFederationRuntimePlugin { } runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); runtimeRequirements.add('embeddedFederationRuntime'); - + if (runtimeRequirements.has(RuntimeGlobals.startup)) { + // debugger; + } else { + // debugger + } const runtimeModule = new EmbedFederationRuntimeModule( containerEntrySet, ); @@ -75,6 +179,15 @@ class EmbedFederationRuntimePlugin { compilation.hooks.runtimeRequirementInTree .for(federationGlobal) .tap('EmbedFederationRuntimePlugin', handleRuntimeRequirements); + + // compilation.hooks.additionalTreeRuntimeRequirements.tap( + // 'EmbedFederationRuntimePlugin', + // (chunk, runtimeRequirements) => { + // if(!chunk.hasRuntime()) return; + // runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + // runtimeRequirements.add(RuntimeGlobals.startup); + // }, + // ); }, ); } diff --git a/webpack-lib/lib/APIPlugin.js b/webpack-lib/lib/APIPlugin.js deleted file mode 100644 index a36422ed250..00000000000 --- a/webpack-lib/lib/APIPlugin.js +++ /dev/null @@ -1,324 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const InitFragment = require("./InitFragment"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const WebpackError = require("./WebpackError"); -const ConstDependency = require("./dependencies/ConstDependency"); -const BasicEvaluatedExpression = require("./javascript/BasicEvaluatedExpression"); -const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); -const { - toConstantDependency, - evaluateToString -} = 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/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ - -/** - * @param {boolean | undefined} module true if ES module - * @param {string} importMetaName `import.meta` name - * @returns {Record} replacements - */ -function getReplacements(module, importMetaName) { - return { - __webpack_require__: { - 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: module - ? `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)` - : "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"; - -/** - * @typedef {object} APIPluginOptions - * @property {boolean} [module] the output filename - */ - -class APIPlugin { - /** - * @param {APIPluginOptions} [options] options - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const importMetaName = /** @type {string} */ ( - compilation.outputOptions.importMetaName - ); - const REPLACEMENTS = getReplacements( - this.options.module, - importMetaName - ); - - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - - 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 {BuildInfo} */ (module.buildInfo).needCreateRequire) { - const needPrefix = - renderContext.runtimeTemplate.supportNodePrefixForCoreModules(); - const chunkInitFragments = [ - new InitFragment( - `import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "${ - needPrefix ? "node:" : "" - }module";\n`, - InitFragment.STAGE_HARMONY_IMPORTS, - 0, - "external module node-commonjs" - ) - ]; - - renderContext.chunkInitFragments.push(...chunkInitFragments); - } - - return source; - } - ); - - /** - * @param {JavascriptParser} parser the parser - */ - const handler = parser => { - 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__" && this.options.module) { - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).needCreateRequire = true; - } - - 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 {BuildInfo} */ - (parser.state.module.buildInfo).moduleConcatenationBailout = - "__webpack_module__.id"; - const dep = new ConstDependency( - `${parser.state.module.moduleArgument}.id`, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.moduleId] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - - parser.hooks.expression - .for("__webpack_module__") - .tap(PLUGIN_NAME, expr => { - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).moduleConcatenationBailout = - "__webpack_module__"; - const dep = new ConstDependency( - parser.state.module.moduleArgument, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.module] - ); - 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_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 = APIPlugin; diff --git a/webpack-lib/lib/AbstractMethodError.js b/webpack-lib/lib/AbstractMethodError.js deleted file mode 100644 index 7a9d2f992b4..00000000000 --- a/webpack-lib/lib/AbstractMethodError.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const CURRENT_METHOD_REGEXP = /at ([a-zA-Z0-9_.]*)/; - -/** - * @param {string=} method method name - * @returns {string} message - */ -function createMessage(method) { - return `Abstract method${method ? ` ${method}` : ""}. Must be overridden.`; -} - -/** - * @constructor - */ -function Message() { - /** @type {string | undefined} */ - this.stack = undefined; - Error.captureStackTrace(this); - /** @type {RegExpMatchArray | null} */ - const match = - /** @type {string} */ - (/** @type {unknown} */ (this.stack)) - .split("\n")[3] - .match(CURRENT_METHOD_REGEXP); - - this.message = match && match[1] ? createMessage(match[1]) : createMessage(); -} - -/** - * Error for abstract method - * @example - * ```js - * class FooClass { - * abstractMethod() { - * throw new AbstractMethodError(); // error message: Abstract method FooClass.abstractMethod. Must be overridden. - * } - * } - * ``` - */ -class AbstractMethodError extends WebpackError { - constructor() { - super(new Message().message); - this.name = "AbstractMethodError"; - } -} - -module.exports = AbstractMethodError; diff --git a/webpack-lib/lib/AsyncDependenciesBlock.js b/webpack-lib/lib/AsyncDependenciesBlock.js deleted file mode 100644 index a5a346b9a21..00000000000 --- a/webpack-lib/lib/AsyncDependenciesBlock.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - 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("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./util/Hash")} Hash */ - -class AsyncDependenciesBlock extends DependenciesBlock { - /** - * @param {(ChunkGroupOptions & { entryOptions?: EntryOptions }) | 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 }; - } - this.groupOptions = groupOptions; - this.loc = loc; - this.request = request; - this._stringifiedGroupOptions = undefined; - } - - /** - * @returns {string | null | undefined} The name of the chunk - */ - get chunkName() { - return this.groupOptions.name; - } - - /** - * @param {string | undefined} value The new chunk name - * @returns {void} - */ - set chunkName(value) { - if (this.groupOptions.name !== value) { - this.groupOptions.name = value; - this._stringifiedGroupOptions = undefined; - } - } - - /** - * @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._stringifiedGroupOptions}${chunkGroup ? chunkGroup.id : ""}` - ); - super.updateHash(hash, context); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.groupOptions); - write(this.loc); - write(this.request); - super.serialize(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"); - -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/webpack-lib/lib/AsyncDependencyToInitialChunkError.js b/webpack-lib/lib/AsyncDependencyToInitialChunkError.js deleted file mode 100644 index 75888f869a3..00000000000 --- a/webpack-lib/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("./Dependency").DependencyLocation} DependencyLocation */ -/** @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 {DependencyLocation} 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; - } -} - -module.exports = AsyncDependencyToInitialChunkError; diff --git a/webpack-lib/lib/AutomaticPrefetchPlugin.js b/webpack-lib/lib/AutomaticPrefetchPlugin.js deleted file mode 100644 index 991ffc91732..00000000000 --- a/webpack-lib/lib/AutomaticPrefetchPlugin.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const NormalModule = require("./NormalModule"); -const PrefetchDependency = require("./dependencies/PrefetchDependency"); - -/** @typedef {import("./Compiler")} Compiler */ - -class AutomaticPrefetchPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "AutomaticPrefetchPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - PrefetchDependency, - normalModuleFactory - ); - } - ); - /** @type {{context: string | null, request: string}[] | null} */ - let lastModules = null; - compiler.hooks.afterCompile.tap("AutomaticPrefetchPlugin", compilation => { - lastModules = []; - - for (const m of compilation.modules) { - if (m instanceof NormalModule) { - lastModules.push({ - context: m.context, - request: m.request - }); - } - } - }); - compiler.hooks.make.tapAsync( - "AutomaticPrefetchPlugin", - (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/webpack-lib/lib/BannerPlugin.js b/webpack-lib/lib/BannerPlugin.js deleted file mode 100644 index e0e19a54ac1..00000000000 --- a/webpack-lib/lib/BannerPlugin.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - 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 createSchemaValidation = require("./util/create-schema-validation"); - -/** @typedef {import("../declarations/plugins/BannerPlugin").BannerFunction} BannerFunction */ -/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginArgument} BannerPluginArgument */ -/** @typedef {import("../declarations/plugins/BannerPlugin").BannerPluginOptions} BannerPluginOptions */ -/** @typedef {import("./Compilation").PathData} PathData */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ - -const validate = createSchemaValidation( - /** @type {(function(typeof import("../schemas/plugins/BannerPlugin.json")): boolean)} */ - (require("../schemas/plugins/BannerPlugin.check.js")), - () => require("../schemas/plugins/BannerPlugin.json"), - { - name: "Banner Plugin", - baseDataPath: "options" - } -); - -/** - * @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 * ") - .replace(/\s+\n/g, "\n") - .trimEnd()}\n */`; -}; - -class BannerPlugin { - /** - * @param {BannerPluginArgument} options options object - */ - constructor(options) { - if (typeof options === "string" || typeof options === "function") { - options = { - banner: options - }; - } - - validate(options); - - this.options = options; - - const bannerOption = options.banner; - if (typeof bannerOption === "function") { - const getBanner = bannerOption; - /** @type {BannerFunction} */ - this.banner = this.options.raw - ? getBanner - : /** @type {BannerFunction} */ data => wrapComment(getBanner(data)); - } else { - const banner = this.options.raw - ? bannerOption - : wrapComment(bannerOption); - /** @type {BannerFunction} */ - this.banner = () => banner; - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - const banner = this.banner; - const matchObject = ModuleFilenameHelpers.matchObject.bind( - undefined, - options - ); - const cache = new WeakMap(); - const stage = - this.options.stage || Compilation.PROCESS_ASSETS_STAGE_ADDITIONS; - - compiler.hooks.compilation.tap("BannerPlugin", compilation => { - compilation.hooks.processAssets.tap( - { - name: "BannerPlugin", - stage - }, - () => { - for (const chunk of compilation.chunks) { - if (options.entryOnly && !chunk.canBeInitial()) { - continue; - } - - for (const file of chunk.files) { - if (!matchObject(file)) { - continue; - } - - /** @type {PathData} */ - const data = { chunk, filename: file }; - - const comment = compilation.getPath( - /** @type {TemplatePath} */ - (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; - }); - } - } - } - ); - }); - } -} - -module.exports = BannerPlugin; diff --git a/webpack-lib/lib/Cache.js b/webpack-lib/lib/Cache.js deleted file mode 100644 index 055ad6d225a..00000000000 --- a/webpack-lib/lib/Cache.js +++ /dev/null @@ -1,165 +0,0 @@ -/* - 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("./HookWebpackError"); - -/** @typedef {import("./WebpackError")} WebpackError */ - -/** - * @typedef {object} Etag - * @property {function(): string} toString - */ - -/** - * @template T - * @callback CallbackCache - * @param {WebpackError | null} err - * @param {T=} result - * @returns {void} - */ - -/** - * @callback GotHandler - * @param {any} result - * @param {function(Error=): void} callback - * @returns {void} - */ - -/** - * @param {number} times times - * @param {function(Error=): void} callback callback - * @returns {function(Error=): void} callback - */ -const needCalls = (times, callback) => err => { - if (--times === 0) { - return callback(err); - } - if (err && times > 0) { - times = 0; - return callback(err); - } -}; - -class Cache { - constructor() { - this.hooks = { - /** @type {AsyncSeriesBailHook<[string, Etag | null, GotHandler[]], any>} */ - get: new AsyncSeriesBailHook(["identifier", "etag", "gotHandlers"]), - /** @type {AsyncParallelHook<[string, Etag | null, any]>} */ - 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([]) - }; - } - - /** - * @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); - } - }); - } - - /** - * @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") - ); - } - - /** - * After this method has succeeded the cache can only be restored when build dependencies are - * @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") - ); - } - - /** - * @returns {void} - */ - beginIdle() { - this.hooks.beginIdle.call(); - } - - /** - * @param {CallbackCache} callback signals when the call finishes - * @returns {void} - */ - endIdle(callback) { - this.hooks.endIdle.callAsync( - makeWebpackErrorCallback(callback, "Cache.hooks.endIdle") - ); - } - - /** - * @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/webpack-lib/lib/CacheFacade.js b/webpack-lib/lib/CacheFacade.js deleted file mode 100644 index eece9631735..00000000000 --- a/webpack-lib/lib/CacheFacade.js +++ /dev/null @@ -1,349 +0,0 @@ -/* - 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("./WebpackError")} WebpackError */ -/** @typedef {import("./cache/getLazyHashedEtag").HashableObject} HashableObject */ -/** @typedef {typeof import("./util/Hash")} HashConstructor */ - -/** - * @template T - * @callback CallbackCache - * @param {(Error | null)=} err - * @param {(T | null)=} result - * @returns {void} - */ - -/** - * @template T - * @callback CallbackNormalErrorCache - * @param {(Error | null)=} err - * @param {T=} result - * @returns {void} - */ - -class MultiItemCache { - /** - * @param {ItemCacheFacade[]} items item caches - */ - constructor(items) { - this._items = items; - // eslint-disable-next-line no-constructor-return - if (items.length === 1) return /** @type {any} */ (items[0]); - } - - /** - * @template T - * @param {CallbackCache} callback signals when the value is retrieved - * @returns {void} - */ - get(callback) { - forEachBail(this._items, (item, callback) => item.get(callback), callback); - } - - /** - * @template T - * @returns {Promise} promise with the data - */ - getPromise() { - /** - * @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); - } - - /** - * @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 - ); - } - - /** - * @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 { - /** - * @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; - } - - /** - * @template T - * @param {CallbackCache} callback signals when the value is retrieved - * @returns {void} - */ - get(callback) { - this._cache.get(this._name, this._etag, callback); - } - - /** - * @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); - } - }); - }); - } - - /** - * @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); - } - - /** - * @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(); - } - }); - }); - } - - /** - * @template T - * @param {function(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); - }); - }); - }); - } - - /** - * @template T - * @param {function(): 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 { - /** - * @param {Cache} cache the root cache - * @param {string} name the child cache name - * @param {(string | HashConstructor)=} hashFunction the hash function to use - */ - constructor(cache, name, hashFunction) { - this._cache = cache; - this._name = name; - this._hashFunction = hashFunction; - } - - /** - * @param {string} name the child cache name# - * @returns {CacheFacade} child cache - */ - getChildCache(name) { - return new CacheFacade( - this._cache, - `${this._name}|${name}`, - this._hashFunction - ); - } - - /** - * @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 - ); - } - - /** - * @param {HashableObject} obj an hashable object - * @returns {Etag} an etag that is lazy hashed - */ - getLazyHashedEtag(obj) { - return getLazyHashedEtag(obj, this._hashFunction); - } - - /** - * @param {Etag} a an etag - * @param {Etag} b another etag - * @returns {Etag} an etag that represents both - */ - mergeEtags(a, b) { - return mergeEtags(a, b); - } - - /** - * @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); - } - - /** - * @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); - } - }); - }); - } - - /** - * @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); - } - - /** - * @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(); - } - }); - }); - } - - /** - * @template T - * @param {string} identifier the cache identifier - * @param {Etag | null} etag the etag - * @param {function(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); - }); - }); - }); - } - - /** - * @template T - * @param {string} identifier the cache identifier - * @param {Etag | null} etag the etag - * @param {function(): 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/webpack-lib/lib/CaseSensitiveModulesWarning.js b/webpack-lib/lib/CaseSensitiveModulesWarning.js deleted file mode 100644 index 58a38e5506e..00000000000 --- a/webpack-lib/lib/CaseSensitiveModulesWarning.js +++ /dev/null @@ -1,71 +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 */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ - -/** - * @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; - }); - -/** - * @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 = Array.from( - 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"); - -class CaseSensitiveModulesWarning extends WebpackError { - /** - * Creates an instance of CaseSensitiveModulesWarning. - * @param {Iterable} modules modules that were detected - * @param {ModuleGraph} moduleGraph the module graph - */ - constructor(modules, moduleGraph) { - const sortedModules = sortModules(Array.from(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}`); - - this.name = "CaseSensitiveModulesWarning"; - this.module = sortedModules[0]; - } -} - -module.exports = CaseSensitiveModulesWarning; diff --git a/webpack-lib/lib/Chunk.js b/webpack-lib/lib/Chunk.js deleted file mode 100644 index 3da64be3981..00000000000 --- a/webpack-lib/lib/Chunk.js +++ /dev/null @@ -1,874 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ChunkGraph = require("./ChunkGraph"); -const Entrypoint = require("./Entrypoint"); -const { intersect } = require("./util/SetHelpers"); -const SortableSet = require("./util/SortableSet"); -const StringXor = require("./util/StringXor"); -const { - compareModulesByIdentifier, - compareChunkGroupsByIndex, - compareModulesById -} = require("./util/comparators"); -const { createArrayToSetDeprecationSet } = require("./util/deprecation"); -const { mergeRuntime } = require("./util/runtime"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @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("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("./util/Hash")} Hash */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** @typedef {number | string} ChunkId */ - -const ChunkFilesSet = createArrayToSetDeprecationSet("chunk.files"); - -/** - * @typedef {object} WithId an object who has an id property * - * @property {string | number} id the id of the object - */ - -/** - * @deprecated - * @typedef {object} ChunkMaps - * @property {Record} hash - * @property {Record>} contentHash - * @property {Record} name - */ - -/** - * @deprecated - * @typedef {object} ChunkModuleMaps - * @property {Record} id - * @property {Record} hash - */ - -let debugId = 1000; - -/** - * A Chunk is a unit of encapsulation for Modules. - * Chunks are "rendered" into bundles that get emitted when the build completes. - */ -class Chunk { - /** - * @param {string=} name of chunk being created, is optional (for subclasses) - * @param {boolean} backCompat enable backward-compatibility - */ - constructor(name, backCompat = true) { - /** @type {ChunkId | null} */ - this.id = null; - /** @type {ChunkId[] | null} */ - this.ids = null; - /** @type {number} */ - this.debugId = debugId++; - /** @type {string | undefined} */ - this.name = name; - /** @type {SortableSet} */ - this.idNameHints = new SortableSet(); - /** @type {boolean} */ - this.preventIntegration = false; - /** @type {TemplatePath | undefined} */ - this.filenameTemplate = undefined; - /** @type {TemplatePath | undefined} */ - this.cssFilenameTemplate = undefined; - /** - * @private - * @type {SortableSet} - */ - 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 {Record} */ - this.contentHash = Object.create(null); - /** @type {string=} */ - this.renderedHash = undefined; - /** @type {string=} */ - this.chunkReason = undefined; - /** @type {boolean} */ - this.extraAsync = false; - } - - // TODO remove in webpack 6 - // BACKWARD-COMPAT START - get entryModule() { - const entryModules = Array.from( - 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)" - ); - } - - /** - * @returns {boolean} true, if the chunk contains an entry module - */ - hasEntryModule() { - return ( - ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.hasEntryModule", - "DEP_WEBPACK_CHUNK_HAS_ENTRY_MODULE" - ).getNumberOfEntryModules(this) > 0 - ); - } - - /** - * @param {Module} module the module - * @returns {boolean} true, if the chunk could be added - */ - 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; - } - - /** - * @param {Module} module the module - * @returns {void} - */ - removeModule(module) { - ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.removeModule", - "DEP_WEBPACK_CHUNK_REMOVE_MODULE" - ).disconnectChunkAndModule(this, module); - } - - /** - * @returns {number} the number of module which are contained in this chunk - */ - getNumberOfModules() { - return ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.getNumberOfModules", - "DEP_WEBPACK_CHUNK_GET_NUMBER_OF_MODULES" - ).getNumberOfChunkModules(this); - } - - get modulesIterable() { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.modulesIterable", - "DEP_WEBPACK_CHUNK_MODULES_ITERABLE" - ); - return chunkGraph.getOrderedChunkModulesIterable( - this, - compareModulesByIdentifier - ); - } - - /** - * @param {Chunk} otherChunk the chunk to compare with - * @returns {-1|0|1} the comparison result - */ - compareTo(otherChunk) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.compareTo", - "DEP_WEBPACK_CHUNK_COMPARE_TO" - ); - return chunkGraph.compareChunks(this, otherChunk); - } - - /** - * @param {Module} module the module - * @returns {boolean} true, if the chunk contains the module - */ - containsModule(module) { - return ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.containsModule", - "DEP_WEBPACK_CHUNK_CONTAINS_MODULE" - ).isModuleInChunk(module, this); - } - - /** - * @returns {Module[]} the modules for this chunk - */ - getModules() { - return ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.getModules", - "DEP_WEBPACK_CHUNK_GET_MODULES" - ).getChunkModules(this); - } - - /** - * @returns {void} - */ - remove() { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.remove", - "DEP_WEBPACK_CHUNK_REMOVE" - ); - chunkGraph.disconnectChunk(this); - this.disconnectFromGroups(); - } - - /** - * @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); - } - - /** - * @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 {Chunk} otherChunk the other chunk - * @returns {boolean} true, if chunks could be integrated - */ - canBeIntegrated(otherChunk) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.canBeIntegrated", - "DEP_WEBPACK_CHUNK_CAN_BE_INTEGRATED" - ); - return chunkGraph.canChunksBeIntegrated(this, otherChunk); - } - - /** - * @returns {boolean} true, if this chunk contains no module - */ - isEmpty() { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.isEmpty", - "DEP_WEBPACK_CHUNK_IS_EMPTY" - ); - return chunkGraph.getNumberOfChunkModules(this) === 0; - } - - /** - * @returns {number} total size of all modules in this chunk - */ - modulesSize() { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.modulesSize", - "DEP_WEBPACK_CHUNK_MODULES_SIZE" - ); - return chunkGraph.getChunkModulesSize(this); - } - - /** - * @param {ChunkSizeOptions} options options object - * @returns {number} total size of this chunk - */ - size(options = {}) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.size", - "DEP_WEBPACK_CHUNK_SIZE" - ); - return chunkGraph.getChunkSize(this, options); - } - - /** - * @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 - */ - integratedSize(otherChunk, options) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.integratedSize", - "DEP_WEBPACK_CHUNK_INTEGRATED_SIZE" - ); - return chunkGraph.getIntegratedChunksSize(this, otherChunk, options); - } - - /** - * @param {ModuleFilterPredicate} filterFn function used to filter modules - * @returns {ChunkModuleMaps} module map information - */ - getChunkModuleMaps(filterFn) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.getChunkModuleMaps", - "DEP_WEBPACK_CHUNK_GET_CHUNK_MODULE_MAPS" - ); - /** @type {Record} */ - const chunkModuleIdMap = Object.create(null); - /** @type {Record} */ - 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 - }; - } - - /** - * @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(filterFn, filterChunkFn) { - const chunkGraph = ChunkGraph.getChunkGraphForChunk( - this, - "Chunk.hasModuleInGraph", - "DEP_WEBPACK_CHUNK_HAS_MODULE_IN_GRAPH" - ); - return chunkGraph.hasModuleInGraph(this, filterFn, filterChunkFn); - } - - /** - * @deprecated - * @param {boolean} realHash whether the full hash or the rendered hash is to be used - * @returns {ChunkMaps} the chunk map information - */ - 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 - - /** - * @returns {boolean} whether or not the Chunk will have a runtime - */ - hasRuntime() { - for (const chunkGroup of this._groups) { - if ( - chunkGroup instanceof Entrypoint && - chunkGroup.getRuntimeChunk() === this - ) { - return true; - } - } - return false; - } - - /** - * @returns {boolean} whether or not this chunk can be an initial chunk - */ - canBeInitial() { - for (const chunkGroup of this._groups) { - if (chunkGroup.isInitial()) return true; - } - return false; - } - - /** - * @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; - } - - /** - * @returns {EntryOptions | undefined} the entry options for this chunk - */ - getEntryOptions() { - for (const chunkGroup of this._groups) { - if (chunkGroup instanceof Entrypoint) { - return chunkGroup.options; - } - } - return undefined; - } - - /** - * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being added - * @returns {void} - */ - addGroup(chunkGroup) { - this._groups.add(chunkGroup); - } - - /** - * @param {ChunkGroup} chunkGroup the chunkGroup the chunk is being removed from - * @returns {void} - */ - removeGroup(chunkGroup) { - this._groups.delete(chunkGroup); - } - - /** - * @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); - } - - /** - * @returns {number} the amount of groups that the said chunk is in - */ - getNumberOfGroups() { - return this._groups.size; - } - - /** - * @returns {SortableSet} the chunkGroups that the said chunk is referenced in - */ - get groupsIterable() { - this._groups.sort(); - return this._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 - * @returns {void} - */ - split(newChunk) { - for (const chunkGroup of this._groups) { - chunkGroup.insertChunk(newChunk, this); - newChunk.addGroup(chunkGroup); - } - for (const idHint of this.idNameHints) { - newChunk.idNameHints.add(idHint); - } - newChunk.runtime = mergeRuntime(newChunk.runtime, this.runtime); - } - - /** - * @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 - }` - ); - } - } - - /** - * @returns {Set} a set of all the async chunks - */ - getAllAsyncChunks() { - const queue = new Set(); - const chunks = new Set(); - - const initialChunks = intersect( - Array.from(this.groupsIterable, g => new Set(g.chunks)) - ); - - 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); - } - } - } - - 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 chunks; - } - - /** - * @returns {Set} a set of all the initial chunks (including itself) - */ - getAllInitialChunks() { - const chunks = new Set(); - 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 {Set} a set of all the referenced chunks (including itself) - */ - getAllReferencedChunks() { - const queue = new Set(this.groupsIterable); - 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; - } - - /** - * @returns {Set} a set of all the referenced entrypoints - */ - getAllReferencedAsyncEntrypoints() { - const queue = new Set(this.groupsIterable); - const entrypoints = new Set(); - - for (const chunkGroup of queue) { - for (const entrypoint of chunkGroup.asyncEntrypointsIterable) { - entrypoints.add(entrypoint); - } - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - - return entrypoints; - } - - /** - * @returns {boolean} true, if the chunk references async chunks - */ - hasAsyncChunks() { - const queue = new Set(); - - const initialChunks = intersect( - Array.from(this.groupsIterable, g => new Set(g.chunks)) - ); - - for (const chunkGroup of this.groupsIterable) { - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - - for (const chunkGroup of queue) { - for (const chunk of chunkGroup.chunks) { - if (!initialChunks.has(chunk)) { - return true; - } - } - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - - return false; - } - - /** - * @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) { - for (const key of Object.keys(childGroup.options)) { - if (key.endsWith("Order")) { - 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: - /** @type {number} */ - ( - childGroup.options[ - /** @type {keyof ChunkGroupOptions} */ (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; - return a.group.compareTo(chunkGraph, b.group); - }); - /** @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] = Array.from(chunkIdSet); - } - } - return result; - } - - /** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {string} type option name - * @returns {{ onChunks: Chunk[], chunks: Set }[] | undefined} referenced chunks for a specific type - */ - getChildrenOfTypeInOrder(chunkGraph, type) { - const list = []; - for (const group of this.groupsIterable) { - for (const childGroup of group.childrenIterable) { - const order = - childGroup.options[/** @type {keyof ChunkGroupOptions} */ (type)]; - if (order === undefined) continue; - list.push({ - order, - group, - childGroup - }); - } - } - if (list.length === 0) return; - list.sort((a, b) => { - const cmp = - /** @type {number} */ (b.order) - /** @type {number} */ (a.order); - if (cmp !== 0) return cmp; - return a.group.compareTo(chunkGraph, b.group); - }); - const result = []; - 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; - } - - /** - * @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 {Record>} a record object of names to lists of child ids(?) by chunk id - */ - getChildIdsByOrdersMap(chunkGraph, includeDirectChildren, filterFn) { - /** @type {Record>} */ - const chunkMaps = Object.create(null); - - /** - * @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[/** @type {ChunkId} */ (chunk.id)] = data[key]; - } - }; - - if (includeDirectChildren) { - /** @type {Set} */ - 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()) { - addChildIdsByOrdersToMap(chunk); - } - - return chunkMaps; - } - - /** - * @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 - */ - hasChildByOrder(chunkGraph, type, includeDirectChildren, filterFn) { - if (includeDirectChildren) { - /** @type {Set} */ - const chunks = new Set(); - for (const chunkGroup of this.groupsIterable) { - for (const chunk of chunkGroup.chunks) { - chunks.add(chunk); - } - } - for (const chunk of chunks) { - const data = chunk.getChildIdsByOrders(chunkGraph, filterFn); - if (data[type] !== undefined) return true; - } - } - - for (const chunk of this.getAllAsyncChunks()) { - const data = chunk.getChildIdsByOrders(chunkGraph, filterFn); - if (data[type] !== undefined) return true; - } - - return false; - } -} - -module.exports = Chunk; diff --git a/webpack-lib/lib/ChunkGraph.js b/webpack-lib/lib/ChunkGraph.js deleted file mode 100644 index d13e8afe5c9..00000000000 --- a/webpack-lib/lib/ChunkGraph.js +++ /dev/null @@ -1,1868 +0,0 @@ -/* - 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 { first } = require("./util/SetHelpers"); -const SortableSet = require("./util/SortableSet"); -const { - compareModulesById, - compareIterables, - compareModulesByIdentifier, - concatComparators, - compareSelect, - compareIds -} = require("./util/comparators"); -const createHash = require("./util/createHash"); -const findGraphRoots = require("./util/findGraphRoots"); -const { - RuntimeSpecMap, - RuntimeSpecSet, - runtimeToString, - mergeRuntime, - forEachRuntime -} = require("./util/runtime"); - -/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Chunk").ChunkId} ChunkId */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("./RuntimeModule")} RuntimeModule */ -/** @typedef {typeof import("./util/Hash")} Hash */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** @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 */ - -/** - * @typedef {object} ChunkSizeOptions - * @property {number=} chunkOverhead constant overhead for a chunk - * @property {number=} entryChunkMultiplicator multiplicator for initial chunks - */ - -class ModuleHashInfo { - /** - * @param {string} hash hash - * @param {string} renderedHash rendered hash - */ - constructor(hash, renderedHash) { - this.hash = hash; - this.renderedHash = renderedHash; - } -} - -/** @template T @typedef {(set: SortableSet) => T[]} SetToArrayFunction */ - -/** - * @template T - * @param {SortableSet} set the set - * @returns {T[]} set as array - */ -const getArray = set => Array.from(set); - -/** - * @param {SortableSet} chunks the chunks - * @returns {RuntimeSpecSet} runtimes - */ -const getModuleRuntimes = chunks => { - const runtimes = new RuntimeSpecSet(); - for (const chunk of chunks) { - runtimes.add(chunk.runtime); - } - return runtimes; -}; - -/** - * @param {WeakMap> | undefined} sourceTypesByModule sourceTypesByModule - * @returns {function (SortableSet): Map>} modules by source type - */ -const modulesBySourceType = sourceTypesByModule => set => { - /** @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) { - 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; -}; -const defaultModulesBySourceType = modulesBySourceType(undefined); - -/** - * @template T - * @type {WeakMap} - */ -const createOrderedArrayFunctionMap = new WeakMap(); - -/** - * @template T - * @param {function(T, T): -1|0|1} comparator comparator function - * @returns {SetToArrayFunction} set as ordered array - */ -const createOrderedArrayFunction = comparator => { - /** @type {SetToArrayFunction} */ - let fn = createOrderedArrayFunctionMap.get(comparator); - if (fn !== undefined) return fn; - fn = set => { - set.sortWith(comparator); - return Array.from(set); - }; - createOrderedArrayFunctionMap.set(comparator, fn); - return fn; -}; - -/** - * @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; -}; - -/** - * @param {Iterable} modules the sortable Set to get the size of - * @returns {Record} the sizes of the modules - */ -const getModulesSizes = modules => { - 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; -}; - -/** - * @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 {Set} EntryInChunks */ -/** @typedef {Set} RuntimeInChunks */ -/** @typedef {string | number} ModuleId */ - -class ChunkGraphModule { - constructor() { - /** @type {SortableSet} */ - 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 {RuntimeSpecMap> | undefined} */ - this.runtimeRequirements = undefined; - /** @type {RuntimeSpecMap | undefined} */ - this.graphHashes = undefined; - /** @type {RuntimeSpecMap | undefined} */ - this.graphHashesWithConnections = undefined; - } -} - -class ChunkGraphChunk { - constructor() { - /** @type {SortableSet} */ - this.modules = new SortableSet(); - /** @type {WeakMap> | undefined} */ - this.sourceTypesByModule = undefined; - /** @type {Map} */ - this.entryModules = new Map(); - /** @type {SortableSet} */ - this.runtimeModules = new SortableSet(); - /** @type {Set | undefined} */ - this.fullHashModules = undefined; - /** @type {Set | undefined} */ - this.dependentHashModules = undefined; - /** @type {Set | undefined} */ - this.runtimeRequirements = undefined; - /** @type {Set} */ - this.runtimeRequirementsInTree = new Set(); - - this._modulesBySourceType = defaultModulesBySourceType; - } -} - -class ChunkGraph { - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {string | Hash} hashFunction the hash function to use - */ - constructor(moduleGraph, hashFunction = "md4") { - /** - * @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(); - /** @type {ModuleGraph} */ - this.moduleGraph = moduleGraph; - - this._hashFunction = hashFunction; - - this._getGraphRoots = this._getGraphRoots.bind(this); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {SortableSet} set the sortable Set to get the roots of - * @returns {Module[]} the graph roots - */ - _getGraphRoots(set) { - const { moduleGraph } = this; - return Array.from( - findGraphRoots(set, module => { - /** @type {Set} */ - const set = new Set(); - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - } - - /** - * @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); - } - } - - /** - * @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); - } - } - - /** - * @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); - } - } - - /** - * @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 {Map} */ - 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; - } - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {Iterable} iterable of chunks (do not modify) - */ - getModuleChunksIterable(module) { - const cgm = this._getChunkGraphModule(module); - return cgm.chunks; - } - - /** - * @param {Module} module the module - * @param {function(Chunk, 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; - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {RuntimeSpecSet} runtimes - */ - getModuleRuntimes(module) { - const cgm = this._getChunkGraphModule(module); - return cgm.chunks.getFromUnorderedCache(getModuleRuntimes); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable} return the modules for this chunk - */ - getChunkModulesIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.modules; - } - - /** - * @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; - } - - /** - * @param {Chunk} chunk chunk - * @param {Module} module chunk module - * @param {Set} 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); - } - - /** - * @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(); - } - - /** - * @param {Module} module module - * @returns {SourceTypes} source types - */ - getModuleSourceTypes(module) { - return ( - this._getOverwrittenModuleSourceTypes(module) || module.getSourceTypes() - ); - } - - /** - * @param {Module} module module - * @returns {Set | undefined} source types - */ - _getOverwrittenModuleSourceTypes(module) { - let newSet = false; - 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 = st; - continue; - } 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; - } - - /** - * @param {Chunk} chunk the chunk - * @param {function(Module, Module): -1|0|1} 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; - } - - /** - * @param {Chunk} chunk the chunk - * @param {string} sourceType source type - * @param {function(Module, Module): -1|0|1} 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; - } - - /** - * @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); - } - - /** - * @param {Chunk} chunk the chunk - * @param {function(Module, Module): -1|0|1} 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); - } - - /** - * @param {Chunk} chunk the chunk - * @param {ModuleFilterPredicate} filterFn function used to filter modules - * @param {boolean} includeAllChunks all chunks or only async chunks - * @returns {Record} chunk to module ids object - */ - getChunkModuleIdMap(chunk, filterFn, includeAllChunks = false) { - /** @type {Record} */ - const chunkModuleIdMap = Object.create(null); - - for (const asyncChunk of includeAllChunks - ? chunk.getAllReferencedChunks() - : chunk.getAllAsyncChunks()) { - /** @type {(string | number)[] | 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; - } - - /** - * @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 {Record>} chunk to module id to module hash object - */ - getChunkModuleRenderedHashMap( - chunk, - filterFn, - hashLength = 0, - includeAllChunks = false - ) { - /** @type {Record>} */ - const chunkModuleHashMap = Object.create(null); - - /** @typedef {Record} IdToHashMap */ - - 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) { - 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; - } - - /** - * @param {Chunk} chunk the chunk - * @param {ChunkFilterPredicate} filterFn function used to filter chunks - * @returns {Record} chunk map - */ - getChunkConditionMap(chunk, filterFn) { - const map = Object.create(null); - for (const c of chunk.getAllReferencedChunks()) { - map[/** @type {ChunkId} */ (c.id)] = filterFn(c, this); - } - return map; - } - - /** - * @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); - 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; - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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) - ); - } - - /** - * @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) - ); - } - - /** - * @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; - } - - /** - * @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 Array.from( - 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); - } - - /** - * @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; - } - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @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(); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable} iterable of modules (do not modify) - */ - getChunkEntryModulesIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.entryModules.keys(); - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable} iterable of chunks - */ - getChunkEntryDependentChunksIterable(chunk) { - /** @type {Set} */ - 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; - } - - /** - * @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; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable} iterable of modules (do not modify) - */ - getChunkRuntimeModulesIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.runtimeModules; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {RuntimeModule[]} array of modules in order of execution - */ - getChunkRuntimeModulesInOrder(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - const array = Array.from(cgc.runtimeModules); - array.sort( - concatComparators( - compareSelect(r => /** @type {RuntimeModule} */ (r).stage, compareIds), - compareModulesByIdentifier - ) - ); - return array; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable | undefined} iterable of modules (do not modify) - */ - getChunkFullHashModulesIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.fullHashModules; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {ReadonlySet | undefined} set of modules (do not modify) - */ - getChunkFullHashModulesSet(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.fullHashModules; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable | undefined} iterable of modules (do not modify) - */ - getChunkDependentHashModulesIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.dependentHashModules; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {Iterable} iterable of modules (do not modify) - */ - getChunkEntryModulesWithChunkGroupIterable(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.entryModules; - } - - /** - * @param {AsyncDependenciesBlock} depBlock the async block - * @returns {ChunkGroup | undefined} the chunk group - */ - getBlockChunkGroup(depBlock) { - return this._blockChunkGroups.get(depBlock); - } - - /** - * @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); - } - - /** - * @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(); - } - - /** - * @param {Module} module the module - * @returns {ModuleId | null} the id of the module - */ - getModuleId(module) { - const cgm = this._getChunkGraphModule(module); - return cgm.id; - } - - /** - * @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; - } - - /** - * @param {string} runtime runtime - * @returns {string | number} the id of the runtime - */ - getRuntimeId(runtime) { - return /** @type {string | number} */ (this._runtimeIds.get(runtime)); - } - - /** - * @param {string} runtime runtime - * @param {string | number} id the id of the runtime - * @returns {void} - */ - setRuntimeId(runtime, id) { - this._runtimeIds.set(runtime, id); - } - - /** - * @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; - } - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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)); - } - - /** - * @param {Module} module the module - * @param {RuntimeSpec} runtime the runtime - * @param {Set} 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 (runtimeRequirementsMap === undefined) { - const map = new RuntimeSpecMap(); - // TODO avoid cloning item and track ownership instead - map.set(runtime, transferOwnership ? items : new Set(items)); - cgm.runtimeRequirements = map; - return; - } - runtimeRequirementsMap.update(runtime, runtimeRequirements => { - if (runtimeRequirements === undefined) { - return transferOwnership ? items : new Set(items); - } else if (!transferOwnership || runtimeRequirements.size >= items.size) { - for (const item of items) runtimeRequirements.add(item); - return runtimeRequirements; - } - - for (const item of runtimeRequirements) items.add(item); - return items; - }); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Set} 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; - } - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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${/** @type {string} */ (hash.digest("hex"))}`); - }); - return graphHash; - } - - /** - * @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(); - } - - /** - * @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 && 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(); - /** - * @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) { - 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(state); - } - processConnection(connection, stateInfo); - } - } - // cspell:word Tnamespace - if (activeNamespaceModules.size === 0 && connectedModules.size === 0) - return graphHash; - const connectedModulesInOrder = - connectedModules.size > 1 - ? Array.from(connectedModules).sort(([a], [b]) => (a < b ? -1 : 1)) - : connectedModules; - const hash = createHash(this._hashFunction); - /** - * @param {Module} module module - */ - const addModuleToHash = module => { - hash.update( - this._getModuleGraphHashBigInt( - this._getChunkGraphModule(module), - module, - runtime - ).toString(16) - ); - }; - /** - * @param {Set} modules modules - */ - const addModulesToHash = modules => { - let xor = ZERO_BIG_INT; - for (const m of modules) { - xor = - 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 /** @type {string} */ (hash.digest("hex")); - }); - } - - /** - * @param {Chunk} chunk the chunk - * @returns {ReadOnlyRuntimeRequirements} runtime requirements - */ - getTreeRuntimeRequirements(chunk) { - const cgc = this._getChunkGraphChunk(chunk); - return cgc.runtimeRequirementsInTree; - } - - // TODO remove in webpack 6 - /** - * @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( - /** - * @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 - /** - * @param {Module} module the module - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {void} - */ - static setChunkGraphForModule(module, chunkGraph) { - chunkGraphForModuleMap.set(module, chunkGraph); - } - - // TODO remove in webpack 6 - /** - * @param {Module} module the module - * @returns {void} - */ - static clearChunkGraphForModule(module) { - chunkGraphForModuleMap.delete(module); - } - - // TODO remove in webpack 6 - /** - * @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( - /** - * @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); - } - - // TODO remove in webpack 6 - /** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {void} - */ - static setChunkGraphForChunk(chunk, chunkGraph) { - chunkGraphForChunkMap.set(chunk, chunkGraph); - } - - // TODO remove in webpack 6 - /** - * @param {Chunk} chunk the chunk - * @returns {void} - */ - static clearChunkGraphForChunk(chunk) { - chunkGraphForChunkMap.delete(chunk); - } -} - -// 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/webpack-lib/lib/ChunkGroup.js b/webpack-lib/lib/ChunkGroup.js deleted file mode 100644 index 2fcb71d1d9b..00000000000 --- a/webpack-lib/lib/ChunkGroup.js +++ /dev/null @@ -1,604 +0,0 @@ -/* - 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, - compareChunks, - compareIterables -} = 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("./ModuleGraph")} ModuleGraph */ - -/** @typedef {{id: number}} HasId */ -/** @typedef {{module: Module | null, loc: DependencyLocation, request: string}} OriginRecord */ - -/** - * @typedef {object} RawChunkGroupOptions - * @property {number=} preloadOrder - * @property {number=} prefetchOrder - * @property {("low" | "high" | "auto")=} fetchPriority - */ - -/** @typedef {RawChunkGroupOptions & { name?: string | null }} ChunkGroupOptions */ - -let debugId = 5000; - -/** - * @template T - * @param {SortableSet} set set to convert to array. - * @returns {T[]} the array format of existing set - */ -const getArray = set => Array.from(set); - -/** - * A convenience method used to sort chunks based on their id's - * @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; - if (b.id < a.id) return 1; - return 0; -}; - -/** - * @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 - */ -const sortOrigin = (a, b) => { - const aIdent = a.module ? a.module.identifier() : ""; - const bIdent = b.module ? b.module.identifier() : ""; - if (aIdent < bIdent) return -1; - if (aIdent > bIdent) return 1; - return compareLocations(a.loc, b.loc); -}; - -class ChunkGroup { - /** - * Creates an instance of ChunkGroup. - * @param {string | ChunkGroupOptions=} options chunk group options passed to chunkGroup - */ - constructor(options) { - if (typeof options === "string") { - options = { name: options }; - } else if (!options) { - options = { name: undefined }; - } - /** @type {number} */ - this.groupDebugId = debugId++; - this.options = /** @type {ChunkGroupOptions} */ (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); - this._blocks = new SortableSet(); - /** @type {Chunk[]} */ - this.chunks = []; - /** @type {OriginRecord[]} */ - this.origins = []; - /** Indices in top-down order */ - /** - * @private - * @type {Map} - */ - this._modulePreOrderIndices = new Map(); - /** Indices in bottom-up order */ - /** - * @private - * @type {Map} - */ - this._modulePostOrderIndices = new Map(); - /** @type {number | undefined} */ - this.index = undefined; - } - - /** - * when a new chunk is added to a chunkGroup, addingOptions will occur. - * @param {ChunkGroupOptions} options the chunkGroup options passed to addOptions - * @returns {void} - */ - addOptions(options) { - for (const _key of Object.keys(options)) { - const key = /** @type {keyof ChunkGroupOptions} */ (_key); - if (this.options[key] === undefined) { - /** @type {TODO} */ - (this.options)[key] = options[key]; - } else if (this.options[key] !== options[key]) { - if (key.endsWith("Order")) { - /** @type {TODO} */ - (this.options)[key] = Math.max( - /** @type {number} */ (this.options[key]), - /** @type {number} */ (options[key]) - ); - } else { - throw new Error( - `ChunkGroup.addOptions: No option merge strategy for ${key}` - ); - } - } - } - } - - /** - * returns the name of current ChunkGroup - * @returns {string | null | undefined} returns the ChunkGroup name - */ - get name() { - return this.options.name; - } - - /** - * sets a new name for current ChunkGroup - * @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 {string} a unique concatenation of chunk debugId's - */ - get debugId() { - return Array.from(this.chunks, x => x.debugId).join("+"); - } - - /** - * get a unique id for ChunkGroup, made up of its member Chunk id's - * @returns {string} a unique concatenation of chunk ids - */ - get id() { - return Array.from(this.chunks, x => x.id).join("+"); - } - - /** - * Performs an unshift of a specific chunk - * @param {Chunk} chunk chunk being unshifted - * @returns {boolean} returns true if attempted chunk shift is accepted - */ - unshiftChunk(chunk) { - const oldIdx = this.chunks.indexOf(chunk); - if (oldIdx > 0) { - this.chunks.splice(oldIdx, 1); - this.chunks.unshift(chunk); - } else if (oldIdx < 0) { - this.chunks.unshift(chunk); - return true; - } - return false; - } - - /** - * inserts a chunk before another existing chunk in group - * @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 - */ - insertChunk(chunk, before) { - const oldIdx = this.chunks.indexOf(chunk); - const idx = this.chunks.indexOf(before); - if (idx < 0) { - throw new Error("before chunk not found"); - } - if (oldIdx >= 0 && oldIdx > idx) { - this.chunks.splice(oldIdx, 1); - this.chunks.splice(idx, 0, chunk); - } else if (oldIdx < 0) { - this.chunks.splice(idx, 0, chunk); - return true; - } - return false; - } - - /** - * add a chunk into ChunkGroup. Is pushed on or prepended - * @param {Chunk} chunk chunk being pushed into ChunkGroupS - * @returns {boolean} returns true if chunk addition was successful. - */ - pushChunk(chunk) { - const oldIdx = this.chunks.indexOf(chunk); - if (oldIdx >= 0) { - return false; - } - this.chunks.push(chunk); - return true; - } - - /** - * @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) { - const oldIdx = this.chunks.indexOf(oldChunk); - if (oldIdx < 0) return false; - const newIdx = this.chunks.indexOf(newChunk); - if (newIdx < 0) { - this.chunks[oldIdx] = newChunk; - return true; - } - if (newIdx < oldIdx) { - this.chunks.splice(oldIdx, 1); - return true; - } else if (newIdx !== oldIdx) { - this.chunks[oldIdx] = newChunk; - this.chunks.splice(newIdx, 1); - return true; - } - } - - /** - * @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) { - this.chunks.splice(idx, 1); - return true; - } - return false; - } - - /** - * @returns {boolean} true, when this chunk group will be loaded on initial page load - */ - isInitial() { - return false; - } - - /** - * @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 {ChunkGroup[]} returns the children of this group - */ - getChildren() { - return this._children.getFromCache(getArray); - } - - getNumberOfChildren() { - return this._children.size; - } - - get childrenIterable() { - return this._children; - } - - /** - * @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(group); - group.removeParent(this); - return true; - } - - /** - * @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); - return true; - } - return false; - } - - /** - * @returns {ChunkGroup[]} returns the parents of this group - */ - getParents() { - return this._parents.getFromCache(getArray); - } - - getNumberOfParents() { - return this._parents.size; - } - - /** - * @param {ChunkGroup} parent the parent group - * @returns {boolean} returns true if the parent group contains this group - */ - hasParent(parent) { - return this._parents.has(parent); - } - - get parentsIterable() { - return this._parents; - } - - /** - * @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; - } - - /** - * @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 {Array} an array containing the blocks - */ - getBlocks() { - return this._blocks.getFromCache(getArray); - } - - getNumberOfBlocks() { - return this._blocks.size; - } - - /** - * @param {AsyncDependenciesBlock} block block - * @returns {boolean} true, if block exists - */ - hasBlock(block) { - return this._blocks.has(block); - } - - /** - * @returns {Iterable} blocks - */ - get blocksIterable() { - return this._blocks; - } - - /** - * @param {AsyncDependenciesBlock} block a block - * @returns {boolean} false, if block was already added - */ - addBlock(block) { - if (!this._blocks.has(block)) { - this._blocks.add(block); - return true; - } - return false; - } - - /** - * @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, - loc, - request - }); - } - - /** - * @returns {string[]} the files contained this chunk group - */ - getFiles() { - const files = new Set(); - - for (const chunk of this.chunks) { - for (const file of chunk.files) { - files.add(file); - } - } - - return Array.from(files); - } - - /** - * @returns {void} - */ - remove() { - // cleanup parents - for (const parentChunkGroup of this._parents) { - // remove this chunk from its parents - parentChunkGroup._children.delete(this); - - // cleanup "sub chunks" - for (const chunkGroup of this._children) { - /** - * remove this chunk as "intermediary" and connect - * it "sub chunks" and parents directly - */ - // add parent to each "sub chunk" - chunkGroup.addParent(parentChunkGroup); - // add "sub chunk" to parent - parentChunkGroup.addChild(chunkGroup); - } - } - - /** - * we need to iterate again over the children - * 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. - */ - for (const chunkGroup of this._children) { - // remove this as parent of every "sub chunk" - chunkGroup._parents.delete(this); - } - - // remove chunks - for (const chunk of this.chunks) { - chunk.removeGroup(this); - } - } - - sortItems() { - this.origins.sort(sortOrigin); - } - - /** - * 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 - */ - compareTo(chunkGraph, otherGroup) { - if (this.chunks.length > otherGroup.chunks.length) return -1; - if (this.chunks.length < otherGroup.chunks.length) return 1; - return compareIterables(compareChunks(chunkGraph))( - this.chunks, - otherGroup.chunks - ); - } - - /** - * @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) { - for (const key of Object.keys(childGroup.options)) { - if (key.endsWith("Order")) { - const name = key.slice(0, key.length - "Order".length); - let list = lists.get(name); - if (list === undefined) { - lists.set(name, (list = [])); - } - list.push({ - order: - /** @type {number} */ - ( - childGroup.options[/** @type {keyof ChunkGroupOptions} */ (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; - return a.group.compareTo(chunkGraph, b.group); - }); - result[name] = list.map(i => i.group); - } - return result; - } - - /** - * Sets the top-down index of a module in this ChunkGroup - * @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); - } - - /** - * Gets the top-down index of a module in this ChunkGroup - * @param {Module} module the module - * @returns {number | undefined} index - */ - getModulePreOrderIndex(module) { - return this._modulePreOrderIndices.get(module); - } - - /** - * Sets the bottom-up index of a module in this ChunkGroup - * @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); - } - - /** - * Gets the bottom-up index of a module in this ChunkGroup - * @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}` - ); - } - } - for (const parentChunk of chunk._parents) { - if (!parentChunk._children.has(chunk)) { - throw new Error( - `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/webpack-lib/lib/ChunkRenderError.js b/webpack-lib/lib/ChunkRenderError.js deleted file mode 100644 index fce913f171a..00000000000 --- a/webpack-lib/lib/ChunkRenderError.js +++ /dev/null @@ -1,31 +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; - } -} - -module.exports = ChunkRenderError; diff --git a/webpack-lib/lib/ChunkTemplate.js b/webpack-lib/lib/ChunkTemplate.js deleted file mode 100644 index 238144a30ac..00000000000 --- a/webpack-lib/lib/ChunkTemplate.js +++ /dev/null @@ -1,181 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const memoize = require("./util/memoize"); - -/** @typedef {import("tapable").Tap} Tap */ -/** @typedef {import("../declarations/WebpackOptions").Output} OutputOptions */ -/** @typedef {import("./Chunk")} Chunk */ -/** @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 */ -/** - * @template T - * @typedef {import("tapable").IfSet} IfSet - */ - -const getJavascriptModulesPlugin = memoize(() => - require("./javascript/JavascriptModulesPlugin") -); - -// TODO webpack 6 remove this class -class ChunkTemplate { - /** - * @param {OutputOptions} outputOptions output options - * @param {Compilation} compilation the compilation - */ - constructor(outputOptions, compilation) { - this._outputOptions = outputOptions || {}; - this.hooks = Object.freeze({ - renderManifest: { - tap: util.deprecate( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(RenderManifestEntry[], 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, ModuleTemplate, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, ModuleTemplate, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Hash, Chunk, 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" - ) - } - }); - } -} - -Object.defineProperty(ChunkTemplate.prototype, "outputOptions", { - get: util.deprecate( - /** - * @this {ChunkTemplate} - * @returns {OutputOptions} output options - */ - function () { - return this._outputOptions; - }, - "ChunkTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)", - "DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS" - ) -}); - -module.exports = ChunkTemplate; diff --git a/webpack-lib/lib/CleanPlugin.js b/webpack-lib/lib/CleanPlugin.js deleted file mode 100644 index 2e8fe9bac65..00000000000 --- a/webpack-lib/lib/CleanPlugin.js +++ /dev/null @@ -1,443 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const { SyncBailHook } = require("tapable"); -const Compilation = require("./Compilation"); -const createSchemaValidation = require("./util/create-schema-validation"); -const { join } = require("./util/fs"); -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 {(function(string):boolean)|RegExp} IgnoreItem */ -/** @typedef {Map} Assets */ -/** @typedef {function(IgnoreItem): void} AddToIgnoreCallback */ - -/** - * @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 - */ - -/** - * @callback KeepFn - * @param {string} path path - * @returns {boolean | void} true, if the path should be kept - */ - -const validate = createSchemaValidation( - undefined, - () => { - const { definitions } = require("../schemas/WebpackOptions.json"); - return { - definitions, - oneOf: [{ $ref: "#/definitions/CleanOptions" }] - }; - }, - { - name: "Clean Plugin", - baseDataPath: "options" - } -); -const _10sec = 10 * 1000; - -/** - * marge 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); - } -}; - -/** - * @param {OutputFileSystem} fs filesystem - * @param {string} outputPath output path - * @param {Map} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator) - * @param {function((Error | null)=, Set=): void} callback returns the filenames of the assets that shouldn't be there - * @returns {void} - */ -const getDiffToFs = (fs, outputPath, currentAssets, callback) => { - const directories = new Set(); - // get directories of assets - for (const [asset] of currentAssets) { - directories.add(asset.replace(/(^|\/)[^/]*$/, "")); - } - // and all parent directories - for (const directory of directories) { - directories.add(directory.replace(/(^|\/)[^/]*$/, "")); - } - 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; - const filename = directory ? `${directory}/${file}` : file; - if (!directories.has(filename) && !currentAssets.has(filename)) { - diff.add(filename); - } - } - callback(); - }); - }, - err => { - if (err) return callback(err); - - callback(null, diff); - } - ); -}; - -/** - * @param {Assets} currentAssets assets list - * @param {Assets} oldAssets old assets list - * @returns {Set} diff - */ -const getDiffToOldAssets = (currentAssets, oldAssets) => { - 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; -}; - -/** - * @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); - } -}; - -/** - * @param {OutputFileSystem} fs filesystem - * @param {string} outputPath output path - * @param {boolean} dry only log instead of fs modification - * @param {Logger} logger logger - * @param {Set} diff filenames of the assets that shouldn't be there - * @param {function(string): boolean | void} isKept check if the entry is ignored - * @param {function(Error=, Assets=): void} callback callback - * @returns {void} - */ -const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => { - /** - * @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) => { - /** - * @param {Error & { code?: string }} err error - * @returns {void} - */ - const handleError = err => { - if (err.code === "ENOENT") { - 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); - }; - const path = join(fs, outputPath, filename); - 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(); - -class CleanPlugin { - /** - * @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 = {}) { - validate(options); - this.options = { dry: false, ...options }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { dry, keep } = this.options; - - /** @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: "CleanPlugin", - stage: 100 - }, - (compilation, callback) => { - const hooks = CleanPlugin.getCompilationHooks(compilation); - const logger = compilation.getLogger("webpack.CleanPlugin"); - const fs = /** @type {OutputFileSystem} */ (compiler.outputFileSystem); - - if (!fs.readdir) { - return callback( - new Error( - "CleanPlugin: 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 (/^[A-Za-z]:\\|^\/|^\\\\/.test(asset)) continue; - 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, {}); - - /** - * @param {string} path path - * @returns {boolean | void} true, if needs to be kept - */ - const isKept = path => { - const result = hooks.keep.call(path); - if (result !== undefined) return result; - return keepFn(path); - }; - - /** - * @param {(Error | null)=} err err - * @param {Set=} diff diff - */ - const diffCallback = (err, diff) => { - if (err) { - oldAssets = undefined; - callback(err); - return; - } - applyDiff( - fs, - outputPath, - dry, - logger, - /** @type {Set} */ (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; diff --git a/webpack-lib/lib/CodeGenerationError.js b/webpack-lib/lib/CodeGenerationError.js deleted file mode 100644 index b1cf51d744e..00000000000 --- a/webpack-lib/lib/CodeGenerationError.js +++ /dev/null @@ -1,29 +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 CodeGenerationError extends WebpackError { - /** - * Create a new CodeGenerationError - * @param {Module} module related module - * @param {Error} error Original error - */ - constructor(module, error) { - super(); - - this.name = "CodeGenerationError"; - this.error = error; - this.message = error.message; - this.details = error.stack; - this.module = module; - } -} - -module.exports = CodeGenerationError; diff --git a/webpack-lib/lib/CodeGenerationResults.js b/webpack-lib/lib/CodeGenerationResults.js deleted file mode 100644 index 551d212599c..00000000000 --- a/webpack-lib/lib/CodeGenerationResults.js +++ /dev/null @@ -1,157 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { getOrInsert } = require("./util/MapHelpers"); -const { first } = require("./util/SetHelpers"); -const createHash = require("./util/createHash"); -const { runtimeToString, RuntimeSpecMap } = require("./util/runtime"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {typeof import("./util/Hash")} Hash */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -class CodeGenerationResults { - /** - * @param {string | Hash} hashFunction the hash function to use - */ - constructor(hashFunction = "md4") { - /** @type {Map>} */ - this.map = new Map(); - this._hashFunction = hashFunction; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @param {RuntimeSpec} runtime runtime(s) - * @param {string} sourceType the source type - * @returns {Source} a source - */ - getSource(module, runtime, sourceType) { - return /** @type {Source} */ ( - this.get(module, runtime).sources.get(sourceType) - ); - } - - /** - * @param {Module} module the module - * @param {RuntimeSpec} runtime runtime(s) - * @returns {ReadOnlyRuntimeRequirements | null} runtime requirements - */ - getRuntimeRequirements(module, runtime) { - return this.get(module, runtime).runtimeRequirements; - } - - /** - * @param {Module} module the module - * @param {RuntimeSpec} runtime runtime(s) - * @param {string} key data key - * @returns {any} data generated by code generation - */ - getData(module, runtime, key) { - const data = this.get(module, runtime).data; - return data === undefined ? undefined : data.get(key); - } - - /** - * @param {Module} module the module - * @param {RuntimeSpec} runtime runtime(s) - * @returns {any} 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 = /** @type {string} */ (hash.digest("hex"))); - } - - /** - * @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, () => new RuntimeSpecMap()); - map.set(runtime, result); - } -} - -module.exports = CodeGenerationResults; diff --git a/webpack-lib/lib/CommentCompilationWarning.js b/webpack-lib/lib/CommentCompilationWarning.js deleted file mode 100644 index 99cd0fbdada..00000000000 --- a/webpack-lib/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"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ - -class CommentCompilationWarning extends WebpackError { - /** - * @param {string} message warning message - * @param {DependencyLocation} loc affected lines of code - */ - constructor(message, loc) { - super(message); - - this.name = "CommentCompilationWarning"; - - this.loc = loc; - } -} - -makeSerializable( - CommentCompilationWarning, - "webpack/lib/CommentCompilationWarning" -); - -module.exports = CommentCompilationWarning; diff --git a/webpack-lib/lib/CompatibilityPlugin.js b/webpack-lib/lib/CompatibilityPlugin.js deleted file mode 100644 index 46ddd7e802e..00000000000 --- a/webpack-lib/lib/CompatibilityPlugin.js +++ /dev/null @@ -1,191 +0,0 @@ -/* - 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"); - -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ - -const nestedWebpackIdentifierTag = Symbol("nested webpack identifier"); -const PLUGIN_NAME = "CompatibilityPlugin"; - -class CompatibilityPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, (parser, parserOptions) => { - if ( - parserOptions.browserify !== undefined && - !parserOptions.browserify - ) - return; - - parser.hooks.call.for("require").tap( - PLUGIN_NAME, - /** - * @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", - /** @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 - ]; - 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; - } - ); - }); - - /** - * @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: statement.id.loc, - 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: pattern.loc, - range: pattern.range - } - }); - return true; - }); - parser.hooks.pattern - .for(RuntimeGlobals.exports) - .tap(PLUGIN_NAME, pattern => { - parser.tagVariable(pattern.name, nestedWebpackIdentifierTag, { - name: "__nested_webpack_exports__", - declaration: { - updated: false, - loc: pattern.loc, - range: pattern.range - } - }); - return true; - }); - parser.hooks.expression - .for(nestedWebpackIdentifierTag) - .tap(PLUGIN_NAME, expr => { - const { name, declaration } = 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; diff --git a/webpack-lib/lib/Compilation.js b/webpack-lib/lib/Compilation.js deleted file mode 100644 index 3dc2775f53d..00000000000 --- a/webpack-lib/lib/Compilation.js +++ /dev/null @@ -1,5549 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const { - HookMap, - SyncHook, - SyncBailHook, - SyncWaterfallHook, - AsyncSeriesHook, - AsyncSeriesBailHook, - AsyncParallelHook -} = require("tapable"); -const util = require("util"); -const { CachedSource } = require("webpack-sources"); -const { MultiItemCache } = require("./CacheFacade"); -const Chunk = require("./Chunk"); -const ChunkGraph = require("./ChunkGraph"); -const ChunkGroup = require("./ChunkGroup"); -const ChunkRenderError = require("./ChunkRenderError"); -const ChunkTemplate = require("./ChunkTemplate"); -const CodeGenerationError = require("./CodeGenerationError"); -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 { - connectChunkGroupAndChunk, - connectChunkGroupParentAndChild -} = require("./GraphHelpers"); -const { - makeWebpackError, - tryRunOrWebpackError -} = require("./HookWebpackError"); -const MainTemplate = require("./MainTemplate"); -const Module = require("./Module"); -const ModuleDependencyError = require("./ModuleDependencyError"); -const ModuleDependencyWarning = require("./ModuleDependencyWarning"); -const ModuleGraph = require("./ModuleGraph"); -const ModuleHashingError = require("./ModuleHashingError"); -const ModuleNotFoundError = require("./ModuleNotFoundError"); -const ModuleProfile = require("./ModuleProfile"); -const ModuleRestoreError = require("./ModuleRestoreError"); -const ModuleStoreError = require("./ModuleStoreError"); -const ModuleTemplate = require("./ModuleTemplate"); -const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const RuntimeTemplate = require("./RuntimeTemplate"); -const Stats = require("./Stats"); -const WebpackError = require("./WebpackError"); -const buildChunkGraph = require("./buildChunkGraph"); -const BuildCycleError = require("./errors/BuildCycleError"); -const { Logger, LogType } = 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 { - compareLocations, - concatComparators, - compareSelect, - compareIds, - compareStringsNumeric, - compareModulesByIdentifier -} = require("./util/comparators"); -const createHash = require("./util/createHash"); -const { - arrayToSetDeprecation, - soonFrozenObjectDeprecation, - createFakeHook -} = require("./util/deprecation"); -const processAsyncTree = require("./util/processAsyncTree"); -const { getRuntimeKey } = require("./util/runtime"); -const { isSourceEqual } = require("./util/source"); - -/** @template T @typedef {import("tapable").AsArray} AsArray */ -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ -/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ -/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("./Cache")} Cache */ -/** @typedef {import("./CacheFacade")} CacheFacade */ -/** @typedef {import("./Chunk").ChunkId} ChunkId */ -/** @typedef {import("./ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Compiler").CompilationParams} CompilationParams */ -/** @typedef {import("./Compiler").ModuleMemCachesItem} ModuleMemCachesItem */ -/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ -/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./Module").ValueCacheVersions} ValueCacheVersions */ -/** @typedef {import("./NormalModule").NormalModuleCompilationHooks} NormalModuleCompilationHooks */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./ModuleFactory")} ModuleFactory */ -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateDataContextInfo} ModuleFactoryCreateDataContextInfo */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @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 */ -/** @typedef {import("./util/createHash").Algorithm} Algorithm */ -/** - * @template T - * @typedef {import("./util/deprecation").FakeHook} FakeHook - */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {WeakMap} References */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** - * @callback Callback - * @param {(WebpackError | null)=} err - * @returns {void} - */ - -/** - * @callback ModuleCallback - * @param {(WebpackError | null)=} err - * @param {(Module | null)=} result - * @returns {void} - */ - -/** - * @callback ModuleFactoryResultCallback - * @param {(WebpackError | null)=} err - * @param {ModuleFactoryResult=} result - * @returns {void} - */ - -/** - * @callback ModuleOrFactoryResultCallback - * @param {(WebpackError | null)=} err - * @param {Module | ModuleFactoryResult=} result - * @returns {void} - */ - -/** - * @callback ExecuteModuleCallback - * @param {WebpackError | null} err - * @param {ExecuteModuleResult=} result - * @returns {void} - */ - -/** - * @callback DepBlockVarDependenciesCallback - * @param {Dependency} dependency - * @returns {any} - */ - -/** @typedef {new (...args: any[]) => Dependency} DepConstructor */ - -/** @typedef {Record} CompilationAssets */ - -/** - * @typedef {object} AvailableModulesChunkGroupMapping - * @property {ChunkGroup} chunkGroup - * @property {Set} availableModules - * @property {boolean} needCopy - */ - -/** - * @typedef {object} DependenciesBlockLike - * @property {Dependency[]} dependencies - * @property {AsyncDependenciesBlock[]} blocks - */ - -/** - * @typedef {object} ChunkPathData - * @property {string|number} id - * @property {string=} name - * @property {string} hash - * @property {function(number): string=} hashWithLength - * @property {(Record)=} contentHash - * @property {(Record string>)=} contentHashWithLength - */ - -/** - * @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 - */ - -/** - * @typedef {object} RuntimeRequirementsContext - * @property {ChunkGraph} chunkGraph the chunk graph - * @property {CodeGenerationResults} codeGenerationResults the code generation results - */ - -/** - * @typedef {object} ExecuteModuleOptions - * @property {EntryOptions=} entryOptions - */ - -/** - * @typedef {object} ExecuteModuleResult - * @property {any} exports - * @property {boolean} cacheable - * @property {Map} assets - * @property {LazySet} fileDependencies - * @property {LazySet} contextDependencies - * @property {LazySet} missingDependencies - * @property {LazySet} buildDependencies - */ - -/** - * @typedef {{ id: string, exports: any, loaded: boolean }} ModuleObject - * - * /** - * @typedef {object} ExecuteModuleArgument - * @property {Module} module - * @property {ModuleObject=} moduleObject - * @property {any} preparedInfo - * @property {CodeGenerationResult} codeGenerationResult - */ - -/** - * @typedef {object} ExecuteModuleContext - * @property {Map} assets - * @property {Chunk} chunk - * @property {ChunkGraph} chunkGraph - * @property {function(string): any=} __webpack_require__ - */ - -/** - * @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 - */ - -/** - * @typedef {object} LogEntry - * @property {string} type - * @property {any[]=} args - * @property {number} time - * @property {string[]=} trace - */ - -/** - * @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 {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 */ - -/** - * @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 {object} ModulePathData - * @property {string|number} id - * @property {string} hash - * @property {function(number): string=} hashWithLength - */ - -/** - * @typedef {object} PathData - * @property {ChunkGraph=} chunkGraph - * @property {string=} hash - * @property {function(number): string=} 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 {function(number): string=} contentHashWithLength - * @property {boolean=} noChunkHash - * @property {string=} url - */ - -/** - * @typedef {object} KnownNormalizedStatsOptions - * @property {string} context - * @property {RequestShortener} requestShortener - * @property {string} chunksSort - * @property {string} modulesSort - * @property {string} chunkModulesSort - * @property {string} nestedModulesSort - * @property {string} 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: "module" | "chunk" | "root-of-chunk" | "nested") => 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 {any} _env - */ - -/** @typedef {KnownNormalizedStatsOptions & Omit & Record} NormalizedStatsOptions */ - -/** - * @typedef {object} KnownCreateStatsOptionsContext - * @property {boolean=} forToString - */ - -/** @typedef {Record & KnownCreateStatsOptionsContext} CreateStatsOptionsContext */ - -/** @typedef {{module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[]}[]} 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( - /** - * @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 -/** - * @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 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); - -/** @type {WeakMap} */ -const unsafeCacheDependencies = new WeakMap(); - -/** @type {WeakMap} */ -const unsafeCacheData = new WeakMap(); - -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?: true | Function }} ProcessAssetsAdditionalOptions */ - /** @type {AsyncSeriesHook<[CompilationAssets], ProcessAssetsAdditionalOptions>} */ - const processAssetsHook = new AsyncSeriesHook(["assets"]); - - let savedAssets = new Set(); - /** - * @param {CompilationAssets} assets assets - * @returns {CompilationAssets} new assets - */ - const popNewAssets = assets => { - let newAssets; - for (const file of Object.keys(assets)) { - if (savedAssets.has(file)) continue; - if (newAssets === undefined) { - newAssets = Object.create(null); - } - newAssets[file] = assets[file]; - savedAssets.add(file); - } - return 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; - const processedAssets = additionalAssetsFn ? new WeakSet() : undefined; - switch (type) { - case "sync": - if (additionalAssetsFn) { - this.hooks.processAdditionalAssets.tap(name, assets => { - if (processedAssets.has(this.assets)) - additionalAssetsFn(assets); - }); - } - return { - ...remainingTap, - type: "async", - fn: (assets, callback) => { - try { - fn(assets); - } catch (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 "async": - if (additionalAssetsFn) { - this.hooks.processAdditionalAssets.tapAsync( - name, - (assets, callback) => { - if (processedAssets.has(this.assets)) - return additionalAssetsFn(assets, callback); - callback(); - } - ); - } - return { - ...remainingTap, - fn: (assets, callback) => { - fn(assets, 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 (processedAssets.has(this.assets)) - return additionalAssetsFn(assets); - return Promise.resolve(); - }); - } - return { - ...remainingTap, - 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 - ); - } - }); - } - }; - } - } - }); - - /** @type {SyncHook<[CompilationAssets]>} */ - const afterProcessAssetsHook = new SyncHook(["assets"]); - - /** - * @template T - * @param {string} name name of the hook - * @param {number} stage new stage - * @param {function(): AsArray} getArgs get old hook function args - * @param {string=} code deprecation code (not deprecated when unset) - * @returns {FakeHook, "tap" | "tapAsync" | "tapPromise" | "name">>} fake hook which redirects - */ - const createProcessAssetsHook = (name, stage, getArgs, code) => { - if (!this._backCompat && code) return; - /** - * @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.`; - 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 {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<[(string[] | ReferencedExport)[], Dependency, RuntimeSpec]>} */ - dependencyReferencedExports: new SyncWaterfallHook([ - "referencedExports", - "dependency", - "runtime" - ]), - - /** @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([]), - - /** @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"]), - /** @type {SyncHook<[Iterable]>} */ - afterOptimizeDependencies: new SyncHook(["modules"]), - - /** @type {SyncHook<[]>} */ - optimize: new SyncHook([]), - /** @type {SyncBailHook<[Iterable], boolean | void>} */ - optimizeModules: new SyncBailHook(["modules"]), - /** @type {SyncHook<[Iterable]>} */ - afterOptimizeModules: new SyncHook(["modules"]), - - /** @type {SyncBailHook<[Iterable, ChunkGroup[]], boolean | void>} */ - optimizeChunks: 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"]), - - /** @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, Set, RuntimeRequirementsContext]>} */ - additionalChunkRuntimeRequirements: new SyncHook([ - "chunk", - "runtimeRequirements", - "context" - ]), - /** @type {HookMap, RuntimeRequirementsContext], void>>} */ - runtimeRequirementInChunk: new HookMap( - () => new SyncBailHook(["chunk", "runtimeRequirements", "context"]) - ), - /** @type {SyncHook<[Module, Set, RuntimeRequirementsContext]>} */ - additionalModuleRuntimeRequirements: new SyncHook([ - "module", - "runtimeRequirements", - "context" - ]), - /** @type {HookMap, RuntimeRequirementsContext], void>>} */ - runtimeRequirementInModule: new HookMap( - () => new SyncBailHook(["module", "runtimeRequirements", "context"]) - ), - /** @type {SyncHook<[Chunk, Set, RuntimeRequirementsContext]>} */ - additionalTreeRuntimeRequirements: new SyncHook([ - "chunk", - "runtimeRequirements", - "context" - ]), - /** @type {HookMap, RuntimeRequirementsContext], void>>} */ - runtimeRequirementInTree: new HookMap( - () => new SyncBailHook(["chunk", "runtimeRequirements", "context"]) - ), - - /** @type {SyncHook<[RuntimeModule, Chunk]>} */ - runtimeModule: new SyncHook(["module", "chunk"]), - - /** @type {SyncHook<[Iterable, any]>} */ - reviveModules: new SyncHook(["modules", "records"]), - /** @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, any]>} */ - reviveChunks: new SyncHook(["chunks", "records"]), - /** @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, any]>} */ - recordModules: new SyncHook(["modules", "records"]), - /** @type {SyncHook<[Iterable, any]>} */ - 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<[any]>} */ - recordHash: new SyncHook(["records"]), - /** @type {SyncHook<[Compilation, any]>} */ - record: new SyncHook(["compilation", "records"]), - - /** @type {SyncHook<[]>} */ - beforeModuleAssets: new SyncHook([]), - /** @type {SyncBailHook<[], boolean | void>} */ - shouldGenerateChunkAssets: new SyncBailHook([]), - /** @type {SyncHook<[]>} */ - beforeChunkAssets: new SyncHook([]), - // TODO webpack 6 remove - /** @deprecated */ - additionalChunkAssets: createProcessAssetsHook( - "additionalChunkAssets", - Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, - () => [this.chunks], - "DEP_WEBPACK_COMPILATION_ADDITIONAL_CHUNK_ASSETS" - ), - - // TODO webpack 6 deprecate - /** @deprecated */ - additionalAssets: createProcessAssetsHook( - "additionalAssets", - Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, - () => [] - ), - // TODO webpack 6 remove - /** @deprecated */ - optimizeChunkAssets: createProcessAssetsHook( - "optimizeChunkAssets", - Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE, - () => [this.chunks], - "DEP_WEBPACK_COMPILATION_OPTIMIZE_CHUNK_ASSETS" - ), - // TODO webpack 6 remove - /** @deprecated */ - afterOptimizeChunkAssets: 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([]), - - /** @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"]), - - /** @type {SyncWaterfallHook<[string, object, 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" - ]), - - /** @type {SyncBailHook<[string, LogEntry], boolean | void>} */ - log: new SyncBailHook(["origin", "logEntry"]), - - /** @type {SyncWaterfallHook<[WebpackError[]]>} */ - processWarnings: new SyncWaterfallHook(["warnings"]), - /** @type {SyncWaterfallHook<[WebpackError[]]>} */ - 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"]), - - 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; - /** @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; - this.compilerPath = compiler.compilerPath; - - this.logger = this.getLogger("webpack.Compilation"); - - const options = /** @type {WebpackOptions} */ (compiler.options); - this.options = options; - this.outputOptions = 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, this) - }; - defineRemovedModuleTemplates(this.moduleTemplates); - - /** @type {Map> | undefined} */ - this.moduleMemCaches = undefined; - /** @type {Map> | undefined} */ - this.moduleMemCaches2 = undefined; - this.moduleGraph = new ModuleGraph(); - /** @type {ChunkGraph} */ - this.chunkGraph = undefined; - /** @type {CodeGenerationResults} */ - 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) - }); - - /** - * 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} */ - this.entries = new Map(); - /** @type {EntryData} */ - this.globalEntry = { - dependencies: [], - includeDependencies: [], - options: { - name: undefined - } - }; - /** @type {Map} */ - this.entrypoints = new Map(); - /** @type {Entrypoint[]} */ - this.asyncEntrypoints = []; - /** @type {Set} */ - this.chunks = new Set(); - /** @type {ChunkGroup[]} */ - this.chunkGroups = []; - /** @type {Map} */ - this.namedChunkGroups = new Map(); - /** @type {Map} */ - this.namedChunks = new Map(); - /** @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.records = null; - /** @type {string[]} */ - this.additionalChunkAssets = []; - /** @type {CompilationAssets} */ - this.assets = {}; - /** @type {Map} */ - this.assetsInfo = new Map(); - /** @type {Map>>} */ - this._assetsRelatedIn = new Map(); - /** @type {WebpackError[]} */ - this.errors = []; - /** @type {WebpackError[]} */ - this.warnings = []; - /** @type {Compilation[]} */ - this.children = []; - /** @type {Map} */ - this.logging = new Map(); - /** @type {Map} */ - this.dependencyFactories = new Map(); - /** @type {DependencyTemplates} */ - this.dependencyTemplates = new DependencyTemplates( - this.outputOptions.hashFunction - ); - /** @type {Record} */ - this.childrenCounters = {}; - /** @type {Set} */ - this.usedChunkIds = null; - /** @type {Set} */ - this.usedModuleIds = null; - /** @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 {LazySet} */ - this.fileDependencies = new LazySet(); - /** @type {LazySet} */ - this.contextDependencies = new LazySet(); - /** @type {LazySet} */ - this.missingDependencies = new LazySet(); - /** @type {LazySet} */ - this.buildDependencies = new LazySet(); - // TODO webpack 6 remove - this.compilationDependencies = { - add: util.deprecate( - /** - * @param {string} item item - * @returns {LazySet} 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"); - - const unsafeCache = options.module.unsafeCache; - this._unsafeCache = Boolean(unsafeCache); - this._unsafeCachePredicate = - typeof unsafeCache === "function" ? unsafeCache : () => true; - } - - getStats() { - return new Stats(this); - } - - /** - * @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 }; - } - 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 = {}; - // eslint-disable-next-line guard-for-in - for (const key in optionsOrPreset) { - options[key] = optionsOrPreset[/** @type {keyof StatsOptions} */ (key)]; - } - 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); - } - - /** - * @param {NormalizedStatsOptions} options options - * @returns {StatsFactory} the stats factory - */ - createStatsFactory(options) { - const statsFactory = new StatsFactory(); - this.hooks.statsFactory.call(statsFactory, options); - return statsFactory; - } - - /** - * @param {NormalizedStatsOptions} options options - * @returns {StatsPrinter} the stats printer - */ - createStatsPrinter(options) { - const statsPrinter = new StatsPrinter(); - this.hooks.statsPrinter.call(statsPrinter, options); - return statsPrinter; - } - - /** - * @param {string} name cache name - * @returns {CacheFacade} the cache facade instance - */ - getCache(name) { - return this.compiler.getCache(name); - } - - /** - * @param {string | (function(): 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" - ); - } - } - 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; - } - /** @type {LogEntry} */ - const logEntry = { - time: Date.now(), - type, - args, - trace - }; - 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] - }` - ); - } - } - }, - 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}`); - } - ); - } - - /** - * @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); - } - - /** - * @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); - } - - const currentProfile = this.profile - ? this.moduleGraph.getProfile(module) - : undefined; - if (currentProfile !== undefined) { - currentProfile.markRestoringStart(); - } - - 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 /** @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); - } - - /** - * 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); - } - - /** - * 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(); - } - - module.needBuild( - { - compilation: this, - fileSystemInfo: this.fileSystemInfo, - valueCacheVersions: this.valueCacheVersions - }, - (err, needBuild) => { - if (err) return callback(err); - - if (!needBuild) { - if (currentProfile !== undefined) { - currentProfile.markBuildingEnd(); - } - this.hooks.stillValidModule.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(); - }); - } - ); - } - ); - } - - /** - * @param {Module} module to be processed for deps - * @param {ModuleCallback} callback callback to be triggered - * @returns {void} - */ - processModuleDependencies(module, callback) { - this.processDependenciesQueue.add(module, callback); - } - - /** - * @param {Module} module to be processed for deps - * @returns {void} - */ - processModuleDependenciesNonRecursive(module) { - /** - * @param {DependenciesBlock} block block - */ - const processDependenciesBlock = block => { - if (block.dependencies) { - let i = 0; - for (const dep of block.dependencies) { - this.moduleGraph.setParents(dep, block, module, i++); - } - } - if (block.blocks) { - for (const b of block.blocks) processDependenciesBlock(b); - } - }; - - processDependenciesBlock(module); - } - - /** - * @param {Module} module to be processed for deps - * @param {ModuleCallback} callback callback to be triggered - * @returns {void} - */ - _processModuleDependencies(module, callback) { - /** @type {Array<{factory: ModuleFactory, dependencies: Dependency[], context: string|undefined, originModule: Module|null}>} */ - const sortedDependencies = []; - - /** @type {DependenciesBlock} */ - let currentBlock; - - /** @type {Map>} */ - let dependencies; - /** @type {DepConstructor} */ - 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; - - /** - * @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(); - } - - // 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(); - }; - - /** - * @param {WebpackError=} err error - * @returns {void} - */ - const onTransitiveTasksFinished = err => { - if (err) return callback(err); - this.processDependenciesQueue.decreaseParallelism(); - - return callback(); - }; - - /** - * @param {Dependency} dep dependency - * @param {number} index index in block - * @returns {void} - */ - const processDependency = (dep, index) => { - this.moduleGraph.setParents(dep, currentBlock, module, index); - 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 - ); - 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 - ); - 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 - } catch (err) { - if (inProgressSorting <= 0) return; - inProgressSorting = -1; - onDependenciesSorted(/** @type {WebpackError} */ (err)); - return; - } - if (--inProgressSorting === 0) onDependenciesSorted(); - }); - return; - } - } catch (err) { - console.error(err); - } - } - processDependencyForResolving(dep); - }; - - /** - * @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 {DepConstructor} */ (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 = [])); - sortedDependencies.push({ - factory: factoryCacheKey2, - dependencies: list, - context: dep.getContext(), - originModule: module - }); - } - 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(); - } - - /** - * @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, - true, - false, - callback - ); - } - - /** - * @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); - } - - /** - * @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 - */ - - /** - * @param {HandleModuleCreationOptions} options options object - * @param {ModuleCallback} callback callback - * @returns {void} - */ - handleModuleCreation( - { - factory, - dependencies, - originModule, - contextInfo, - context, - recursive = true, - connectOrigin = recursive, - checkCycle = !recursive - }, - callback - ) { - const moduleGraph = this.moduleGraph; - - const currentProfile = this.profile ? new ModuleProfile() : undefined; - - this.factorizeModule( - { - currentProfile, - factory, - dependencies, - factoryResult: true, - originModule, - contextInfo, - context - }, - (err, factoryResult) => { - const applyFactoryResultDependencies = () => { - const { fileDependencies, contextDependencies, missingDependencies } = - /** @type {ModuleFactoryResult} */ (factoryResult); - if (fileDependencies) { - this.fileDependencies.addAll(fileDependencies); - } - if (contextDependencies) { - this.contextDependencies.addAll(contextDependencies); - } - if (missingDependencies) { - this.missingDependencies.addAll(missingDependencies); - } - }; - if (err) { - if (factoryResult) applyFactoryResultDependencies(); - if (dependencies.every(d => d.optional)) { - this.warnings.push(err); - return callback(); - } - this.errors.push(err); - return callback(err); - } - - const newModule = - /** @type {ModuleFactoryResult} */ - (factoryResult).module; - - if (!newModule) { - applyFactoryResultDependencies(); - return callback(); - } - - if (currentProfile !== undefined) { - moduleGraph.setProfile(newModule, currentProfile); - } - - this.addModule(newModule, (err, _module) => { - if (err) { - applyFactoryResultDependencies(); - if (!err.module) { - err.module = _module; - } - this.errors.push(err); - - return callback(err); - } - - const module = - /** @type {Module & { restoreFromUnsafeCache?: Function }} */ - (_module); - - if ( - this._unsafeCache && - /** @type {ModuleFactoryResult} */ - (factoryResult).cacheable !== false && - module.restoreFromUnsafeCache && - this._unsafeCachePredicate(module) - ) { - const unsafeCacheableModule = - /** @type {Module & { restoreFromUnsafeCache: Function }} */ - (module); - for (let i = 0; i < dependencies.length; i++) { - const dependency = dependencies[i]; - moduleGraph.setResolvedModule( - connectOrigin ? originModule : null, - dependency, - unsafeCacheableModule - ); - unsafeCacheDependencies.set(dependency, unsafeCacheableModule); - } - if (!unsafeCacheData.has(unsafeCacheableModule)) { - unsafeCacheData.set( - unsafeCacheableModule, - unsafeCacheableModule.getUnsafeCacheData() - ); - } - } else { - applyFactoryResultDependencies(); - for (let i = 0; i < dependencies.length; i++) { - const dependency = dependencies[i]; - moduleGraph.setResolvedModule( - connectOrigin ? originModule : null, - dependency, - module - ); - } - } - - moduleGraph.setIssuerIfUnset( - module, - originModule !== undefined ? originModule : null - ); - if (module !== newModule && currentProfile !== undefined) { - const otherProfile = moduleGraph.getProfile(module); - if (otherProfile !== undefined) { - currentProfile.mergeInto(otherProfile); - } else { - moduleGraph.setProfile(module, currentProfile); - } - } - - this._handleModuleBuildAndDependencies( - originModule, - module, - recursive, - checkCycle, - callback - ); - }); - } - ); - } - - /** - * @private - * @param {Module} 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 {ModuleCallback} callback callback - * @returns {void} - */ - _handleModuleBuildAndDependencies( - originModule, - module, - recursive, - checkCycle, - callback - ) { - // Check for cycles when build is trigger inside another build - /** @type {Set | undefined} */ - let creatingModuleDuringBuildSet; - if (checkCycle && this.buildQueue.isProcessing(originModule)) { - // Track build dependency - creatingModuleDuringBuildSet = - this.creatingModuleDuringBuild.get(originModule); - if (creatingModuleDuringBuildSet === undefined) { - creatingModuleDuringBuildSet = new Set(); - this.creatingModuleDuringBuild.set( - 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); - } - } - } - } - } - - this.buildModule(module, err => { - if (creatingModuleDuringBuildSet !== undefined) { - creatingModuleDuringBuildSet.delete(module); - } - if (err) { - if (!err.module) { - err.module = module; - } - this.errors.push(err); - - return callback(err); - } - - if (!recursive) { - this.processModuleDependenciesNonRecursive(module); - callback(null, module); - return; - } - - // This avoids deadlocks for circular dependencies - if (this.processDependenciesQueue.isProcessing(module)) { - return callback(null, module); - } - - this.processModuleDependencies(module, err => { - if (err) { - return callback(err); - } - callback(null, module); - }); - }); - } - - /** - * @param {FactorizeModuleOptions} options options object - * @param {ModuleOrFactoryResultCallback} callback callback - * @returns {void} - */ - _factorizeModule( - { - currentProfile, - factory, - dependencies, - originModule, - factoryResult, - contextInfo, - context - }, - callback - ) { - if (currentProfile !== undefined) { - currentProfile.markFactoryStart(); - } - factory.create( - { - contextInfo: { - issuer: originModule ? originModule.nameForCondition() : "", - issuerLayer: originModule ? originModule.layer : null, - compiler: this.compiler.name, - ...contextInfo - }, - resolveOptions: originModule ? originModule.resolveOptions : undefined, - context: - context || - (originModule ? 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(); - } - - if (currentProfile !== undefined) { - currentProfile.markFactoryEnd(); - } - - callback(null, factoryResult ? result : result.module); - } - ); - } - - /** - * @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); - } - - /** - * @param {object} options options - * @param {string} options.context context string path - * @param {Dependency} options.dependency dependency used to create Module chain - * @param {Partial=} options.contextInfo additional context info for the root module - * @param {ModuleCallback} callback callback for when module chain is complete - * @returns {void} will throw if dependency instance is not a valid Dependency - */ - addModuleTree({ context, dependency, contextInfo }, callback) { - if ( - typeof dependency !== "object" || - dependency === null || - !dependency.constructor - ) { - return callback( - new WebpackError("Parameter 'dependency' must be a Dependency") - ); - } - const Dep = /** @type {DepConstructor} */ (dependency.constructor); - const moduleFactory = this.dependencyFactories.get(Dep); - if (!moduleFactory) { - return callback( - new WebpackError( - `No dependency factory available for this dependency type: ${dependency.constructor.name}` - ) - ); - } - - this.handleModuleCreation( - { - factory: moduleFactory, - dependencies: [dependency], - originModule: null, - contextInfo, - context - }, - (err, result) => { - if (err && this.bail) { - callback(err); - this.buildQueue.stop(); - this.rebuildQueue.stop(); - this.processDependenciesQueue.stop(); - this.factorizeQueue.stop(); - } else if (!err && result) { - callback(null, result); - } else { - callback(); - } - } - ); - } - - /** - * @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); - } - - /** - * @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 - ); - } - - /** - * @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; - 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)) { - 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) { - entryData.options[key] = options[key]; - } else { - return callback( - new WebpackError( - `Conflicting entry option ${key} = ${entryData.options[key]} vs ${options[key]}` - ) - ); - } - } - } - - this.hooks.addEntry.call(entry, options); - - this.addModuleTree( - { - context, - dependency: entry, - contextInfo: entryData.options.layer - ? { issuerLayer: entryData.options.layer } - : undefined - }, - (err, module) => { - if (err) { - this.hooks.failedEntry.call(entry, options, err); - return callback(err); - } - this.hooks.succeedEntry.call(entry, options, module); - return callback(null, 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); - } - - /** - * @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.slice(); - const oldBlocks = module.blocks.slice(); - module.invalidateBuild(); - this.buildQueue.invalidate(module); - this.buildModule(module, err => { - if (err) { - return this.hooks.finishRebuildingModule.callAsync(module, err2 => { - if (err2) { - callback( - makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule") - ); - return; - } - callback(err); - }); - } - - this.processDependenciesQueue.invalidate(module); - this.moduleGraph.unfreeze(); - this.processModuleDependencies(module, err => { - if (err) return callback(err); - this.removeReasonsOfDependencyBlock(module, { - dependencies: oldDependencies, - blocks: oldBlocks - }); - this.hooks.finishRebuildingModule.callAsync(module, err2 => { - if (err2) { - callback( - makeWebpackError(err2, "Compilation.hooks.finishRebuildingModule") - ); - return; - } - callback(null, module); - }); - }); - }); - } - - /** - * @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; - const affectedModules = new Set(); - const infectedModules = new Set(); - let statNew = 0; - let statChanged = 0; - let statUnchanged = 0; - let statReferencesChanged = 0; - let statWithoutBuild = 0; - - /** - * @param {Module} module module - * @returns {References | undefined} references - */ - const computeReferences = module => { - /** @type {References | 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; - }; - - /** - * @param {Module} module the module - * @param {References | 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 - 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 - const memCache = new WeakTupleMap(); - moduleMemCaches.set(module, memCache); - affectedModules.add(module); - cachedMemCache.references = computeReferences(module); - cachedMemCache.memCache = memCache; - statReferencesChanged++; - } else { - // keep the old mem cache - moduleMemCaches.set(module, cachedMemCache.memCache); - statUnchanged++; - } - } else { - infectedModules.add(module); - moduleMemCacheCache.delete(module); - statWithoutBuild++; - } - modulesWithoutCache.delete(module); - } else { - moduleMemCacheCache.delete(module); - } - } - - for (const module of modulesWithoutCache) { - const buildInfo = module.buildInfo; - if (buildInfo) { - // create a new entry - const memCache = new WeakTupleMap(); - moduleMemCacheCache.set(module, { - buildInfo, - references: computeReferences(module), - memCache - }); - moduleMemCaches.set(module, memCache); - affectedModules.add(module); - statNew++; - } else { - infectedModules.add(module); - statWithoutBuild++; - } - } - - /** - * @param {readonly ModuleGraphConnection[]} 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; - }; - const directOnlyInfectedModules = new Set(); - for (const module of infectedModules) { - for (const [ - referencingModule, - connections - ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { - if (!referencingModule) continue; - if (infectedModules.has(referencingModule)) continue; - const type = reduceAffectType(connections); - if (!type) continue; - if (type === true) { - directOnlyInfectedModules.add(referencingModule); - } else { - infectedModules.add(referencingModule); - } - } - } - for (const module of directOnlyInfectedModules) infectedModules.add(module); - 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); - } - 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)` - ); - } - - _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; - /** - * @param {Module} module module - * @returns {{ id: ModuleId, modules?: Map, blocks?: (string | number | null)[] }} references - */ - const computeReferences = module => { - const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - /** @type {Map | undefined} */ - let modules; - /** @type {(string | number | 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 = Array.from(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 }; - }; - /** - * @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 - * @returns {boolean} ok? - */ - const compareReferences = (module, { id, modules, blocks }) => { - 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 = Array.from(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; - } - return true; - }; - - for (const [module, memCache] of moduleMemCaches) { - /** @type {{ references: { id: string | number, modules?: Map, blocks?: (string | number | null)[]}, memCache: WeakTupleMap }} */ - const cache = memCache.get(key); - if (cache === undefined) { - const memCache2 = new WeakTupleMap(); - memCache.set(key, { - references: computeReferences(module), - memCache: memCache2 - }); - moduleMemCaches2.set(module, memCache2); - statNew++; - } else if (!compareReferences(module, cache.references)) { - 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)` - ); - } - - /** - * @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 - /** - * @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); - } - }; - /** - * @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}` - ); - }; - /** - * @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) => { - const map = new Map(); - for (const [module, profile] of modulesWithProfiles) { - const list = getOrInsert( - map, - `${module.type}!${module.identifier().replace(/(!|^)[^!]*$/, "")}`, - () => [] - ); - list.push({ module, profile }); - } - - let sum = 0; - let max = 0; - for (const [key, modules] of map) { - let innerSum = 0; - let innerMax = 0; - for (const { module, profile } of modules) { - const p = getParallelism(profile); - const d = getDuration(profile); - if (d === 0 || p === 0) continue; - const t = d / p; - innerSum += t; - if (t <= 10) continue; - 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"); - // TODO keep a cacheToken (= {}) for each module in the graph - // create a new one per compilation and flag all updated files - // and parents with it - this.logger.time("report dependency errors and warnings"); - for (const module of modules) { - // TODO only run for modules with changed cacheToken - // global WeakMap> to keep modules without errors/warnings - const memCache = moduleMemCaches && moduleMemCaches.get(module); - if (memCache && memCache.get("noWarningsOrErrors")) continue; - let hasProblems = this.reportDependencyErrorsAndWarnings(module, [ - module - ]); - const errors = module.getErrors(); - if (errors !== undefined) { - for (const error of errors) { - if (!error.module) { - error.module = module; - } - this.errors.push(error); - hasProblems = true; - } - } - const warnings = module.getWarnings(); - if (warnings !== undefined) { - for (const warning of warnings) { - if (!warning.module) { - warning.module = module; - } - this.warnings.push(warning); - hasProblems = true; - } - } - if (!hasProblems && memCache) memCache.set("noWarningsOrErrors", true); - } - this.moduleGraph.unfreeze(); - this.logger.timeEnd("report dependency errors and warnings"); - - callback(); - }); - } - - unseal() { - this.hooks.unseal.call(); - this.chunks.clear(); - this.chunkGroups.length = 0; - this.namedChunks.clear(); - this.namedChunkGroups.clear(); - this.entrypoints.clear(); - this.additionalChunkAssets.length = 0; - this.assets = {}; - this.assetsInfo.clear(); - this.moduleGraph.removeAllModuleAttributes(); - this.moduleGraph.unfreeze(); - this.moduleMemCaches2 = undefined; - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - seal(callback) { - /** - * @param {WebpackError=} err err - * @returns {void} - */ - const finalCallback = err => { - this.factorizeQueue.clear(); - this.buildQueue.clear(); - this.rebuildQueue.clear(); - this.processDependenciesQueue.clear(); - this.addModuleQueue.clear(); - return callback(err); - }; - const chunkGraph = new ChunkGraph( - this.moduleGraph, - this.outputOptions.hashFunction - ); - this.chunkGraph = chunkGraph; - - if (this._backCompat) { - for (const module of this.modules) { - ChunkGraph.setChunkGraphForModule(module, chunkGraph); - } - } - - this.hooks.seal.call(); - - this.logger.time("optimize dependencies"); - while (this.hooks.optimizeDependencies.call(this.modules)) { - /* empty */ - } - this.hooks.afterOptimizeDependencies.call(this.modules); - 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); - 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); - connectChunkGroupAndChunk(entrypoint, chunk); - - const entryModules = new Set(); - for (const dep of [...this.globalEntry.dependencies, ...dependencies]) { - entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request); - - const module = this.moduleGraph.getModule(dep); - if (module) { - 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); - - /** - * @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.assignDepth(module); - modulesList.push(module); - } - } - 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(); - const dependOnEntries = []; - 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; - } - dependOnEntries.push(dependency); - } - for (const dependency of dependOnEntries) { - connectChunkGroupParentAndChild(dependency, entry); - } - } 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); - } - } - 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.optimizeModules.call(this.modules)) { - /* empty */ - } - this.hooks.afterOptimizeModules.call(this.modules); - - 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 => { - if (err) { - return finalCallback( - makeWebpackError(err, "Compilation.hooks.optimizeTree") - ); - } - - this.hooks.afterOptimizeTree.call(this.chunks, this.modules); - - this.hooks.optimizeChunkModules.callAsync( - this.chunks, - this.modules, - err => { - if (err) { - return finalCallback( - makeWebpackError(err, "Compilation.hooks.optimizeChunkModules") - ); - } - - this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules); - - const shouldRecord = this.hooks.shouldRecord.call() !== false; - - this.hooks.reviveModules.call(this.modules, 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, 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.assignRuntimeIds(); - - this.logger.time("compute affected modules with chunk graph"); - this._computeAffectedModulesWithChunkGraph(); - this.logger.timeEnd("compute affected modules with chunk graph"); - - this.sortItemsWithChunkIds(); - - if (shouldRecord) { - this.hooks.recordModules.call(this.modules, this.records); - this.hooks.recordChunks.call(this.chunks, this.records); - } - - this.hooks.optimizeCodeGeneration.call(this.modules); - this.logger.timeEnd("optimize"); - - this.logger.time("module hashing"); - this.hooks.beforeModuleHash.call(); - this.createModuleHashes(); - this.hooks.afterModuleHash.call(); - this.logger.timeEnd("module hashing"); - - this.logger.time("code generation"); - this.hooks.beforeCodeGeneration.call(); - this.codeGeneration(err => { - if (err) { - return finalCallback(err); - } - 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(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, 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(); - } - }); - }); - } - ); - }); - } - - /** - * @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) { - let hasProblems = false; - for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { - const block = blocks[indexBlock]; - const dependencies = block.dependencies; - - for (let indexDep = 0; indexDep < dependencies.length; indexDep++) { - const d = dependencies[indexDep]; - - 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); - this.warnings.push(warning); - hasProblems = true; - } - } - 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); - this.errors.push(error); - hasProblems = true; - } - } - } - - if (this.reportDependencyErrorsAndWarnings(module, block.blocks)) - hasProblems = true; - } - return hasProblems; - } - - /** - * @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); - } - - /** - * @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 = this.codeGenerationResults; - /** @type {WebpackError[]} */ - const errors = []; - /** @type {NotCodeGeneratedModules | undefined} */ - let notCodeGeneratedModules; - const runIteration = () => { - /** @type {CodeGenerationJobs} */ - let delayedJobs = []; - let delayedModules = new Set(); - asyncLib.eachLimit( - jobs, - /** @type {number} */ - (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(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(); - } - - /** - * @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 {function((WebpackError | null)=, 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)); - let result; - if (!cachedResult) { - try { - codeGenerated = true; - this.codeGeneratedModules.add(module); - result = module.codeGeneration({ - chunkGraph, - moduleGraph, - dependencyTemplates, - runtimeTemplate, - runtime, - codeGenerationResults: results, - compilation: this - }); - } catch (err) { - errors.push( - new CodeGenerationError(module, /** @type {Error} */ (err)) - ); - result = cachedResult = { - sources: new Map(), - runtimeRequirements: null - }; - } - } 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; - } - - /** - * @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 = 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, - cached, - false - ); - } - continue; - } - } - 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) { - 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) { - 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"); - } - - // TODO webpack 6 make chunkGraph argument non-optional - /** - * @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 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 }; - } - const name = groupOptions.name; - - if (name) { - const chunkGroup = this.namedChunkGroups.get(name); - if (chunkGroup !== undefined) { - if (module) { - chunkGroup.addOrigin( - module, - /** @type {DependencyLocation} */ - (loc), - request - ); - } - return chunkGroup; - } - } - const chunkGroup = new ChunkGroup(groupOptions); - if (module) - chunkGroup.addOrigin( - module, - /** @type {DependencyLocation} */ - (loc), - request - ); - const chunk = this.addChunk(name); - - connectChunkGroupAndChunk(chunkGroup, chunk); - - this.chunkGroups.push(chunkGroup); - if (name) { - this.namedChunkGroups.set(name, chunkGroup); - } - return chunkGroup; - } - - /** - * @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 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 (entrypoint !== undefined) { - if (module) { - entrypoint.addOrigin(module, loc, request); - } - return entrypoint; - } - } else if (entrypoint) { - throw new Error( - `Cannot add an async entrypoint with the name '${name}', because there is already an chunk group with this name` - ); - } - } - const chunk = this.addChunk(name); - if (options.filename) { - chunk.filenameTemplate = options.filename; - } - const entrypoint = new Entrypoint(options, false); - entrypoint.setRuntimeChunk(chunk); - entrypoint.setEntrypointChunk(chunk); - if (name) { - this.namedChunkGroups.set(name, entrypoint); - } - this.chunkGroups.push(entrypoint); - this.asyncEntrypoints.push(entrypoint); - connectChunkGroupAndChunk(entrypoint, chunk); - 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 {string=} 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); - if (chunk !== undefined) { - return 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; - } - - /** - * @deprecated - * @param {Module} module module to assign depth - * @returns {void} - */ - assignDepth(module) { - const moduleGraph = this.moduleGraph; - - const queue = new Set([module]); - /** @type {number} */ - let depth; - - moduleGraph.setDepth(module, 0); - - /** - * @param {Module} module module for processing - * @returns {void} - */ - const processModule = module => { - if (!moduleGraph.setDepthIfLower(module, depth)) return; - queue.add(module); - }; - - for (module of queue) { - queue.delete(module); - depth = /** @type {number} */ (moduleGraph.getDepth(module)) + 1; - - for (const connection of moduleGraph.getOutgoingConnections(module)) { - const refModule = connection.module; - if (refModule) { - processModule(refModule); - } - } - } - } - - /** - * @param {Set} modules module to assign depth - * @returns {void} - */ - assignDepths(modules) { - const moduleGraph = this.moduleGraph; - - /** @type {Set} */ - const queue = new Set(modules); - queue.add(1); - let depth = 0; - - let i = 0; - for (const module of queue) { - i++; - if (typeof module === "number") { - depth = module; - if (queue.size === i) return; - queue.add(depth + 1); - } else { - moduleGraph.setDepth(module, depth); - for (const { module: refModule } of moduleGraph.getOutgoingConnections( - module - )) { - if (refModule) { - queue.add(refModule); - } - } - } - } - } - - /** - * @param {Dependency} dependency the dependency - * @param {RuntimeSpec} runtime the runtime - * @returns {(string[] | ReferencedExport)[]} referenced exports - */ - getDependencyReferencedExports(dependency, runtime) { - const referencedExports = dependency.getReferencedExports( - this.moduleGraph, - runtime - ); - return this.hooks.dependencyReferencedExports.call( - referencedExports, - dependency, - runtime - ); - } - - /** - * @param {Module} module module relationship for removal - * @param {DependenciesBlockLike} block //TODO: good description - * @returns {void} - */ - removeReasonsOfDependencyBlock(module, block) { - if (block.blocks) { - for (const b of block.blocks) { - this.removeReasonsOfDependencyBlock(module, b); - } - } - - 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); - } - } - } - } - } - } - - /** - * @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); - } - } - - /** - * @param {DependenciesBlock} block block tie for Chunk - * @param {Chunk} chunk chunk to remove from dep - * @returns {void} - */ - removeChunkFromDependencies(block, chunk) { - /** - * @param {Dependency} d dependency to (maybe) patch up - */ - const iteratorDependency = d => { - const depModule = this.moduleGraph.getModule(d); - if (!depModule) { - return; - } - this.patchChunksAfterReasonRemoval(depModule, chunk); - }; - - const blocks = block.blocks; - for (let indexBlock = 0; indexBlock < blocks.length; indexBlock++) { - const asyncBlock = blocks[indexBlock]; - 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 (let indexChunk = 0; indexChunk < chunks.length; indexChunk++) { - const iteratedChunk = chunks[indexChunk]; - chunkGroup.removeChunk(iteratedChunk); - // Recurse - this.removeChunkFromDependencies(block, iteratedChunk); - } - } - - if (block.dependencies) { - for (const dep of block.dependencies) iteratorDependency(dep); - } - } - - assignRuntimeIds() { - const { chunkGraph } = this; - /** - * @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); - } - } - - sortItemsWithChunkIds() { - for (const chunkGroup of this.chunkGroups) { - chunkGroup.sortItems(); - } - - this.errors.sort(compareErrors); - this.warnings.sort(compareErrors); - this.children.sort(byNameOrHash); - } - - summarizeDependencies() { - for ( - let indexChildren = 0; - indexChildren < this.children.length; - indexChildren++ - ) { - const child = this.children[indexChildren]; - - this.fileDependencies.addAll(child.fileDependencies); - this.contextDependencies.addAll(child.contextDependencies); - this.missingDependencies.addAll(child.missingDependencies); - this.buildDependencies.addAll(child.buildDependencies); - } - - for (const module of this.modules) { - module.addCacheDependencies( - this.fileDependencies, - this.contextDependencies, - this.missingDependencies, - this.buildDependencies - ); - } - } - - 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 = memCache.get(`moduleHash-${getRuntimeKey(runtime)}`); - if (digest !== undefined) { - chunkGraph.setModuleHashes( - module, - runtime, - digest, - digest.slice(0, hashDigestLength) - ); - statModulesFromCache++; - continue; - } - } - statModulesHashed++; - const digest = this._createModuleHash( - module, - chunkGraph, - runtime, - hashFunction, - runtimeTemplate, - hashDigest, - hashDigestLength, - errors - ); - if (memCache) { - memCache.set(`moduleHash-${getRuntimeKey(runtime)}`, digest); - } - } - } - 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)` - ); - } - - /** - * @private - * @param {Module} module module - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {RuntimeSpec} runtime runtime - * @param {OutputOptions["hashFunction"]} hashFunction hash function - * @param {RuntimeTemplate} runtimeTemplate runtime template - * @param {OutputOptions["hashDigest"]} hashDigest hash digest - * @param {OutputOptions["hashDigestLength"]} hashDigestLength hash digest length - * @param {WebpackError[]} errors errors - * @returns {string} module hash digest - */ - _createModuleHash( - module, - chunkGraph, - runtime, - hashFunction, - runtimeTemplate, - hashDigest, - hashDigestLength, - errors - ) { - let moduleHashDigest; - try { - const moduleHash = createHash(/** @type {Algorithm} */ (hashFunction)); - module.updateHash(moduleHash, { - chunkGraph, - runtime, - runtimeTemplate - }); - moduleHashDigest = /** @type {string} */ (moduleHash.digest(hashDigest)); - } catch (err) { - errors.push(new ModuleHashingError(module, /** @type {Error} */ (err))); - moduleHashDigest = "XXXXXX"; - } - chunkGraph.setModuleHashes( - module, - runtime, - moduleHashDigest, - moduleHashDigest.slice(0, hashDigestLength) - ); - return moduleHashDigest; - } - - 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(/** @type {Algorithm} */ (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)); - } - 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"); - } - - this.logger.time("hashing: sort chunks"); - /* - * all non-runtime chunks need to be hashes first, - * since runtime chunk might use their hashes. - * runtime chunks need to be hashed in the correct order - * since they may depend on each other (for async entrypoints). - * So we put all non-runtime chunks first and hash them in any order. - * And order runtime chunks according to referenced between each other. - * Chunks need to be in deterministic order since we add hashes to full chunk - * during these hashing. - */ - /** @type {Chunk[]} */ - const unorderedRuntimeChunks = []; - /** @type {Chunk[]} */ - const otherChunks = []; - for (const c of this.chunks) { - if (c.hasRuntime()) { - unorderedRuntimeChunks.push(c); - } else { - otherChunks.push(c); - } - } - unorderedRuntimeChunks.sort(byId); - otherChunks.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( - Array.from(info.chunk.getAllReferencedAsyncEntrypoints()).map( - e => e.chunks[e.chunks.length - 1] - ) - )) { - const otherInfo = - /** @type {RuntimeChunkInfo} */ - (runtimeChunksMap.get(other)); - otherInfo.referencedBy.push(info); - info.remaining++; - remaining++; - } - } - /** @type {Chunk[]} */ - const runtimeChunks = []; - for (const info of runtimeChunksMap.values()) { - if (info.remaining === 0) { - runtimeChunks.push(info.chunk); - } - } - // If there are any references between chunks - // make sure to follow these chains - if (remaining > 0) { - 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; - } - } - } - // If there are still remaining references we have cycles and want to create a warning - if (remaining > 0) { - const circularRuntimeChunkInfo = []; - for (const info of runtimeChunksMap.values()) { - if (info.remaining !== 0) { - circularRuntimeChunkInfo.push(info); - } - } - 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"); - - const fullHashChunks = new Set(); - /** @type {{module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[]}[]} */ - const codeGenerationJobs = []; - /** @type {Map>} */ - const codeGenerationJobsMap = new Map(); - /** @type {WebpackError[]} */ - const errors = []; - - /** - * @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); - } - } - this.logger.timeAggregate("hashing: hash runtime modules"); - try { - this.logger.time("hashing: hash chunks"); - const chunkHash = createHash(/** @type {Algorithm} */ (hashFunction)); - if (outputOptions.hashSalt) { - chunkHash.update(outputOptions.hashSalt); - } - chunk.updateHash(chunkHash, chunkGraph); - this.hooks.chunkHash.call(chunk, chunkHash, { - chunkGraph, - codeGenerationResults: this.codeGenerationResults, - moduleGraph: this.moduleGraph, - runtimeTemplate: this.runtimeTemplate - }); - const chunkHashDigest = /** @type {string} */ ( - 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"); - }; - for (const chunk of otherChunks) processChunk(chunk); - for (const chunk of runtimeChunks) processChunk(chunk); - if (errors.length > 0) { - errors.sort(compareSelect(err => err.module, compareModulesByIdentifier)); - for (const error of errors) { - this.errors.push(error); - } - } - - 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 = /** @type {string} */ (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(/** @type {Algorithm} */ (hashFunction)); - module.updateHash(moduleHash, { - chunkGraph, - runtime: chunk.runtime, - runtimeTemplate - }); - const moduleHashDigest = /** @type {string} */ ( - moduleHash.digest(hashDigest) - ); - const oldHash = chunkGraph.getModuleHash(module, chunk.runtime); - chunkGraph.setModuleHashes( - module, - chunk.runtime, - moduleHashDigest, - moduleHashDigest.slice(0, hashDigestLength) - ); - codeGenerationJobsMap.get(oldHash).get(module).hash = moduleHashDigest; - } - const chunkHash = createHash(/** @type {Algorithm} */ (hashFunction)); - chunkHash.update(chunk.hash); - chunkHash.update(this.hash); - const chunkHashDigest = - /** @type {string} */ - (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; - } - - /** - * @param {string} file file name - * @param {Source} source asset source - * @param {AssetInfo} assetInfo extra asset information - * @returns {void} - */ - emitAsset(file, source, assetInfo = {}) { - 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); - } - - /** - * @private - * @param {string} file file name - * @param {AssetInfo} newInfo new asset information - * @param {AssetInfo=} oldInfo old asset information - */ - _setAssetInfo(file, newInfo, oldInfo = this.assetsInfo.get(file)) { - if (newInfo === undefined) { - this.assetsInfo.delete(file); - } else { - this.assetsInfo.set(file, newInfo); - } - const oldRelated = oldInfo && oldInfo.related; - const newRelated = newInfo && newInfo.related; - if (oldRelated) { - for (const key of Object.keys(oldRelated)) { - /** - * @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); - } - } - } - if (newRelated) { - for (const key of Object.keys(newRelated)) { - /** - * @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); - } - } - } - } - - /** - * @param {string} file file name - * @param {Source | function(Source): Source} newSourceOrFunction new asset source or function converting old to new - * @param {(AssetInfo | function(AssetInfo | undefined): AssetInfo) | 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 - ); - } - } - } - - /** - * @param {string} file file name - * @param {string} newFile the new name of file - */ - renameAsset(file, newFile) { - const source = this.assets[file]; - if (!source) { - throw new Error( - `Called Compilation.renameAsset for not existing filename ${file}` - ); - } - if (this.assets[newFile] && !isSourceEqual(this.assets[file], source)) { - this.errors.push( - new WebpackError( - `Conflict: Called Compilation.renameAsset for already existing filename ${newFile} with different content` - ) - ); - } - const assetInfo = this.assetsInfo.get(file); - // Update related in all other assets - const relatedInInfo = this._assetsRelatedIn.get(file); - if (relatedInInfo) { - for (const [key, assets] of relatedInInfo) { - for (const name of assets) { - const info = this.assetsInfo.get(name); - if (!info) continue; - const related = info.related; - if (!related) continue; - const entry = related[key]; - 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 - } - }); - } - } - } - this._setAssetInfo(file, undefined, assetInfo); - this._setAssetInfo(newFile, assetInfo); - delete this.assets[file]; - this.assets[newFile] = source; - for (const chunk of this.chunks) { - { - const size = chunk.files.size; - chunk.files.delete(file); - if (size !== chunk.files.size) { - chunk.files.add(newFile); - } - } - { - const size = chunk.auxiliaryFiles.size; - chunk.auxiliaryFiles.delete(file); - if (size !== chunk.auxiliaryFiles.size) { - chunk.auxiliaryFiles.add(newFile); - } - } - } - } - - /** - * @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)) { - /** - * @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); - } - } - } - // TODO If this becomes a performance problem - // store a reverse mapping from asset to chunk - for (const chunk of this.chunks) { - chunk.files.delete(file); - chunk.auxiliaryFiles.delete(file); - } - } - - 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; - } - - /** - * @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 - }; - } - - clearAssets() { - for (const chunk of this.chunks) { - chunk.files.clear(); - chunk.auxiliaryFiles.clear(); - } - } - - 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); - } - } - } - } - - /** - * @param {RenderManifestOptions} options options object - * @returns {RenderManifestEntry[]} manifest entries - */ - getRenderManifest(options) { - return this.hooks.renderManifest.call([], options); - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - createChunkAssets(callback) { - const outputOptions = this.outputOptions; - 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: 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 = fileManifest.hash; - - const assetCacheItem = this._assetsCache.getItemCache( - ident, - usedHash - ); - - assetCacheItem.get((err, sourceFromCache) => { - /** @type {TemplatePath} */ - let filenameTemplate; - /** @type {string} */ - let file; - /** @type {AssetInfo} */ - let assetInfo; - - let inTry = true; - /** - * @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(); - }; - - 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; - } - - if (err) { - return errorAndCallback(err); - } - - 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 - ); - } - - /** - * @param {TemplatePath} filename used to get asset path with hash - * @param {PathData} data context data - * @returns {string} interpolated path - */ - getPath(filename, data = {}) { - if (!data.hash) { - data = { - hash: this.hash, - ...data - }; - } - return this.getAssetPath(filename, data); - } - - /** - * @param {TemplatePath} filename used to get asset path with hash - * @param {PathData} data context data - * @returns {InterpolatedPathAndAssetInfo} interpolated path and asset info - */ - getPathWithInfo(filename, data = {}) { - if (!data.hash) { - data = { - hash: this.hash, - ...data - }; - } - return this.getAssetPathWithInfo(filename, data); - } - - /** - * @param {TemplatePath} filename used to get asset path with hash - * @param {PathData} data context data - * @returns {string} interpolated path - */ - getAssetPath(filename, data) { - return this.hooks.assetPath.call( - typeof filename === "function" ? filename(data) : filename, - data, - undefined - ); - } - - /** - * @param {TemplatePath} filename used to get asset path with hash - * @param {PathData} 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 }; - } - - getWarnings() { - return this.hooks.processWarnings.call(this.warnings); - } - - 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 {Array=} 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; - return this.compiler.createChildCompiler( - this, - name, - idx, - outputOptions, - plugins - ); - } - - /** - * @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); - connectChunkGroupAndChunk(entrypoint, chunk); - 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 - ); - /** - * @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(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(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, - preparedInfo: undefined, - 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(err); - - let exports; - try { - const { - strictModuleErrorHandling, - strictModuleExceptionHandling - } = this.outputOptions; - 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__(moduleArgument, id); - }; - const interceptModuleExecution = (__webpack_require__[ - RuntimeGlobals.interceptModuleExecution.replace( - `${RuntimeGlobals.require}.`, - "" - ) - ] = []); - const moduleCache = (__webpack_require__[ - RuntimeGlobals.moduleCache.replace( - `${RuntimeGlobals.require}.`, - "" - ) - ] = {}); - - context.__webpack_require__ = __webpack_require__; - - /** - * @param {ExecuteModuleArgument} moduleArgument the module argument - * @param {string=} id id - * @returns {any} exports - */ - const __webpack_require_module__ = (moduleArgument, id) => { - 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 = execErr; - } - if (!execErr.module) 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 err = new WebpackError( - `Execution of module code from module graph (${module.readableIdentifier( - this.requestShortener - )}) failed: ${execErr.message}` - ); - err.stack = execErr.stack; - err.module = execErr.module; - return callback(err); - } - - callback(null, { - exports, - assets, - cacheable, - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - }); - } - ); - }); - }); - } - ); - } - - checkConstraints() { - const chunkGraph = this.chunkGraph; - - /** @type {Set} */ - const usedIds = new Set(); - - 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}`); - } - usedIds.add(moduleId); - } - - 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}` - ); - } - } - } - - for (const chunkGroup of this.chunkGroups) { - chunkGroup.checkConstraints(); - } - } -} - -/** - * @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 - */ - -/** - * @param {FactorizeModuleOptions} options options object - * @param {ModuleCallback | ModuleFactoryResultCallback} callback callback - * @returns {void} - */ - -// Workaround for typescript as it doesn't support function overloading in jsdoc within a class -/* eslint-disable jsdoc/require-asterisk-prefix */ -Compilation.prototype.factorizeModule = /** - @type {{ - (options: FactorizeModuleOptions & { factoryResult?: false }, callback: ModuleCallback): void; - (options: FactorizeModuleOptions & { factoryResult: true }, callback: ModuleFactoryResultCallback): void; -}} */ ( - function (options, callback) { - this.factorizeQueue.add(options, callback); - } -); -/* eslint-enable jsdoc/require-asterisk-prefix */ - -// 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 webpack 6 remove -Object.defineProperty(compilationPrototype, "cache", { - enumerable: false, - configurable: false, - get: util.deprecate( - /** - * @this {Compilation} the compilation - * @returns {Cache} the cache - */ - function () { - return this.compiler.cache; - }, - "Compilation.cache was removed in favor of Compilation.getCache()", - "DEP_WEBPACK_COMPILATION_CACHE" - ), - set: util.deprecate( - /** - * @param {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/webpack-lib/lib/Compiler.js b/webpack-lib/lib/Compiler.js deleted file mode 100644 index 99d466ec990..00000000000 --- a/webpack-lib/lib/Compiler.js +++ /dev/null @@ -1,1384 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const parseJson = require("json-parse-even-better-errors"); -const asyncLib = require("neo-async"); -const { - SyncHook, - SyncBailHook, - AsyncParallelHook, - AsyncSeriesHook -} = require("tapable"); -const { SizeOnlySource } = require("webpack-sources"); -const webpack = require("."); -const Cache = require("./Cache"); -const CacheFacade = require("./CacheFacade"); -const ChunkGraph = require("./ChunkGraph"); -const Compilation = require("./Compilation"); -const ConcurrentCompilationError = require("./ConcurrentCompilationError"); -const ContextModuleFactory = require("./ContextModuleFactory"); -const ModuleGraph = require("./ModuleGraph"); -const NormalModuleFactory = require("./NormalModuleFactory"); -const RequestShortener = require("./RequestShortener"); -const ResolverFactory = require("./ResolverFactory"); -const Stats = require("./Stats"); -const Watching = require("./Watching"); -const WebpackError = require("./WebpackError"); -const { Logger } = require("./logging/Logger"); -const { join, dirname, mkdirp } = require("./util/fs"); -const { makePathsRelative } = require("./util/identifier"); -const { isSourceEqual } = require("./util/source"); - -/** @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").WebpackPluginInstance} WebpackPluginInstance */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Compilation").References} References */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./config/target").PlatformTargetProperties} PlatformTargetProperties */ -/** @typedef {import("./logging/createConsoleLogger").LoggingFunction} LoggingFunction */ -/** @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").WatchFileSystem} WatchFileSystem */ - -/** - * @template {any[]} T - * @template V - * @typedef {import("./util/WeakTupleMap")} WeakTupleMap - */ - -/** - * @typedef {object} CompilationParams - * @property {NormalModuleFactory} normalModuleFactory - * @property {ContextModuleFactory} contextModuleFactory - */ - -/** - * @template T - * @callback RunCallback - * @param {Error | null} err - * @param {T=} result - */ - -/** - * @template T - * @callback Callback - * @param {(Error | null)=} err - * @param {T=} result - */ - -/** - * @callback RunAsChildCallback - * @param {Error | null} err - * @param {Chunk[]=} entries - * @param {Compilation=} compilation - */ - -/** - * @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: any, file: string }[] | undefined) }} SimilarEntry */ - -/** @typedef {{ buildInfo: BuildInfo, references: References | undefined, memCache: WeakTupleMap }} ModuleMemCachesItem */ - -/** - * @param {string[]} 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; -}; - -/** - * @param {{[key: string]: any}} obj an object - * @param {string[]} keys the keys of the object - * @returns {{[key: string]: any}} the object with properties sorted by property name - */ -const sortObject = (obj, keys) => { - /** @type {{[key: string]: any}} */ - const o = {}; - for (const k of keys.sort()) { - o[k] = obj[k]; - } - return o; -}; - -/** - * @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); -}; - -class 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([]), - /** @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"]), - /** @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, 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<[]>} */ - 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.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 {Record} */ - 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 {ReadonlyMap | undefined} */ - this.fileTimestamps = undefined; - /** @type {ReadonlyMap | undefined} */ - this.contextTimestamps = undefined; - /** @type {number | undefined} */ - this.fsStartTime = undefined; - - /** @type {ResolverFactory} */ - this.resolverFactory = new ResolverFactory(); - - /** @type {LoggingFunction | undefined} */ - this.infrastructureLogger = undefined; - - /** @type {Readonly} */ - this.platform = { - web: null, - browser: null, - webworker: null, - node: null, - nwjs: null, - electron: null - }; - - this.options = options; - - 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(); - } - - /** - * @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 - ); - } - - /** - * @param {string | (function(): 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); - } - }, - 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}`; - }); - } - 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}`; - }); - } - return this.getInfrastructureLogger(`${name}/${childName}`); - } - ); - } - - // 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); - } - } - - 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; - } - } - - // TODO webpack 6: solve this in a better way - _cleanupLastNormalModuleFactory() { - if (this._lastNormalModuleFactory !== undefined) { - this._lastNormalModuleFactory.cleanupForCache(); - this._lastNormalModuleFactory = undefined; - } - } - - /** - * @param {WatchOptions} watchOptions the watcher's options - * @param {RunCallback} handler signals when the call finishes - * @returns {Watching} a compiler watcher - */ - watch(watchOptions, handler) { - if (this.running) { - return handler(new ConcurrentCompilationError()); - } - - this.running = true; - this.watchMode = true; - this.watching = new Watching(this, watchOptions, handler); - return this.watching; - } - - /** - * @param {RunCallback} callback signals when the call finishes - * @returns {void} - */ - run(callback) { - if (this.running) { - return callback(new ConcurrentCompilationError()); - } - - /** @type {Logger | undefined} */ - let logger; - - /** - * @param {Error | null} err error - * @param {Stats=} stats stats - */ - const finalCallback = (err, stats) => { - if (logger) logger.time("beginIdle"); - this.idle = true; - this.cache.beginIdle(); - this.idle = true; - if (logger) logger.timeEnd("beginIdle"); - this.running = false; - 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; - - /** - * @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); - this.hooks.done.callAsync(stats, err => { - if (err) return finalCallback(err); - return finalCallback(null, stats); - }); - return; - } - - 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; - - compilation.startTime = startTime; - compilation.endTime = Date.now(); - /** @type {Logger} */ - (logger).time("done hook"); - const stats = new Stats(compilation); - this.hooks.done.callAsync(stats, err => { - /** @type {Logger} */ - (logger).timeEnd("done hook"); - if (err) return finalCallback(err); - - this.hooks.additionalPass.callAsync(err => { - if (err) return finalCallback(err); - this.compile(onCompiled); - }); - }); - return; - } - - /** @type {Logger} */ - (logger).time("emitRecords"); - this.emitRecords(err => { - /** @type {Logger} */ - (logger).timeEnd("emitRecords"); - if (err) return finalCallback(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.cache.storeBuildDependencies( - compilation.buildDependencies, - err => { - if (err) return finalCallback(err); - return finalCallback(null, stats); - } - ); - }); - }); - }); - }); - }; - - const run = () => { - this.hooks.beforeRun.callAsync(this, err => { - if (err) return finalCallback(err); - - this.hooks.run.callAsync(this, err => { - if (err) return finalCallback(err); - - this.readRecords(err => { - if (err) return finalCallback(err); - - this.compile(onCompiled); - }); - }); - }); - }; - - if (this.idle) { - this.cache.endIdle(err => { - if (err) return finalCallback(err); - - this.idle = false; - run(); - }); - } else { - run(); - } - } - - /** - * @param {RunAsChildCallback} callback signals when the call finishes - * @returns {void} - */ - runAsChild(callback) { - const startTime = Date.now(); - - /** - * @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}` - ); - err.details = /** @type {Error} */ (runAsChildErr).stack; - /** @type {Compilation} */ - (this.parentCompilation).errors.push(err); - } - }; - - this.compile((err, _compilation) => { - if (err) return finalCallback(err); - - const compilation = /** @type {Compilation} */ (_compilation); - const parentCompilation = /** @type {Compilation} */ ( - this.parentCompilation - ); - - parentCompilation.children.push(compilation); - - for (const { name, source, info } of compilation.getAssets()) { - parentCompilation.emitAsset(name, source, info); - } - - /** @type {Chunk[]} */ - const entries = []; - - for (const ep of compilation.entrypoints.values()) { - entries.push(...ep.chunks); - } - - compilation.startTime = startTime; - compilation.endTime = Date.now(); - - return finalCallback(null, entries, compilation); - }); - } - - purgeInputFileSystem() { - if (this.inputFileSystem && this.inputFileSystem.purge) { - this.inputFileSystem.purge(); - } - } - - /** - * @param {Compilation} compilation the compilation - * @param {Callback} callback signals when the assets are emitted - * @returns {void} - */ - emitAssets(compilation, callback) { - /** @type {string} */ - let outputPath; - - /** - * @param {Error=} err error - * @returns {void} - */ - const emitFiles = err => { - if (err) return callback(err); - - 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; - let immutable = info.immutable; - const queryStringIdx = targetFile.indexOf("?"); - if (queryStringIdx >= 0) { - targetFile = targetFile.slice(0, queryStringIdx); - // 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)); - } - - /** - * @param {Error=} err error - * @returns {void} - */ - const writeOut = err => { - if (err) return callback(err); - const targetPath = join( - /** @type {OutputFileSystem} */ - (this.outputFileSystem), - outputPath, - targetFile - ); - 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, - writtenTo: new Map() - }; - this._assetEmittingSourceCache.set(source, cacheEntry); - } - - /** @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 - ); - }); - }; - - /** - * @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); - } - } - }; - - /** - * @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 - }); - }; - - /** - * @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(); - } - } - - 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 (/\/|\\/.test(targetFile)) { - const fs = /** @type {OutputFileSystem} */ (this.outputFileSystem); - const dir = dirname(fs, join(fs, outputPath, targetFile)); - mkdirp(fs, dir, writeOut); - } else { - writeOut(); - } - }, - 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 => { - if (err) return callback(err); - - return callback(); - }); - } - ); - }; - - this.hooks.emit.callAsync(compilation, err => { - if (err) return callback(err); - outputPath = compilation.getPath(this.outputPath, {}); - mkdirp( - /** @type {OutputFileSystem} */ (this.outputFileSystem), - outputPath, - emitFiles - ); - }); - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - emitRecords(callback) { - if (this.hooks.emitRecords.isUsed()) { - if (this.recordsOutputPath) { - asyncLib.parallel( - [ - cb => this.hooks.emitRecords.callAsync(cb), - this._emitRecords.bind(this) - ], - err => callback(err) - ); - } else { - this.hooks.emitRecords.callAsync(callback); - } - } else if (this.recordsOutputPath) { - this._emitRecords(callback); - } else { - callback(); - } - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - _emitRecords(callback) { - const writeFile = () => { - /** @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(); - } - mkdirp( - /** @type {OutputFileSystem} */ (this.outputFileSystem), - recordsOutputPathDirectory, - err => { - if (err) return callback(err); - writeFile(); - } - ); - } - - /** - * @param {Callback} 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(err) - ); - } else { - this.records = {}; - this.hooks.readRecords.callAsync(callback); - } - } else if (this.recordsInputPath) { - this._readRecords(callback); - } else { - this.records = {}; - callback(); - } - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - _readRecords(callback) { - if (!this.recordsInputPath) { - this.records = {}; - return callback(); - } - /** @type {InputFileSystem} */ - (this.inputFileSystem).stat(this.recordsInputPath, err => { - // It doesn't exist - // We can ignore this. - if (err) return callback(); - - /** @type {InputFileSystem} */ - (this.inputFileSystem).readFile( - /** @type {string} */ (this.recordsInputPath), - (err, content) => { - if (err) return callback(err); - - try { - this.records = parseJson( - /** @type {Buffer} */ (content).toString("utf-8") - ); - } catch (parseErr) { - return callback( - new Error( - `Cannot parse records: ${/** @type {Error} */ (parseErr).message}` - ) - ); - } - - return callback(); - } - ); - }); - } - - /** - * @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 {WebpackPluginInstance[]=} plugins the plugins to apply - * @returns {Compiler} a child compiler - */ - createChildCompiler( - compilation, - compilerName, - compilerIndex, - outputOptions, - plugins - ) { - 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, - this.root - ); - if (!this.records[relativeCompilerName]) { - this.records[relativeCompilerName] = []; - } - if (this.records[relativeCompilerName][compilerIndex]) { - childCompiler.records = this.records[relativeCompilerName][compilerIndex]; - } else { - this.records[relativeCompilerName].push((childCompiler.records = {})); - } - - childCompiler.parentCompilation = compilation; - childCompiler.root = this.root; - if (Array.isArray(plugins)) { - for (const plugin of plugins) { - 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.slice(); - } - } - - compilation.hooks.childCompiler.call( - childCompiler, - compilerName, - compilerIndex - ); - - return childCompiler; - } - - isChild() { - return Boolean(this.parentCompilation); - } - - /** - * @param {CompilationParams} params the compilation parameters - * @returns {Compilation} compilation - */ - createCompilation(params) { - this._cleanupLastCompilation(); - return (this._lastCompilation = new Compilation(this, params)); - } - - /** - * @param {CompilationParams} params the compilation parameters - * @returns {Compilation} the created compilation - */ - newCompilation(params) { - const compilation = this.createCompilation(params); - compilation.name = this.name; - compilation.records = this.records; - this.hooks.thisCompilation.call(compilation, params); - this.hooks.compilation.call(compilation, params); - return compilation; - } - - createNormalModuleFactory() { - 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, - layers: this.options.experiments.layers - }); - this._lastNormalModuleFactory = normalModuleFactory; - this.hooks.normalModuleFactory.call(normalModuleFactory); - return normalModuleFactory; - } - - createContextModuleFactory() { - const contextModuleFactory = new ContextModuleFactory(this.resolverFactory); - this.hooks.contextModuleFactory.call(contextModuleFactory); - return contextModuleFactory; - } - - newCompilationParams() { - const params = { - normalModuleFactory: this.createNormalModuleFactory(), - contextModuleFactory: this.createContextModuleFactory() - }; - return params; - } - - /** - * @param {RunCallback} callback signals when the compilation finishes - * @returns {void} - */ - compile(callback) { - const params = this.newCompilationParams(); - this.hooks.beforeCompile.callAsync(params, err => { - if (err) return callback(err); - - this.hooks.compile.call(params); - - const compilation = this.newCompilation(params); - - const logger = compilation.getLogger("webpack.Compiler"); - - logger.time("make hook"); - this.hooks.make.callAsync(compilation, err => { - logger.timeEnd("make hook"); - if (err) return callback(err); - - logger.time("finish make hook"); - this.hooks.finishMake.callAsync(compilation, err => { - logger.timeEnd("finish make hook"); - if (err) return callback(err); - - 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); - }); - }); - }); - }); - }); - }); - }); - } - - /** - * @param {RunCallback} 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); - // Get rid of reference to last compilation to avoid leaking memory - // We can't run this._cleanupLastCompilation() as the Stats to this compilation - // might be still in use. We try to get rid of the reference to the cache instead. - this._lastCompilation = undefined; - this._lastNormalModuleFactory = undefined; - this.cache.shutdown(callback); - }); - } -} - -module.exports = Compiler; diff --git a/webpack-lib/lib/ConcatenationScope.js b/webpack-lib/lib/ConcatenationScope.js deleted file mode 100644 index 5c7bb6fd0dc..00000000000 --- a/webpack-lib/lib/ConcatenationScope.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - 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("./Module")} Module */ -/** @typedef {import("./optimize/ConcatenatedModule").ConcatenatedModuleInfo} ConcatenatedModuleInfo */ -/** @typedef {import("./optimize/ConcatenatedModule").ModuleInfo} ModuleInfo */ - -const MODULE_REFERENCE_REGEXP = - /^__WEBPACK_MODULE_REFERENCE__(\d+)_([\da-f]+|ns)(_call)?(_directImport)?(?:_asiSafe(\d))?__$/; - -/** - * @typedef {object} ModuleReferenceOptions - * @property {string[]} ids the properties/exports of the 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 | undefined} asiSafe if the position is ASI safe or unknown - */ - -class ConcatenationScope { - /** - * @param {ModuleInfo[] | Map} modulesMap all module info by module - * @param {ConcatenatedModuleInfo} currentModule the current module info - */ - constructor(modulesMap, currentModule) { - this._currentModule = currentModule; - if (Array.isArray(modulesMap)) { - const map = new Map(); - for (const info of modulesMap) { - map.set(info.module, info); - } - modulesMap = map; - } - this._modulesMap = modulesMap; - } - - /** - * @param {Module} module the referenced module - * @returns {boolean} true, when it's in the scope - */ - isModuleInScope(module) { - return this._modulesMap.has(module); - } - - /** - * @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); - } - } - - /** - * @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); - } - } - - /** - * @param {string} symbol identifier of the export in source code - */ - registerNamespaceExport(symbol) { - this._currentModule.namespaceExportSymbol = symbol; - } - - /** - * @param {Module} module the referenced module - * @param {Partial} options options - * @returns {string} the reference as identifier - */ - createModuleReference( - module, - { ids = undefined, call = false, directImport = false, asiSafe = false } - ) { - const info = /** @type {ModuleInfo} */ (this._modulesMap.get(module)); - const callFlag = call ? "_call" : ""; - const directImportFlag = directImport ? "_directImport" : ""; - const asiSafeFlag = asiSafe - ? "_asiSafe1" - : asiSafe === false - ? "_asiSafe0" - : ""; - const exportData = ids - ? Buffer.from(JSON.stringify(ids), "utf-8").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}${asiSafeFlag}__._`; - } - - /** - * @param {string} name the identifier - * @returns {boolean} true, when it's an module reference - */ - static isModuleReference(name) { - return MODULE_REFERENCE_REGEXP.test(name); - } - - /** - * @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[5]; - return { - index, - ids: - match[2] === "ns" - ? [] - : JSON.parse(Buffer.from(match[2], "hex").toString("utf-8")), - call: Boolean(match[3]), - directImport: Boolean(match[4]), - asiSafe: asiSafe ? asiSafe === "1" : undefined - }; - } -} - -ConcatenationScope.DEFAULT_EXPORT = DEFAULT_EXPORT; -ConcatenationScope.NAMESPACE_OBJECT_EXPORT = NAMESPACE_OBJECT_EXPORT; - -module.exports = ConcatenationScope; diff --git a/webpack-lib/lib/ConcurrentCompilationError.js b/webpack-lib/lib/ConcurrentCompilationError.js deleted file mode 100644 index 3643553f050..00000000000 --- a/webpack-lib/lib/ConcurrentCompilationError.js +++ /dev/null @@ -1,18 +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."; - } -}; diff --git a/webpack-lib/lib/ConditionalInitFragment.js b/webpack-lib/lib/ConditionalInitFragment.js deleted file mode 100644 index 67351383d95..00000000000 --- a/webpack-lib/lib/ConditionalInitFragment.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - 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 */ - -/** - * @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" - ); -}; - -/** - * @extends {InitFragment} - */ -class ConditionalInitFragment extends 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 {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; - } - - /** - * @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); - } - - /** - * @param {GenerateContext} context context - * @returns {string|Source=} 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); - } - - /** - * @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/webpack-lib/lib/ConstPlugin.js b/webpack-lib/lib/ConstPlugin.js deleted file mode 100644 index 63ed2622de6..00000000000 --- a/webpack-lib/lib/ConstPlugin.js +++ /dev/null @@ -1,537 +0,0 @@ -/* - 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 { evaluateToString } = require("./javascript/JavascriptParserHelpers"); -const { parseResource } = require("./util/identifier"); - -/** @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("./Compiler")} Compiler */ -/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ - -/** - * @param {Set} 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 = /** @type {Pattern} */ (stack.pop()); - switch (node.type) { - case "Identifier": - declarations.add(node.name); - break; - case "ArrayPattern": - for (const element of node.elements) { - if (element) { - stack.push(element); - } - } - break; - case "AssignmentPattern": - stack.push(node.left); - break; - case "ObjectPattern": - for (const property of node.properties) { - stack.push(/** @type {AssignmentProperty} */ (property).value); - } - break; - case "RestElement": - stack.push(node.argument); - break; - } - } -}; - -/** - * @param {Statement} branch branch to get hoisted declarations from - * @param {boolean} includeFunctionDeclarations whether to include function declarations - * @returns {Array} hoisted declarations - */ -const getHoistedDeclarations = (branch, includeFunctionDeclarations) => { - const declarations = new Set(); - /** @type {Array} */ - const stack = [branch]; - while (stack.length > 0) { - const node = stack.pop(); - // Some node could be `null` or `undefined`. - if (!node) continue; - switch (node.type) { - // Walk through control statements to look for hoisted declarations. - // Some branches are skipped since they do not allow declarations. - case "BlockStatement": - for (const stmt of node.body) { - stack.push(stmt); - } - break; - case "IfStatement": - stack.push(node.consequent); - stack.push(node.alternate); - break; - case "ForStatement": - stack.push(node.init); - stack.push(node.body); - break; - case "ForInStatement": - case "ForOfStatement": - stack.push(node.left); - stack.push(node.body); - break; - case "DoWhileStatement": - case "WhileStatement": - case "LabeledStatement": - stack.push(node.body); - break; - case "SwitchStatement": - for (const cs of node.cases) { - for (const consequent of cs.consequent) { - stack.push(consequent); - } - } - break; - case "TryStatement": - stack.push(node.block); - if (node.handler) { - stack.push(node.handler.body); - } - stack.push(node.finalizer); - break; - case "FunctionDeclaration": - if (includeFunctionDeclarations) { - collectDeclaration(declarations, /** @type {Identifier} */ (node.id)); - } - break; - case "VariableDeclaration": - if (node.kind === "var") { - for (const decl of node.declarations) { - collectDeclaration(declarations, decl.id); - } - } - break; - } - } - return Array.from(declarations); -}; - -const PLUGIN_NAME = "ConstPlugin"; - -class ConstPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const cachedParseResource = parseResource.bindCache(compiler.root); - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - - compilation.dependencyTemplates.set( - CachedConstDependency, - new CachedConstDependency.Template() - ); - - /** - * @param {JavascriptParser} parser the parser - */ - const handler = parser => { - 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 (!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. - // - const declarations = parser.scope.isStrict - ? getHoistedDeclarations(branchToRemove, false) - : getHoistedDeclarations(branchToRemove, true); - const replacement = - declarations.length > 0 - ? `{ var ${declarations.join(", ")}; }` - : "{}"; - const dep = new ConstDependency( - replacement, - /** @type {Range} */ (branchToRemove.range) - ); - dep.loc = /** @type {SourceLocation} */ (branchToRemove.loc); - parser.state.module.addPresentationalDependency(dep); - } - return bool; - } - }); - parser.hooks.expressionConditionalOperator.tap( - PLUGIN_NAME, - expression => { - if (parser.scope.isAsmJs) return; - const param = parser.evaluateExpression(expression.test); - const bool = param.asBool(); - if (typeof bool === "boolean") { - 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. - // - // Given the following code: - // - // false ? someExpression() : otherExpression(); - // - // the generated code is: - // - // false ? 0 : otherExpression(); - // - const branchToRemove = bool - ? expression.alternate - : expression.consequent; - const dep = new ConstDependency( - "0", - /** @type {Range} */ (branchToRemove.range) - ); - 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(PLUGIN_NAME, expr => { - if (parser.scope.isAsmJs) return; - if (!parser.state.module) return; - return evaluateToString( - cachedParseResource(parser.state.module.resource).query - )(expr); - }); - parser.hooks.expression - .for("__resourceQuery") - .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; - 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_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 = ConstPlugin; diff --git a/webpack-lib/lib/ContextExclusionPlugin.js b/webpack-lib/lib/ContextExclusionPlugin.js deleted file mode 100644 index 8b291072c2b..00000000000 --- a/webpack-lib/lib/ContextExclusionPlugin.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./ContextModuleFactory")} ContextModuleFactory */ - -class ContextExclusionPlugin { - /** - * @param {RegExp} negativeMatcher Matcher regular expression - */ - constructor(negativeMatcher) { - this.negativeMatcher = negativeMatcher; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.contextModuleFactory.tap("ContextExclusionPlugin", cmf => { - cmf.hooks.contextModuleFiles.tap("ContextExclusionPlugin", files => - files.filter(filePath => !this.negativeMatcher.test(filePath)) - ); - }); - } -} - -module.exports = ContextExclusionPlugin; diff --git a/webpack-lib/lib/ContextModule.js b/webpack-lib/lib/ContextModule.js deleted file mode 100644 index 0ad81bd0b2a..00000000000 --- a/webpack-lib/lib/ContextModule.js +++ /dev/null @@ -1,1253 +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 AsyncDependenciesBlock = require("./AsyncDependenciesBlock"); -const { makeWebpackError } = require("./HookWebpackError"); -const Module = require("./Module"); -const { JS_TYPES } = require("./ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const Template = require("./Template"); -const WebpackError = require("./WebpackError"); -const { - compareLocations, - concatComparators, - compareSelect, - keepOriginalOrder, - compareModulesById -} = require("./util/comparators"); -const { - contextify, - parseResource, - makePathsRelative -} = require("./util/identifier"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Chunk").ChunkId} ChunkId */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./Module").BuildMeta} BuildMeta */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @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} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @template T @typedef {import("./util/LazySet")} LazySet */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -/** @typedef {"sync" | "eager" | "weak" | "async-weak" | "lazy" | "lazy-once"} ContextMode Context mode */ - -/** - * @typedef {object} ContextOptions - * @property {ContextMode} mode - * @property {boolean} recursive - * @property {RegExp} regExp - * @property {("strict" | boolean)=} namespaceObject - * @property {string=} addon - * @property {(string | null)=} chunkName - * @property {(RegExp | null)=} include - * @property {(RegExp | null)=} exclude - * @property {RawChunkGroupOptions=} groupOptions - * @property {string=} typePrefix - * @property {string=} category - * @property {(string[][] | null)=} referencedExports exports referenced from modules (won't be mangled) - * @property {string=} layer - * @property {ImportAttributes=} attributes - */ - -/** - * @typedef {object} ContextModuleOptionsExtras - * @property {false|string|string[]} resource - * @property {string=} resourceQuery - * @property {string=} resourceFragment - * @property {TODO} resolveOptions - */ - -/** @typedef {ContextOptions & ContextModuleOptionsExtras} ContextModuleOptions */ - -/** - * @callback ResolveDependenciesCallback - * @param {Error | null} err - * @param {ContextElementDependency[]=} dependencies - */ - -/** - * @callback ResolveDependencies - * @param {InputFileSystem} fs - * @param {ContextModuleOptions} options - * @param {ResolveDependenciesCallback} callback - */ - -/** @typedef {1 | 3 | 7 | 9} FakeMapType */ - -/** @typedef {Record} FakeMap */ - -const SNAPSHOT_OPTIONS = { timestamp: true }; - -class ContextModule extends Module { - /** - * @param {ResolveDependencies} resolveDependencies function to get dependencies in this context - * @param {ContextModuleOptions} options options object - */ - constructor(resolveDependencies, options) { - 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 { - super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, undefined, options.layer); - /** @type {ContextModuleOptions} */ - this.options = { - ...options, - resource: options.resource, - resourceQuery: options.resourceQuery || "", - resourceFragment: options.resourceFragment || "" - }; - } - - // Info from Factory - /** @type {ResolveDependencies | undefined} */ - this.resolveDependencies = resolveDependencies; - if (options && options.resolveOptions !== undefined) { - this.resolveOptions = options.resolveOptions; - } - - if (options && typeof options.mode !== "string") { - throw new Error("options.mode is a required option"); - } - - this._identifier = this._createIdentifier(); - this._forceBuild = true; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_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) { - const m = /** @type {ContextModule} */ (module); - this.resolveDependencies = m.resolveDependencies; - this.options = m.options; - } - - /** - * Assuming this module is in the cache. Remove internal references to allow freeing some memory. - */ - cleanupForCache() { - super.cleanupForCache(); - this.resolveDependencies = undefined; - } - - /** - * @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 || - (typeof this.options.resource === "string" || - this.options.resource === false - ? `${this.options.resource}` - : this.options.resource.join("|")); - if (this.options.resourceQuery) { - identifier += `|${this.options.resourceQuery}`; - } - if (this.options.resourceFragment) { - identifier += `|${this.options.resourceFragment}`; - } - if (this.options.mode) { - identifier += `|${this.options.mode}`; - } - if (!this.options.recursive) { - identifier += "|nonrecursive"; - } - if (this.options.addon) { - identifier += `|${this.options.addon}`; - } - if (this.options.regExp) { - identifier += `|${this._prettyRegExp(this.options.regExp, false)}`; - } - if (this.options.include) { - identifier += `|include: ${this._prettyRegExp( - this.options.include, - false - )}`; - } - if (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( - this.options.groupOptions - )}`; - } - if (this.options.namespaceObject === "strict") { - identifier += "|strict namespace object"; - } else if (this.options.namespaceObject) { - identifier += "|namespace object"; - } - if (this.layer) { - identifier += `|layer: ${this.layer}`; - } - - return identifier; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this._identifier; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - 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}`; - } - if (this.options.mode) { - identifier += ` ${this.options.mode}`; - } - if (!this.options.recursive) { - identifier += " nonrecursive"; - } - if (this.options.addon) { - identifier += ` ${requestShortener.shorten(this.options.addon)}`; - } - if (this.options.regExp) { - identifier += ` ${this._prettyRegExp(this.options.regExp)}`; - } - if (this.options.include) { - identifier += ` include: ${this._prettyRegExp(this.options.include)}`; - } - if (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[/** @type {keyof RawChunkGroupOptions} */ (key)] - }`; - } - } - if (this.options.namespaceObject === "strict") { - identifier += " strict namespace object"; - } else if (this.options.namespaceObject) { - identifier += " namespace object"; - } - - return identifier; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - 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}`; - } - if (this.options.recursive) { - identifier += " recursive"; - } - if (this.options.addon) { - identifier += ` ${contextify( - options.context, - this.options.addon, - options.associatedObjectForCache - )}`; - } - if (this.options.regExp) { - identifier += ` ${this._prettyRegExp(this.options.regExp)}`; - } - if (this.options.include) { - identifier += ` include: ${this._prettyRegExp(this.options.include)}`; - } - if (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; - } - - /** - * @returns {void} - */ - invalidateBuild() { - this._forceBuild = true; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} 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 {BuildInfo} */ (this.buildInfo); - - // always build when we have no snapshot and context - if (!buildInfo.snapshot) - return callback(null, Boolean(this.context || this.options.resource)); - - fileSystemInfo.checkSnapshotValid(buildInfo.snapshot, (err, valid) => { - callback(err, !valid); - }); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this._forceBuild = false; - /** @type {BuildMeta} */ - this.buildMeta = { - exportsType: "default", - defaultObject: "redirect-warn" - }; - this.buildInfo = { - snapshot: undefined - }; - 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 - if (!dependencies) { - callback(); - return; - } - - // enhance dependencies with meta info - for (const dep of dependencies) { - 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 - // just add all dependencies and continue - this.dependencies = dependencies; - } else if (this.options.mode === "lazy-once") { - // 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({ - ...this.options.groupOptions, - name: this.options.chunkName - }); - for (const dep of dependencies) { - block.addDependency(dep); - } - this.addBlock(block); - } - } else if ( - this.options.mode === "weak" || - this.options.mode === "async-weak" - ) { - // we mark all dependencies as weak - for (const dep of dependencies) { - dep.weak = true; - } - this.dependencies = dependencies; - } else if (this.options.mode === "lazy") { - // if we are lazy create a new async dependency block per dependency - // and add all blocks to this context - let index = 0; - for (const dep of dependencies) { - let chunkName = this.options.chunkName; - if (chunkName) { - if (!/\[(index|request)\]/.test(chunkName)) { - chunkName += "[index]"; - } - chunkName = chunkName.replace(/\[index\]/g, `${index++}`); - chunkName = chunkName.replace( - /\[request\]/g, - Template.toPath(dep.userRequest) - ); - } - const block = new AsyncDependenciesBlock( - { - ...this.options.groupOptions, - name: chunkName - }, - dep.loc, - dep.userRequest - ); - block.addDependency(dep); - this.addBlock(block); - } - } else { - callback( - new WebpackError(`Unsupported mode "${this.options.mode}" in context`) - ); - return; - } - if (!this.context && !this.options.resource) return callback(); - - compilation.fileSystemInfo.createSnapshot( - startTime, - null, - this.context - ? [this.context] - : typeof this.options.resource === "string" - ? [this.options.resource] - : /** @type {string[]} */ (this.options.resource), - null, - SNAPSHOT_OPTIONS, - (err, snapshot) => { - if (err) return callback(err); - /** @type {BuildInfo} */ - (this.buildInfo).snapshot = snapshot; - callback(); - } - ); - }); - } - - /** - * @param {LazySet} fileDependencies set where file dependencies are added to - * @param {LazySet} contextDependencies set where context dependencies are added to - * @param {LazySet} missingDependencies set where missing dependencies are added to - * @param {LazySet} 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 {Map} map with user requests - */ - getUserRequestMap(dependencies, chunkGraph) { - const moduleGraph = chunkGraph.moduleGraph; - // 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 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; - }); - const map = Object.create(null); - for (const dep of sortedDependencies) { - const module = /** @type {Module} */ (moduleGraph.getModule(dep)); - map[dep.userRequest] = chunkGraph.getModuleId(module); - } - return map; - } - - /** - * @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 - // 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 {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 (hasType === 1) { - return 9; - } - if (hasType === 2) { - return 7; - } - if (hasType === 4) { - return 1; - } - if (hasType === 8) { - return 3; - } - if (hasType === 0) { - return 9; - } - return fakeMap; - } - - /** - * @param {FakeMap | FakeMapType} fakeMap fake map - * @returns {string} fake map init statement - */ - getFakeMapInitStatement(fakeMap) { - return typeof fakeMap === "object" - ? `var fakeMap = ${JSON.stringify(fakeMap, null, "\t")};` - : ""; - } - - /** - * @param {FakeMapType} type type - * @param {boolean=} asyncModule is async module - * @returns {string} return result - */ - getReturn(type, asyncModule) { - if (type === 9) { - return `${RuntimeGlobals.require}(id)`; - } - return `${RuntimeGlobals.createFakeNamespaceObject}(id, ${type}${ - asyncModule ? " | 16" : "" - })`; - } - - /** - * @param {FakeMap | FakeMapType} fakeMap fake map - * @param {boolean=} asyncModule us async module - * @param {string=} fakeMapDataExpression fake map data expression - * @returns {string} module object source - */ - getReturnModuleObjectSource( - fakeMap, - asyncModule, - fakeMapDataExpression = "fakeMap[id]" - ) { - if (typeof fakeMap === "number") { - return `return ${this.getReturn(fakeMap, asyncModule)};`; - } - return `return ${ - RuntimeGlobals.createFakeNamespaceObject - }(id, ${fakeMapDataExpression}${asyncModule ? " | 16" : ""})`; - } - - /** - * @param {Dependency[]} dependencies dependencies - * @param {ModuleId} id module id - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {string} source code - */ - getSyncSource(dependencies, id, chunkGraph) { - const map = this.getUserRequestMap(dependencies, chunkGraph); - const fakeMap = this.getFakeMap(dependencies, chunkGraph); - const returnModuleObject = this.getReturnModuleObjectSource(fakeMap); - - return `var map = ${JSON.stringify(map, null, "\t")}; -${this.getFakeMapInitStatement(fakeMap)} - -function webpackContext(req) { - var id = webpackContextResolve(req); - ${returnModuleObject} -} -function webpackContextResolve(req) { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - return map[req]; -} -webpackContext.keys = function webpackContextKeys() { - return Object.keys(map); -}; -webpackContext.resolve = webpackContextResolve; -module.exports = webpackContext; -webpackContext.id = ${JSON.stringify(id)};`; - } - - /** - * @param {Dependency[]} dependencies dependencies - * @param {ModuleId} id module id - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {string} source code - */ - getWeakSyncSource(dependencies, id, chunkGraph) { - const map = this.getUserRequestMap(dependencies, chunkGraph); - const fakeMap = this.getFakeMap(dependencies, chunkGraph); - const returnModuleObject = this.getReturnModuleObjectSource(fakeMap); - - return `var map = ${JSON.stringify(map, null, "\t")}; -${this.getFakeMapInitStatement(fakeMap)} - -function webpackContext(req) { - var id = webpackContextResolve(req); - if(!${RuntimeGlobals.moduleFactories}[id]) { - var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - ${returnModuleObject} -} -function webpackContextResolve(req) { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - return map[req]; -} -webpackContext.keys = function webpackContextKeys() { - return Object.keys(map); -}; -webpackContext.resolve = webpackContextResolve; -webpackContext.id = ${JSON.stringify(id)}; -module.exports = webpackContext;`; - } - - /** - * @param {Dependency[]} dependencies dependencies - * @param {ModuleId} id module id - * @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, { chunkGraph, runtimeTemplate }) { - const arrow = runtimeTemplate.supportsArrowFunction(); - const map = this.getUserRequestMap(dependencies, chunkGraph); - const fakeMap = this.getFakeMap(dependencies, chunkGraph); - const returnModuleObject = this.getReturnModuleObjectSource(fakeMap, true); - - return `var map = ${JSON.stringify(map, null, "\t")}; -${this.getFakeMapInitStatement(fakeMap)} - -function webpackAsyncContext(req) { - return webpackAsyncContextResolve(req).then(${ - arrow ? "id =>" : "function(id)" - } { - if(!${RuntimeGlobals.moduleFactories}[id]) { - var e = new Error("Module '" + req + "' ('" + id + "') is not available (weak dependency)"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - ${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(${arrow ? "() =>" : "function()"} { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - return map[req]; - }); -} -webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( - "Object.keys(map)" - )}; -webpackAsyncContext.resolve = webpackAsyncContextResolve; -webpackAsyncContext.id = ${JSON.stringify(id)}; -module.exports = webpackAsyncContext;`; - } - - /** - * @param {Dependency[]} dependencies dependencies - * @param {ModuleId} id module id - * @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, { chunkGraph, runtimeTemplate }) { - const arrow = runtimeTemplate.supportsArrowFunction(); - const map = this.getUserRequestMap(dependencies, chunkGraph); - const fakeMap = this.getFakeMap(dependencies, chunkGraph); - const thenFunction = - fakeMap !== 9 - ? `${arrow ? "id =>" : "function(id)"} { - ${this.getReturnModuleObjectSource(fakeMap, true)} - }` - : RuntimeGlobals.require; - return `var map = ${JSON.stringify(map, null, "\t")}; -${this.getFakeMapInitStatement(fakeMap)} - -function webpackAsyncContext(req) { - return webpackAsyncContextResolve(req).then(${thenFunction}); -} -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(${arrow ? "() =>" : "function()"} { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - return map[req]; - }); -} -webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( - "Object.keys(map)" - )}; -webpackAsyncContext.resolve = webpackAsyncContextResolve; -webpackAsyncContext.id = ${JSON.stringify(id)}; -module.exports = webpackAsyncContext;`; - } - - /** - * @param {AsyncDependenciesBlock} block block - * @param {Dependency[]} dependencies dependencies - * @param {ModuleId} id module id - * @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, { runtimeTemplate, chunkGraph }) { - const promise = runtimeTemplate.blockPromise({ - chunkGraph, - block, - message: "lazy-once context", - runtimeRequirements: new Set() - }); - const arrow = runtimeTemplate.supportsArrowFunction(); - const map = this.getUserRequestMap(dependencies, chunkGraph); - const fakeMap = this.getFakeMap(dependencies, chunkGraph); - const thenFunction = - fakeMap !== 9 - ? `${arrow ? "id =>" : "function(id)"} { - ${this.getReturnModuleObjectSource(fakeMap, true)}; - }` - : RuntimeGlobals.require; - - return `var map = ${JSON.stringify(map, null, "\t")}; -${this.getFakeMapInitStatement(fakeMap)} - -function webpackAsyncContext(req) { - return webpackAsyncContextResolve(req).then(${thenFunction}); -} -function webpackAsyncContextResolve(req) { - return ${promise}.then(${arrow ? "() =>" : "function()"} { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - return map[req]; - }); -} -webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( - "Object.keys(map)" - )}; -webpackAsyncContext.resolve = webpackAsyncContextResolve; -webpackAsyncContext.id = ${JSON.stringify(id)}; -module.exports = webpackAsyncContext;`; - } - - /** - * @param {AsyncDependenciesBlock[]} blocks blocks - * @param {ModuleId} id module id - * @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, { chunkGraph, runtimeTemplate }) { - const moduleGraph = chunkGraph.moduleGraph; - const arrow = runtimeTemplate.supportsArrowFunction(); - let hasMultipleOrNoChunks = false; - let hasNoChunk = 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}} 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 - }; - }) - .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 shortMode = hasNoChunk && !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 | ChunkId)[]} */ - const arrayStart = [moduleId]; - if (hasFakeMap) { - arrayStart.push(fakeMap[moduleId]); - } - map[item.userRequest] = arrayStart.concat( - /** @type {Chunk[]} */ - (item.chunks).map(chunk => /** @type {ChunkId} */ (chunk.id)) - ); - } - } - - const chunksStartPosition = hasFakeMap ? 2 : 1; - const requestPrefix = hasNoChunk - ? "Promise.resolve()" - : hasMultipleOrNoChunks - ? `Promise.all(ids.slice(${chunksStartPosition}).map(${RuntimeGlobals.ensureChunk}))` - : `${RuntimeGlobals.ensureChunk}(ids[${chunksStartPosition}])`; - const returnModuleObject = this.getReturnModuleObjectSource( - fakeMap, - true, - shortMode ? "invalid" : "ids[1]" - ); - - const webpackAsyncContext = - requestPrefix === "Promise.resolve()" - ? ` -function webpackAsyncContext(req) { - return Promise.resolve().then(${arrow ? "() =>" : "function()"} { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - } - - ${shortMode ? "var id = map[req];" : "var ids = map[req], id = ids[0];"} - ${returnModuleObject} - }); -}` - : `function webpackAsyncContext(req) { - if(!${RuntimeGlobals.hasOwnProperty}(map, req)) { - return Promise.resolve().then(${arrow ? "() =>" : "function()"} { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); - } - - var ids = map[req], id = ids[0]; - return ${requestPrefix}.then(${arrow ? "() =>" : "function()"} { - ${returnModuleObject} - }); -}`; - - return `var map = ${JSON.stringify(map, null, "\t")}; -${webpackAsyncContext} -webpackAsyncContext.keys = ${runtimeTemplate.returningFunction( - "Object.keys(map)" - )}; -webpackAsyncContext.id = ${JSON.stringify(id)}; -module.exports = webpackAsyncContext;`; - } - - /** - * @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 + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; -} -webpackEmptyContext.keys = ${runtimeTemplate.returningFunction("[]")}; -webpackEmptyContext.resolve = webpackEmptyContext; -webpackEmptyContext.id = ${JSON.stringify(id)}; -module.exports = webpackEmptyContext;`; - } - - /** - * @param {ModuleId} id module id - * @param {RuntimeTemplate} runtimeTemplate runtime template - * @returns {string} source for empty async context - */ - getSourceForEmptyAsyncContext(id, runtimeTemplate) { - const arrow = runtimeTemplate.supportsArrowFunction(); - 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(${arrow ? "() =>" : "function()"} { - var e = new Error("Cannot find module '" + req + "'"); - e.code = 'MODULE_NOT_FOUND'; - throw e; - }); -} -webpackEmptyAsyncContext.keys = ${runtimeTemplate.returningFunction("[]")}; -webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext; -webpackEmptyAsyncContext.id = ${JSON.stringify(id)}; -module.exports = webpackEmptyAsyncContext;`; - } - - /** - * @param {string} asyncMode module mode - * @param {CodeGenerationContext} context context info - * @returns {string} the source code - */ - getSourceString(asyncMode, { runtimeTemplate, chunkGraph }) { - const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(this)); - if (asyncMode === "lazy") { - if (this.blocks && this.blocks.length > 0) { - return this.getLazySource(this.blocks, id, { - runtimeTemplate, - chunkGraph - }); - } - return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); - } - if (asyncMode === "eager") { - if (this.dependencies && this.dependencies.length > 0) { - return this.getEagerSource(this.dependencies, id, { - chunkGraph, - runtimeTemplate - }); - } - return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); - } - if (asyncMode === "lazy-once") { - const block = this.blocks[0]; - if (block) { - return this.getLazyOnceSource(block, block.dependencies, id, { - runtimeTemplate, - chunkGraph - }); - } - return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); - } - if (asyncMode === "async-weak") { - if (this.dependencies && this.dependencies.length > 0) { - return this.getAsyncWeakSource(this.dependencies, id, { - chunkGraph, - runtimeTemplate - }); - } - return this.getSourceForEmptyAsyncContext(id, runtimeTemplate); - } - if ( - asyncMode === "weak" && - this.dependencies && - this.dependencies.length > 0 - ) { - return this.getWeakSyncSource(this.dependencies, id, chunkGraph); - } - if (this.dependencies && this.dependencies.length > 0) { - return this.getSyncSource(this.dependencies, id, chunkGraph); - } - return this.getSourceForEmptyContext(id, runtimeTemplate); - } - - /** - * @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); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - const { chunkGraph, compilation } = context; - const sources = new Map(); - sources.set( - "javascript", - this.getSource( - this.getSourceString(this.options.mode, context), - compilation - ) - ); - const set = new Set(); - const allDeps = - this.dependencies.length > 0 - ? /** @type {ContextElementDependency[]} */ (this.dependencies).slice() - : []; - 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); - } - } - return { - sources, - runtimeRequirements: set - }; - } - - /** - * @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 - let size = 160; - - // if we don't have dependencies we stop here. - for (const dependency of this.dependencies) { - const element = /** @type {ContextElementDependency} */ (dependency); - size += 5 + element.userRequest.length; - } - return size; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this._identifier); - write(this._forceBuild); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this._identifier = read(); - this._forceBuild = read(); - super.deserialize(context); - } -} - -makeSerializable(ContextModule, "webpack/lib/ContextModule"); - -module.exports = ContextModule; diff --git a/webpack-lib/lib/ContextModuleFactory.js b/webpack-lib/lib/ContextModuleFactory.js deleted file mode 100644 index 23da02663e2..00000000000 --- a/webpack-lib/lib/ContextModuleFactory.js +++ /dev/null @@ -1,481 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -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("./ContextModule").ContextModuleOptions} ContextModuleOptions */ -/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./ResolverFactory")} ResolverFactory */ -/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */ -/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */ -/** - * @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 */ - -const EMPTY_RESOLVE_OPTIONS = {}; - -module.exports = class ContextModuleFactory extends ModuleFactory { - /** - * @param {ResolverFactory} resolverFactory resolverFactory - */ - constructor(resolverFactory) { - super(); - /** @type {AsyncSeriesWaterfallHook<[ContextAlternativeRequest[], ContextModuleOptions]>} */ - const alternativeRequests = new AsyncSeriesWaterfallHook([ - "modules", - "options" - ]); - this.hooks = Object.freeze({ - /** @type {AsyncSeriesWaterfallHook<[TODO]>} */ - beforeResolve: new AsyncSeriesWaterfallHook(["data"]), - /** @type {AsyncSeriesWaterfallHook<[TODO]>} */ - afterResolve: new AsyncSeriesWaterfallHook(["data"]), - /** @type {SyncWaterfallHook<[string[]]>} */ - contextModuleFiles: new SyncWaterfallHook(["files"]), - /** @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 - }); - this.resolverFactory = resolverFactory; - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const context = data.context; - const dependencies = data.dependencies; - const resolveOptions = data.resolveOptions; - const dependency = /** @type {ContextDependency} */ (dependencies[0]); - const fileDependencies = new LazySet(); - const missingDependencies = new LazySet(); - const contextDependencies = new LazySet(); - this.hooks.beforeResolve.callAsync( - { - context, - dependencies, - layer: data.contextInfo.issuerLayer, - resolveOptions, - fileDependencies, - missingDependencies, - contextDependencies, - ...dependency.options - }, - (err, beforeResolveResult) => { - if (err) { - return callback(err, { - fileDependencies, - missingDependencies, - contextDependencies - }); - } - - // Ignored - if (!beforeResolveResult) { - return callback(null, { - fileDependencies, - missingDependencies, - contextDependencies - }); - } - - const context = beforeResolveResult.context; - const request = beforeResolveResult.request; - const resolveOptions = beforeResolveResult.resolveOptions; - - let loaders; - let resource; - let loadersPrefix = ""; - const idx = request.lastIndexOf("!"); - if (idx >= 0) { - let loadersRequest = request.slice(0, idx + 1); - let i; - for ( - i = 0; - i < loadersRequest.length && loadersRequest[i] === "!"; - i++ - ) { - loadersPrefix += "!"; - } - loadersRequest = loadersRequest - .slice(i) - .replace(/!+$/, "") - .replace(/!!+/g, "!"); - loaders = loadersRequest === "" ? [] : loadersRequest.split("!"); - resource = request.slice(idx + 1); - } else { - loaders = []; - resource = request; - } - - const contextResolver = this.resolverFactory.get( - "context", - dependencies.length > 0 - ? cachedSetProperty( - resolveOptions || EMPTY_RESOLVE_OPTIONS, - "dependencyType", - dependencies[0].category - ) - : resolveOptions - ); - const loaderResolver = this.resolverFactory.get("loader"); - - asyncLib.parallel( - [ - callback => { - const results = /** @type ResolveRequest[] */ ([]); - /** - * @param {ResolveRequest} obj obj - * @returns {void} - */ - const yield_ = obj => { - results.push(obj); - }; - - contextResolver.resolve( - {}, - context, - resource, - { - fileDependencies, - missingDependencies, - contextDependencies, - yield: yield_ - }, - err => { - if (err) return callback(err); - callback(null, results); - } - ); - }, - callback => { - asyncLib.map( - loaders, - (loader, callback) => { - loaderResolver.resolve( - {}, - context, - loader, - { - fileDependencies, - missingDependencies, - contextDependencies - }, - (err, result) => { - if (err) return callback(err); - callback(null, /** @type {string} */ (result)); - } - ); - }, - callback - ); - } - ], - (err, result) => { - 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( - { - addon: - loadersPrefix + - loaderResult.join("!") + - (loaderResult.length > 0 ? "!" : ""), - resource: - contextResult.length > 1 - ? contextResult.map(r => r.path) - : contextResult[0].path, - resolveDependencies: this.resolveDependencies.bind(this), - resourceQuery: contextResult[0].query, - resourceFragment: contextResult[0].fragment, - ...beforeResolveResult - }, - (err, result) => { - if (err) { - return callback(err, { - fileDependencies, - missingDependencies, - contextDependencies - }); - } - - // Ignored - if (!result) { - return callback(null, { - fileDependencies, - missingDependencies, - contextDependencies - }); - } - - return callback(null, { - module: new ContextModule(result.resolveDependencies, result), - fileDependencies, - missingDependencies, - contextDependencies - }); - } - ); - } - ); - } - ); - } - - /** - * @param {InputFileSystem} fs file system - * @param {ContextModuleOptions} options options - * @param {ResolveDependenciesCallback} callback callback function - * @returns {void} - */ - resolveDependencies(fs, options, callback) { - const cmf = this; - const { - resource, - resourceQuery, - resourceFragment, - recursive, - regExp, - include, - exclude, - referencedExports, - category, - typePrefix, - attributes - } = options; - if (!regExp || !resource) return callback(null, []); - - /** - * @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 - ); - }); - }; - - /** - * @param {string} ctx context - * @param {string} directory directory - * @param {function(string, string, function(): void): void} addSubDirectory addSubDirectoryFn - * @param {ResolveDependenciesCallback} callback callback - */ - const addDirectory = (ctx, directory, addSubDirectory, callback) => { - fs.readdir(directory, (err, files) => { - if (err) return callback(err); - const processedFiles = cmf.hooks.contextModuleFiles.call( - /** @type {string[]} */ (files).map(file => file.normalize("NFC")) - ); - if (!processedFiles || processedFiles.length === 0) - return callback(null, []); - asyncLib.map( - processedFiles.filter(p => p.indexOf(".") !== 0), - (segment, callback) => { - const subResource = join(fs, directory, segment); - - if (!exclude || !subResource.match(exclude)) { - 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(); - } - return callback(err); - } - - const stat = /** @type {IStats} */ (_stat); - - if (stat.isDirectory()) { - if (!recursive) return callback(); - addSubDirectory(ctx, subResource, callback); - } else if ( - stat.isFile() && - (!include || subResource.match(include)) - ) { - /** @type {{ context: string, request: string }} */ - const obj = { - context: ctx, - request: `.${subResource.slice(ctx.length).replace(/\\/g, "/")}` - }; - - this.hooks.alternativeRequests.callAsync( - [obj], - options, - (err, alternatives) => { - if (err) return callback(err); - callback( - null, - /** @type {ContextAlternativeRequest[]} */ - (alternatives) - .filter(obj => - regExp.test(/** @type {string} */ (obj.request)) - ) - .map(obj => { - const dep = new ContextElementDependency( - `${obj.request}${resourceQuery}${resourceFragment}`, - obj.request, - typePrefix, - /** @type {string} */ - (category), - referencedExports, - /** @type {TODO} */ - (obj.context), - attributes - ); - dep.optional = true; - return dep; - }) - ); - } - ); - } else { - callback(); - } - }); - } else { - callback(); - } - }, - (err, result) => { - if (err) return callback(err); - - if (!result) return callback(null, []); - - const flattenedResult = []; - - for (const item of result) { - if (item) flattenedResult.push(...item); - } - - callback(null, flattenedResult); - } - ); - }); - }; - - /** - * @param {string} ctx context - * @param {string} dir dir - * @param {ResolveDependenciesCallback} callback callback - * @returns {void} - */ - const addSubDirectory = (ctx, dir, callback) => - addDirectory(ctx, dir, addSubDirectory, callback); - - /** - * @param {string} resource resource - * @param {ResolveDependenciesCallback} callback callback - */ - const visitResource = (resource, callback) => { - if (typeof fs.realpath === "function") { - addDirectoryChecked(resource, resource, 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); - }); - } - } -}; diff --git a/webpack-lib/lib/ContextReplacementPlugin.js b/webpack-lib/lib/ContextReplacementPlugin.js deleted file mode 100644 index ac425f31321..00000000000 --- a/webpack-lib/lib/ContextReplacementPlugin.js +++ /dev/null @@ -1,172 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ContextElementDependency = require("./dependencies/ContextElementDependency"); -const { join } = require("./util/fs"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -class ContextReplacementPlugin { - /** - * @param {RegExp} resourceRegExp A regular expression that determines which files will be selected - * @param {TODO=} newContentResource A new resource to replace the match - * @param {TODO=} newContentRecursive If true, all subdirectories are searched for matches - * @param {RegExp=} newContentRegExp A regular expression that determines which files will be selected - */ - constructor( - resourceRegExp, - newContentResource, - newContentRecursive, - newContentRegExp - ) { - this.resourceRegExp = resourceRegExp; - - if (typeof newContentResource === "function") { - this.newContentCallback = newContentResource; - } else if ( - typeof newContentResource === "string" && - typeof newContentRecursive === "object" - ) { - this.newContentResource = newContentResource; - this.newContentCreateContextMap = (fs, callback) => { - callback(null, newContentRecursive); - }; - } else if ( - typeof newContentResource === "string" && - typeof newContentRecursive === "function" - ) { - this.newContentResource = newContentResource; - this.newContentCreateContextMap = newContentRecursive; - } else { - if (typeof newContentResource !== "string") { - newContentRegExp = newContentRecursive; - newContentRecursive = newContentResource; - newContentResource = undefined; - } - if (typeof newContentRecursive !== "boolean") { - newContentRegExp = newContentRecursive; - newContentRecursive = undefined; - } - this.newContentResource = newContentResource; - this.newContentRecursive = newContentRecursive; - this.newContentRegExp = newContentRegExp; - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const resourceRegExp = this.resourceRegExp; - const newContentCallback = this.newContentCallback; - const newContentResource = this.newContentResource; - const newContentRecursive = this.newContentRecursive; - const newContentRegExp = this.newContentRegExp; - const newContentCreateContextMap = this.newContentCreateContextMap; - - compiler.hooks.contextModuleFactory.tap("ContextReplacementPlugin", cmf => { - cmf.hooks.beforeResolve.tap("ContextReplacementPlugin", result => { - if (!result) return; - if (resourceRegExp.test(result.request)) { - if (newContentResource !== undefined) { - result.request = newContentResource; - } - if (newContentRecursive !== undefined) { - result.recursive = newContentRecursive; - } - if (newContentRegExp !== undefined) { - result.regExp = newContentRegExp; - } - if (typeof newContentCallback === "function") { - newContentCallback(result); - } else { - for (const d of result.dependencies) { - if (d.critical) d.critical = false; - } - } - } - return result; - }); - cmf.hooks.afterResolve.tap("ContextReplacementPlugin", result => { - if (!result) return; - if (resourceRegExp.test(result.resource)) { - if (newContentResource !== undefined) { - if ( - newContentResource.startsWith("/") || - (newContentResource.length > 1 && newContentResource[1] === ":") - ) { - result.resource = newContentResource; - } else { - result.resource = join( - /** @type {InputFileSystem} */ (compiler.inputFileSystem), - result.resource, - newContentResource - ); - } - } - if (newContentRecursive !== undefined) { - result.recursive = newContentRecursive; - } - if (newContentRegExp !== undefined) { - result.regExp = newContentRegExp; - } - if (typeof newContentCreateContextMap === "function") { - result.resolveDependencies = - createResolveDependenciesFromContextMap( - newContentCreateContextMap - ); - } - if (typeof newContentCallback === "function") { - const origResource = result.resource; - newContentCallback(result); - if ( - result.resource !== origResource && - !result.resource.startsWith("/") && - (result.resource.length <= 1 || result.resource[1] !== ":") - ) { - // When the function changed it to an relative path - result.resource = join( - /** @type {InputFileSystem} */ (compiler.inputFileSystem), - origResource, - result.resource - ); - } - } else { - for (const d of result.dependencies) { - if (d.critical) d.critical = false; - } - } - } - return result; - }); - }); - } -} - -const createResolveDependenciesFromContextMap = createContextMap => { - const resolveDependenciesFromContextMap = (fs, options, callback) => { - createContextMap(fs, (err, map) => { - if (err) return callback(err); - const dependencies = Object.keys(map).map( - key => - new ContextElementDependency( - map[key] + options.resourceQuery + options.resourceFragment, - key, - options.category, - options.referencedExports - ) - ); - callback(null, dependencies); - }); - }; - return resolveDependenciesFromContextMap; -}; - -module.exports = ContextReplacementPlugin; diff --git a/webpack-lib/lib/CssModule.js b/webpack-lib/lib/CssModule.js deleted file mode 100644 index c8556627e7e..00000000000 --- a/webpack-lib/lib/CssModule.js +++ /dev/null @@ -1,175 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -const NormalModule = require("./NormalModule"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./NormalModule").NormalModuleCreateData} NormalModuleCreateData */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** @typedef {string | undefined} CssLayer */ -/** @typedef {string | undefined} Supports */ -/** @typedef {string | undefined} Media */ -/** @typedef {[CssLayer, Supports, Media]} InheritanceItem */ -/** @typedef {Array} Inheritance */ - -/** @typedef {NormalModuleCreateData & { cssLayer: CssLayer, supports: Supports, media: Media, inheritance: Inheritance }} CSSModuleCreateData */ - -class CssModule extends NormalModule { - /** - * @param {CSSModuleCreateData} options options object - */ - constructor(options) { - super(options); - - // Avoid override `layer` for `Module` class, because it is a feature to run module in specific layer - this.cssLayer = options.cssLayer; - this.supports = options.supports; - this.media = options.media; - this.inheritance = options.inheritance; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - let identifier = super.identifier(); - - if (this.cssLayer) { - identifier += `|${this.cssLayer}`; - } - - if (this.supports) { - identifier += `|${this.supports}`; - } - - if (this.media) { - identifier += `|${this.media}`; - } - - if (this.inheritance) { - const inheritance = this.inheritance.map( - (item, index) => - `inheritance_${index}|${item[0] || ""}|${item[1] || ""}|${ - item[2] || "" - }` - ); - - identifier += `|${inheritance.join("|")}`; - } - - // We generate extra code for HMR, so we need to invalidate the module - if (this.hot) { - identifier += `|${this.hot}`; - } - - return identifier; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - const readableIdentifier = super.readableIdentifier(requestShortener); - - let identifier = `css ${readableIdentifier}`; - - if (this.cssLayer) { - identifier += ` (layer: ${this.cssLayer})`; - } - - if (this.supports) { - identifier += ` (supports: ${this.supports})`; - } - - if (this.media) { - identifier += ` (media: ${this.media})`; - } - - return identifier; - } - - /** - * 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) { - super.updateCacheModule(module); - const m = /** @type {CssModule} */ (module); - this.cssLayer = m.cssLayer; - this.supports = m.supports; - this.media = m.media; - this.inheritance = m.inheritance; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.cssLayer); - write(this.supports); - write(this.media); - write(this.inheritance); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {CssModule} the deserialized object - */ - static deserialize(context) { - const obj = new CssModule({ - // 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), - cssLayer: /** @type {EXPECTED_ANY} */ (null), - supports: /** @type {EXPECTED_ANY} */ (null), - media: /** @type {EXPECTED_ANY} */ (null), - inheritance: /** @type {EXPECTED_ANY} */ (null) - }); - obj.deserialize(context); - return obj; - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {TODO} Module - */ - deserialize(context) { - const { read } = context; - this.cssLayer = read(); - this.supports = read(); - this.media = read(); - this.inheritance = read(); - super.deserialize(context); - } -} - -makeSerializable(CssModule, "webpack/lib/CssModule"); - -module.exports = CssModule; diff --git a/webpack-lib/lib/DefinePlugin.js b/webpack-lib/lib/DefinePlugin.js deleted file mode 100644 index 1c1cf7aa2e8..00000000000 --- a/webpack-lib/lib/DefinePlugin.js +++ /dev/null @@ -1,719 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM, - JAVASCRIPT_MODULE_TYPE_DYNAMIC -} = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const WebpackError = require("./WebpackError"); -const ConstDependency = require("./dependencies/ConstDependency"); -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").ValueCacheVersions} ValueCacheVersions */ -/** @typedef {import("./NormalModule")} NormalModule */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ -/** @typedef {import("./logging/Logger").Logger} Logger */ -/** @typedef {import("./util/createHash").Algorithm} Algorithm */ - -/** @typedef {null|undefined|RegExp|Function|string|number|boolean|bigint|undefined} CodeValuePrimitive */ -/** @typedef {RecursiveArrayOrRecord} CodeValue */ - -/** - * @typedef {object} RuntimeValueOptions - * @property {string[]=} fileDependencies - * @property {string[]=} contextDependencies - * @property {string[]=} missingDependencies - * @property {string[]=} buildDependencies - * @property {string|function(): string=} version - */ - -/** @typedef {string | Set} ValueCacheVersion */ -/** @typedef {function({ module: NormalModule, key: string, readonly version: ValueCacheVersion }): CodeValuePrimitive} GeneratorFn */ - -class RuntimeValue { - /** - * @param {GeneratorFn} fn generator function - * @param {true | string[] | RuntimeValueOptions=} options options - */ - constructor(fn, options) { - this.fn = fn; - if (Array.isArray(options)) { - options = { - fileDependencies: options - }; - } - this.options = options || {}; - } - - get fileDependencies() { - return this.options === true ? true : this.options.fileDependencies; - } - - /** - * @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"; - } -} - -/** - * @param {Set | undefined} properties properties - * @returns {Set | undefined} used keys - */ -function getObjKeys(properties) { - if (!properties) return; - return new Set([...properties].map(p => p.id)); -} - -/** @typedef {Set | null} ObjKeys */ -/** @typedef {boolean | undefined | null} AsiSafe */ - -/** - * @param {any[]|{[k: string]: 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 -) => { - let code; - const arr = Array.isArray(obj); - if (arr) { - code = `[${ - /** @type {any[]} */ (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 = /** @type {{[k: string]: any}} */ (obj)[key]; - return `${JSON.stringify(key)}:${toCode( - code, - parser, - valueCacheVersions, - key, - runtimeTemplate, - logger, - null - )}`; - }) - .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; -}; - -/** - * @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()})`; - } - if (typeof code === "object") { - const items = Object.keys(code).map(key => ({ - key, - value: toCacheVersion(/** @type {Record} */ (code)[key]) - })); - if (items.some(({ value }) => value === undefined)) return; - return `{${items.map(({ key, value }) => `${key}: ${value}`).join(", ")}}`; - } - if (typeof code === "bigint") { - return `${code}n`; - } - return `${code}`; -}; - -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); - -class DefinePlugin { - /** - * Create a new define plugin - * @param {Record} definitions A map of global object definitions - */ - constructor(definitions) { - this.definitions = definitions; - } - - /** - * @param {GeneratorFn} fn generator function - * @param {true | string[] | RuntimeValueOptions=} options options - * @returns {RuntimeValue} runtime value - */ - static runtimeValue(fn, options) { - return new RuntimeValue(fn, options); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const definitions = this.definitions; - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const logger = compilation.getLogger("webpack.DefinePlugin"); - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - const { runtimeTemplate } = compilation; - - const mainHash = createHash( - /** @type {Algorithm} */ - (compilation.outputOptions.hashFunction) - ); - mainHash.update( - /** @type {string} */ - (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)) || "" - ); - - /** - * Handler - * @param {JavascriptParser} parser Parser - * @returns {void} - */ - const handler = parser => { - const mainValue = - /** @type {ValueCacheVersion} */ - (compilation.valueCacheVersions.get(VALUE_DEP_MAIN)); - parser.hooks.program.tap(PLUGIN_NAME, () => { - const buildInfo = /** @type {BuildInfo} */ ( - parser.state.module.buildInfo - ); - if (!buildInfo.valueDependencies) - buildInfo.valueDependencies = new Map(); - buildInfo.valueDependencies.set(VALUE_DEP_MAIN, mainValue); - }); - - /** - * @param {string} key key - */ - const addValueDependency = key => { - const buildInfo = - /** @type {BuildInfo} */ - (parser.state.module.buildInfo); - /** @type {NonNullable} */ - (buildInfo.valueDependencies).set( - VALUE_DEP_PREFIX + key, - /** @type {ValueCacheVersion} */ - (compilation.valueCacheVersions.get(VALUE_DEP_PREFIX + key)) - ); - }; - - /** - * @template {Function} T - * @param {string} key key - * @param {T} fn fn - * @returns {function(TODO): TODO} result - */ - const withValueDependency = - (key, fn) => - (...args) => { - addValueDependency(key); - return fn(...args); - }; - - /** - * Walk definitions - * @param {Record} definitions Definitions map - * @param {string} prefix Prefix string - * @returns {void} - */ - const walkDefinitions = (definitions, prefix) => { - for (const key of Object.keys(definitions)) { - const code = definitions[key]; - if ( - code && - typeof code === "object" && - !(code instanceof RuntimeValue) && - !(code instanceof RegExp) - ) { - walkDefinitions( - /** @type {Record} */ (code), - `${prefix + key}.` - ); - applyObjectDefine(prefix + key, code); - continue; - } - applyDefineKey(prefix, key); - applyDefine(prefix + key, code); - } - }; - - /** - * Apply define key - * @param {string} prefix Prefix - * @param {string} key Key - * @returns {void} - */ - const applyDefineKey = (prefix, key) => { - const splittedKey = key.split("."); - 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(PLUGIN_NAME, () => { - addValueDependency(key); - if ( - parser.scope.definitions.get(firstKey) instanceof VariableInfo - ) { - return false; - } - return true; - }); - } - }; - - /** - * Apply Code - * @param {string} key Key - * @param {CodeValue} code Code - * @returns {void} - */ - const applyDefine = (key, code) => { - const originalKey = key; - const isTypeof = TYPEOF_OPERATOR_REGEXP.test(key); - if (isTypeof) key = key.replace(TYPEOF_OPERATOR_REGEXP, ""); - let recurse = false; - let recurseTypeof = false; - if (!isTypeof) { - parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => { - addValueDependency(originalKey); - return true; - }); - parser.hooks.evaluateIdentifier - .for(key) - .tap(PLUGIN_NAME, expr => { - /** - * this is needed in case there is a recursion in the DefinePlugin - * to prevent an endless recursion - * e.g.: new DefinePlugin({ - * "a": "b", - * "b": "a" - * }); - */ - if (recurse) return; - addValueDependency(originalKey); - recurse = true; - const res = parser.evaluate( - toCode( - code, - parser, - compilation.valueCacheVersions, - key, - runtimeTemplate, - logger, - null - ) - ); - recurse = false; - res.setRange(/** @type {Range} */ (expr.range)); - return res; - }); - 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); - }); - } - 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 - * e.g.: new DefinePlugin({ - * "typeof a": "typeof b", - * "typeof b": "typeof a" - * }); - */ - 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(/** @type {Range} */ (expr.range)); - return res; - }); - 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 toConstantDependency( - parser, - JSON.stringify(res.string) - ).bind(parser)(expr); - }); - }; - - /** - * Apply Object - * @param {string} key Key - * @param {object} obj Object - * @returns {void} - */ - const applyObjectDefine = (key, obj) => { - parser.hooks.canRename.for(key).tap(PLUGIN_NAME, () => { - addValueDependency(key); - return true; - }); - parser.hooks.evaluateIdentifier.for(key).tap(PLUGIN_NAME, expr => { - addValueDependency(key); - return new BasicEvaluatedExpression() - .setTruthy() - .setSideEffects(false) - .setRange(/** @type {Range} */ (expr.range)); - }); - parser.hooks.evaluateTypeof - .for(key) - .tap( - PLUGIN_NAME, - withValueDependency(key, evaluateToString("object")) - ); - 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); - }); - parser.hooks.typeof - .for(key) - .tap( - PLUGIN_NAME, - withValueDependency( - key, - toConstantDependency(parser, JSON.stringify("object")) - ) - ); - }; - - walkDefinitions(definitions, ""); - }; - - 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); - - /** - * Walk definitions - * @param {Record} 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 ( - code && - typeof code === "object" && - !(code instanceof RuntimeValue) && - !(code instanceof RegExp) - ) { - walkDefinitionsForValues( - /** @type {Record} */ (code), - `${prefix + key}.` - ); - } - } - }; - - walkDefinitionsForValues(definitions, ""); - - compilation.valueCacheVersions.set( - VALUE_DEP_MAIN, - /** @type {string} */ (mainHash.digest("hex").slice(0, 8)) - ); - } - ); - } -} -module.exports = DefinePlugin; diff --git a/webpack-lib/lib/DelegatedModule.js b/webpack-lib/lib/DelegatedModule.js deleted file mode 100644 index e6bc5bc25d5..00000000000 --- a/webpack-lib/lib/DelegatedModule.js +++ /dev/null @@ -1,262 +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 { JS_TYPES } = require("./ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency"); -const StaticExportsDependency = require("./dependencies/StaticExportsDependency"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./LibManifestPlugin").ManifestModuleData} ManifestModuleData */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./Module").SourceContext} SourceContext */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./util/Hash")} Hash */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -/** @typedef {string} SourceRequest */ -/** @typedef {"require" | "object"} Type */ -/** @typedef {TODO} Data */ - -const RUNTIME_REQUIREMENTS = new Set([ - RuntimeGlobals.module, - RuntimeGlobals.require -]); - -class DelegatedModule extends Module { - /** - * @param {SourceRequest} sourceRequest source request - * @param {Data} data data - * @param {Type} type type - * @param {string} userRequest user request - * @param {string | Module} originalRequest original request - */ - constructor(sourceRequest, data, type, userRequest, originalRequest) { - super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); - - // Info from Factory - this.sourceRequest = sourceRequest; - this.request = data.id; - this.delegationType = type; - this.userRequest = userRequest; - this.originalRequest = originalRequest; - /** @type {ManifestModuleData | undefined} */ - this.delegateData = data; - - // Build info - this.delegatedSourceDependency = undefined; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return typeof this.originalRequest === "string" - ? this.originalRequest - : this.originalRequest.libIdent(options); - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `delegated ${JSON.stringify(this.request)} from ${ - this.sourceRequest - }`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `delegated ${this.userRequest} from ${this.sourceRequest}`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - const delegateData = /** @type {ManifestModuleData} */ (this.delegateData); - this.buildMeta = { ...delegateData.buildMeta }; - this.buildInfo = {}; - this.dependencies.length = 0; - this.delegatedSourceDependency = new DelegatedSourceDependency( - this.sourceRequest - ); - this.addDependency(this.delegatedSourceDependency); - this.addDependency( - new StaticExportsDependency(delegateData.exports || true, false) - ); - callback(); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { - const dep = /** @type {DelegatedSourceDependency} */ (this.dependencies[0]); - const sourceModule = moduleGraph.getModule(dep); - let str; - - if (!sourceModule) { - str = runtimeTemplate.throwMissingModuleErrorBlock({ - request: this.sourceRequest - }); - } else { - str = `module.exports = (${runtimeTemplate.moduleExports({ - module: sourceModule, - chunkGraph, - request: dep.request, - runtimeRequirements: new Set() - })})`; - - switch (this.delegationType) { - case "require": - str += `(${JSON.stringify(this.request)})`; - break; - case "object": - str += `[${JSON.stringify(this.request)}]`; - break; - } - - str += ";"; - } - - const sources = new Map(); - if (this.useSourceMap || this.useSimpleSourceMap) { - sources.set("javascript", new OriginalSource(str, this.identifier())); - } else { - sources.set("javascript", new RawSource(str)); - } - - return { - sources, - runtimeRequirements: RUNTIME_REQUIREMENTS - }; - } - - /** - * @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; - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - hash.update(this.delegationType); - hash.update(JSON.stringify(this.request)); - super.updateHash(hash, context); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - // constructor - write(this.sourceRequest); - write(this.delegateData); - write(this.delegationType); - write(this.userRequest); - write(this.originalRequest); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context\ - * @returns {DelegatedModule} DelegatedModule - */ - static deserialize(context) { - const { read } = context; - const obj = new DelegatedModule( - read(), // sourceRequest - read(), // delegateData - read(), // delegationType - read(), // userRequest - read() // originalRequest - ); - obj.deserialize(context); - return obj; - } - - /** - * 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) { - super.updateCacheModule(module); - const m = /** @type {DelegatedModule} */ (module); - this.delegationType = m.delegationType; - this.userRequest = m.userRequest; - this.originalRequest = m.originalRequest; - this.delegateData = m.delegateData; - } - - /** - * Assuming this module is in the cache. Remove internal references to allow freeing some memory. - */ - cleanupForCache() { - super.cleanupForCache(); - this.delegateData = undefined; - } -} - -makeSerializable(DelegatedModule, "webpack/lib/DelegatedModule"); - -module.exports = DelegatedModule; diff --git a/webpack-lib/lib/DelegatedModuleFactoryPlugin.js b/webpack-lib/lib/DelegatedModuleFactoryPlugin.js deleted file mode 100644 index ae9b79aaed7..00000000000 --- a/webpack-lib/lib/DelegatedModuleFactoryPlugin.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const DelegatedModule = require("./DelegatedModule"); - -/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptions} DllReferencePluginOptions */ -/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsContent} DllReferencePluginOptionsContent */ -/** @typedef {import("./DelegatedModule").Data} Data */ -/** @typedef {import("./DelegatedModule").SourceRequest} SourceRequest */ -/** @typedef {import("./DelegatedModule").Type} Type */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ - -/** - * @typedef {object} Options - * @property {SourceRequest} source source - * @property {NonNullable} context absolute context path to which lib ident is relative to - * @property {DllReferencePluginOptionsContent} content content - * @property {DllReferencePluginOptions["type"]} type type - * @property {DllReferencePluginOptions["extensions"]} extensions extensions - * @property {DllReferencePluginOptions["scope"]} scope scope - * @property {object=} associatedObjectForCache object for caching - */ - -class DelegatedModuleFactoryPlugin { - /** - * @param {Options} options options - */ - constructor(options) { - this.options = options; - options.type = options.type || "require"; - options.extensions = options.extensions || ["", ".js", ".json", ".wasm"]; - } - - /** - * @param {NormalModuleFactory} normalModuleFactory the normal module factory - * @returns {void} - */ - apply(normalModuleFactory) { - const scope = this.options.scope; - if (scope) { - normalModuleFactory.hooks.factorize.tapAsync( - "DelegatedModuleFactoryPlugin", - (data, callback) => { - const [dependency] = data.dependencies; - const { request } = dependency; - if (request && request.startsWith(`${scope}/`)) { - const innerRequest = `.${request.slice(scope.length)}`; - let resolved; - if (innerRequest in this.options.content) { - resolved = this.options.content[innerRequest]; - return callback( - null, - new DelegatedModule( - this.options.source, - resolved, - /** @type {Type} */ (this.options.type), - innerRequest, - request - ) - ); - } - const extensions = - /** @type {string[]} */ - (this.options.extensions); - for (let i = 0; i < extensions.length; i++) { - const extension = 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, - /** @type {Type} */ (this.options.type), - requestPlusExt, - request + extension - ) - ); - } - } - } - return callback(); - } - ); - } else { - normalModuleFactory.hooks.module.tap( - "DelegatedModuleFactoryPlugin", - module => { - 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, - /** @type {Type} */ (this.options.type), - request, - module - ); - } - return module; - } - ); - } - } -} -module.exports = DelegatedModuleFactoryPlugin; diff --git a/webpack-lib/lib/DelegatedPlugin.js b/webpack-lib/lib/DelegatedPlugin.js deleted file mode 100644 index 735e2f083e2..00000000000 --- a/webpack-lib/lib/DelegatedPlugin.js +++ /dev/null @@ -1,47 +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"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./DelegatedModuleFactoryPlugin").Options} Options */ - -class DelegatedPlugin { - /** - * @param {Options} options options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DelegatedPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - DelegatedSourceDependency, - normalModuleFactory - ); - } - ); - - compiler.hooks.compile.tap("DelegatedPlugin", ({ normalModuleFactory }) => { - new DelegatedModuleFactoryPlugin({ - associatedObjectForCache: compiler.root, - ...this.options - }).apply(normalModuleFactory); - }); - } -} - -module.exports = DelegatedPlugin; diff --git a/webpack-lib/lib/DependenciesBlock.js b/webpack-lib/lib/DependenciesBlock.js deleted file mode 100644 index a952b643b56..00000000000 --- a/webpack-lib/lib/DependenciesBlock.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @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 = []; - /** @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; - } - - /** - * @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); - } - - /** - * @param {Dependency} dependency dependency being removed - * @returns {void} - */ - removeDependency(dependency) { - const idx = this.dependencies.indexOf(dependency); - if (idx >= 0) { - this.dependencies.splice(idx, 1); - } - } - - /** - * Removes all dependencies and blocks - * @returns {void} - */ - clearDependenciesAndBlocks() { - this.dependencies.length = 0; - this.blocks.length = 0; - } - - /** - * @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); - } - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize({ write }) { - write(this.dependencies); - write(this.blocks); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize({ read }) { - this.dependencies = read(); - this.blocks = read(); - for (const block of this.blocks) { - block.parent = this; - } - } -} - -makeSerializable(DependenciesBlock, "webpack/lib/DependenciesBlock"); - -module.exports = DependenciesBlock; diff --git a/webpack-lib/lib/Dependency.js b/webpack-lib/lib/Dependency.js deleted file mode 100644 index a18f7365444..00000000000 --- a/webpack-lib/lib/Dependency.js +++ /dev/null @@ -1,367 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RawModule = require("./RawModule"); -const memoize = require("./util/memoize"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./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 {object} UpdateHashContext - * @property {ChunkGraph} chunkGraph - * @property {RuntimeSpec} runtime - * @property {RuntimeTemplate=} runtimeTemplate - */ - -/** - * @typedef {object} SourcePosition - * @property {number} line - * @property {number=} column - */ - -/** - * @typedef {object} RealDependencyLocation - * @property {SourcePosition} start - * @property {SourcePosition=} end - * @property {number=} index - */ - -/** - * @typedef {object} SyntheticDependencyLocation - * @property {string} name - * @property {number=} index - */ - -/** @typedef {SyntheticDependencyLocation | RealDependencyLocation} DependencyLocation */ - -/** - * @typedef {object} ExportSpec - * @property {string} 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 {(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 - */ - -/** - * @typedef {object} ExportsSpec - * @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports - * @property {Set=} 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 {Module[]=} dependencies module on which the result depends on - */ - -/** - * @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 - */ - -/** @typedef {function(ModuleGraphConnection, RuntimeSpec): ConnectionState} GetConditionFn */ - -const TRANSITIVE = Symbol("transitive"); - -const getIgnoredModule = memoize( - () => new RawModule("/* (ignored) */", "ignored", "(ignored)") -); - -class Dependency { - constructor() { - /** @type {Module | undefined} */ - this._parentModule = undefined; - /** @type {DependenciesBlock | undefined} */ - this._parentDependenciesBlock = undefined; - /** @type {number} */ - this._parentDependenciesBlockIndex = -1; - // TODO check if this can be moved into ModuleDependency - /** @type {boolean} */ - this.weak = false; - // TODO check if this can be moved into ModuleDependency - /** @type {boolean} */ - this.optional = false; - this._locSL = 0; - this._locSC = 0; - this._locEL = 0; - this._locEC = 0; - this._locI = undefined; - this._locN = undefined; - this._loc = undefined; - } - - /** - * @returns {string} a display name for the type of dependency - */ - get type() { - return "unknown"; - } - - /** - * @returns {string} a dependency category, typical categories are "commonjs", "amd", "esm" - */ - get category() { - return "unknown"; - } - - /** - * @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; - this._loc = loc; - } - - /** - * @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; - } - - /** - * @returns {string | undefined} a request context - */ - getContext() { - return undefined; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return null; - } - - /** - * @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 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 and ModuleGraph.getConnection().active" - ); - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return Dependency.EXPORTS_OBJECT_REFERENCED; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return null; - } - - /** - * 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; - } - - /** - * Returns errors - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} errors - */ - getErrors(moduleGraph) { - return null; - } - - /** - * Update the hash - * @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; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return true; - } - - /** - * @param {string} context context directory - * @returns {Module | null} a module - */ - createIgnoredModule(context) { - return getIgnoredModule(); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize({ write }) { - write(this.weak); - write(this.optional); - write(this._locSL); - write(this._locSC); - write(this._locEL); - write(this._locEC); - write(this._locI); - write(this._locN); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize({ read }) { - this.weak = read(); - this.optional = read(); - this._locSL = read(); - this._locSC = read(); - this._locEL = read(); - this._locEC = read(); - this._locI = read(); - this._locN = read(); - } -} - -/** @type {string[][]} */ -Dependency.NO_EXPORTS_REFERENCED = []; -/** @type {string[][]} */ -Dependency.EXPORTS_OBJECT_REFERENCED = [[]]; - -// eslint-disable-next-line no-warning-comments -// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 -Object.defineProperty(Dependency.prototype, "module", { - /** - * @deprecated - * @returns {never} throws - */ - get() { - throw new Error( - "module property was removed from Dependency (use compilation.moduleGraph.getModule(dependency) instead)" - ); - }, - - /** - * @deprecated - * @returns {never} throws - */ - set() { - throw new Error( - "module property was removed from Dependency (use compilation.moduleGraph.updateModule(dependency, module) instead)" - ); - } -}); - -// eslint-disable-next-line no-warning-comments -// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 -Object.defineProperty(Dependency.prototype, "disconnect", { - get() { - throw new Error( - "disconnect was removed from Dependency (Dependency no longer carries graph specific information)" - ); - } -}); - -Dependency.TRANSITIVE = TRANSITIVE; - -module.exports = Dependency; diff --git a/webpack-lib/lib/DependencyTemplate.js b/webpack-lib/lib/DependencyTemplate.js deleted file mode 100644 index 8402ade157e..00000000000 --- a/webpack-lib/lib/DependencyTemplate.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - 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 */ - -/** - * @template T - * @typedef {import("./InitFragment")} InitFragment - */ - -/** - * @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 - */ - -/** - * @typedef {object} CssDependencyTemplateContextExtras - * @property {CssData} cssData the css exports data - */ - -/** - * @typedef {object} CssData - * @property {boolean} esModule whether export __esModule - * @property {Map} exports the css exports - */ - -/** @typedef {DependencyTemplateContext & CssDependencyTemplateContextExtras} CssDependencyTemplateContext */ - -class DependencyTemplate { - /* istanbul ignore next */ - /** - * @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("./AbstractMethodError"); - throw new AbstractMethodError(); - } -} - -module.exports = DependencyTemplate; diff --git a/webpack-lib/lib/DependencyTemplates.js b/webpack-lib/lib/DependencyTemplates.js deleted file mode 100644 index 3b553dae4cb..00000000000 --- a/webpack-lib/lib/DependencyTemplates.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const createHash = require("./util/createHash"); - -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ -/** @typedef {typeof import("./util/Hash")} Hash */ - -/** @typedef {new (...args: any[]) => Dependency} DependencyConstructor */ - -class DependencyTemplates { - /** - * @param {string | Hash} hashFunction the hash function to use - */ - constructor(hashFunction = "md4") { - /** @type {Map} */ - this._map = new Map(); - /** @type {string} */ - this._hash = "31d6cfe0d16ae931b73c59d7e0c089c0"; - this._hashFunction = hashFunction; - } - - /** - * @param {DependencyConstructor} dependency Constructor of Dependency - * @returns {DependencyTemplate | undefined} template for this dependency - */ - get(dependency) { - return this._map.get(dependency); - } - - /** - * @param {DependencyConstructor} dependency Constructor of Dependency - * @param {DependencyTemplate} dependencyTemplate template for this dependency - * @returns {void} - */ - set(dependency, dependencyTemplate) { - this._map.set(dependency, dependencyTemplate); - } - - /** - * @param {string} part additional hash contributor - * @returns {void} - */ - updateHash(part) { - const hash = createHash(this._hashFunction); - hash.update(`${this._hash}${part}`); - this._hash = /** @type {string} */ (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/webpack-lib/lib/DllEntryPlugin.js b/webpack-lib/lib/DllEntryPlugin.js deleted file mode 100644 index de849fa5376..00000000000 --- a/webpack-lib/lib/DllEntryPlugin.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const DllModuleFactory = require("./DllModuleFactory"); -const DllEntryDependency = require("./dependencies/DllEntryDependency"); -const EntryDependency = require("./dependencies/EntryDependency"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {string[]} Entries */ -/** @typedef {{ name: string, filename: TODO }} Options */ - -class DllEntryPlugin { - /** - * @param {string} context context - * @param {Entries} entries entry names - * @param {Options} options options - */ - constructor(context, entries, options) { - this.context = context; - this.entries = entries; - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DllEntryPlugin", - (compilation, { normalModuleFactory }) => { - const dllModuleFactory = new DllModuleFactory(); - compilation.dependencyFactories.set( - DllEntryDependency, - dllModuleFactory - ); - compilation.dependencyFactories.set( - EntryDependency, - normalModuleFactory - ); - } - ); - compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => { - compilation.addEntry( - this.context, - new DllEntryDependency( - this.entries.map((e, idx) => { - const dep = new EntryDependency(e); - dep.loc = { - name: this.options.name, - index: idx - }; - return dep; - }), - this.options.name - ), - this.options, - error => { - if (error) return callback(error); - callback(); - } - ); - }); - } -} - -module.exports = DllEntryPlugin; diff --git a/webpack-lib/lib/DllModule.js b/webpack-lib/lib/DllModule.js deleted file mode 100644 index e9948fc61cc..00000000000 --- a/webpack-lib/lib/DllModule.js +++ /dev/null @@ -1,174 +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"); -const { JS_TYPES } = require("./ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./Module").SourceContext} SourceContext */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./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 */ - -const RUNTIME_REQUIREMENTS = new Set([ - RuntimeGlobals.require, - RuntimeGlobals.module -]); - -class DllModule extends Module { - /** - * @param {string} context context path - * @param {Dependency[]} dependencies dependencies - * @param {string} name name - */ - constructor(context, dependencies, name) { - super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, context); - - // Info from Factory - /** @type {Dependency[]} */ - this.dependencies = dependencies; - this.name = name; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `dll ${this.name}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `dll ${this.name}`; - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = {}; - return callback(); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - const sources = new Map(); - sources.set( - "javascript", - new RawSource(`module.exports = ${RuntimeGlobals.require};`) - ); - return { - sources, - runtimeRequirements: RUNTIME_REQUIREMENTS - }; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @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 12; - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - hash.update(`dll module${this.name || ""}`); - super.updateHash(hash, context); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - context.write(this.name); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - this.name = context.read(); - super.deserialize(context); - } - - /** - * 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) { - super.updateCacheModule(module); - this.dependencies = module.dependencies; - } - - /** - * Assuming this module is in the cache. Remove internal references to allow freeing some memory. - */ - cleanupForCache() { - super.cleanupForCache(); - this.dependencies = /** @type {EXPECTED_ANY} */ (undefined); - } -} - -makeSerializable(DllModule, "webpack/lib/DllModule"); - -module.exports = DllModule; diff --git a/webpack-lib/lib/DllModuleFactory.js b/webpack-lib/lib/DllModuleFactory.js deleted file mode 100644 index d8800353da9..00000000000 --- a/webpack-lib/lib/DllModuleFactory.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const DllModule = require("./DllModule"); -const ModuleFactory = require("./ModuleFactory"); - -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */ - -class DllModuleFactory extends ModuleFactory { - constructor() { - super(); - this.hooks = Object.freeze({}); - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const dependency = /** @type {DllEntryDependency} */ (data.dependencies[0]); - callback(null, { - module: new DllModule( - data.context, - dependency.dependencies, - dependency.name - ) - }); - } -} - -module.exports = DllModuleFactory; diff --git a/webpack-lib/lib/DllPlugin.js b/webpack-lib/lib/DllPlugin.js deleted file mode 100644 index 25440df04ee..00000000000 --- a/webpack-lib/lib/DllPlugin.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const DllEntryPlugin = require("./DllEntryPlugin"); -const FlagAllModulesAsUsedPlugin = require("./FlagAllModulesAsUsedPlugin"); -const LibManifestPlugin = require("./LibManifestPlugin"); -const createSchemaValidation = require("./util/create-schema-validation"); - -/** @typedef {import("../declarations/plugins/DllPlugin").DllPluginOptions} DllPluginOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./DllEntryPlugin").Entries} Entries */ -/** @typedef {import("./DllEntryPlugin").Options} Options */ - -const validate = createSchemaValidation( - require("../schemas/plugins/DllPlugin.check.js"), - () => require("../schemas/plugins/DllPlugin.json"), - { - name: "Dll Plugin", - baseDataPath: "options" - } -); - -class DllPlugin { - /** - * @param {DllPluginOptions} options options object - */ - constructor(options) { - validate(options); - this.options = { - ...options, - entryOnly: options.entryOnly !== false - }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => { - if (typeof entry !== "function") { - for (const name of Object.keys(entry)) { - /** @type {Options} */ - const options = { name, filename: entry.filename }; - new DllEntryPlugin( - context, - /** @type {Entries} */ (entry[name].import), - options - ).apply(compiler); - } - } else { - throw new Error( - "DllPlugin doesn't support dynamic entry (function) yet" - ); - } - return true; - }); - new LibManifestPlugin(this.options).apply(compiler); - if (!this.options.entryOnly) { - new FlagAllModulesAsUsedPlugin("DllPlugin").apply(compiler); - } - } -} - -module.exports = DllPlugin; diff --git a/webpack-lib/lib/DllReferencePlugin.js b/webpack-lib/lib/DllReferencePlugin.js deleted file mode 100644 index 50b2c541021..00000000000 --- a/webpack-lib/lib/DllReferencePlugin.js +++ /dev/null @@ -1,193 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const parseJson = require("json-parse-even-better-errors"); -const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin"); -const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin"); -const WebpackError = require("./WebpackError"); -const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency"); -const createSchemaValidation = require("./util/create-schema-validation"); -const makePathsRelative = require("./util/identifier").makePathsRelative; - -/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ -/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptions} DllReferencePluginOptions */ -/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsContent} DllReferencePluginOptionsContent */ -/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptionsManifest} DllReferencePluginOptionsManifest */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -const validate = createSchemaValidation( - require("../schemas/plugins/DllReferencePlugin.check.js"), - () => require("../schemas/plugins/DllReferencePlugin.json"), - { - name: "Dll Reference Plugin", - baseDataPath: "options" - } -); - -/** @typedef {{ path: string, data: DllReferencePluginOptionsManifest | undefined, error: Error | undefined }} CompilationDataItem */ - -class DllReferencePlugin { - /** - * @param {DllReferencePluginOptions} options options object - */ - constructor(options) { - validate(options); - this.options = options; - /** @type {WeakMap} */ - this._compilationData = new WeakMap(); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DllReferencePlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - DelegatedSourceDependency, - normalModuleFactory - ); - } - ); - - compiler.hooks.beforeCompile.tapAsync( - "DllReferencePlugin", - (params, callback) => { - if ("manifest" in this.options) { - const manifest = this.options.manifest; - if (typeof manifest === "string") { - /** @type {InputFileSystem} */ - (compiler.inputFileSystem).readFile(manifest, (err, result) => { - if (err) return callback(err); - /** @type {CompilationDataItem} */ - const data = { - path: manifest, - data: undefined, - error: undefined - }; - // Catch errors parsing the manifest so that blank - // or malformed manifest files don't kill the process. - try { - data.data = parseJson( - /** @type {Buffer} */ (result).toString("utf-8") - ); - } catch (parseErr) { - // Store the error in the params so that it can - // be added as a compilation error later on. - const manifestPath = makePathsRelative( - /** @type {string} */ (compiler.options.context), - manifest, - compiler.root - ); - data.error = new DllManifestError( - manifestPath, - /** @type {Error} */ (parseErr).message - ); - } - this._compilationData.set(params, data); - return callback(); - }); - return; - } - } - return callback(); - } - ); - - compiler.hooks.compile.tap("DllReferencePlugin", params => { - let name = this.options.name; - let sourceType = this.options.sourceType; - let resolvedContent = - "content" in this.options ? this.options.content : undefined; - if ("manifest" in this.options) { - const manifestParameter = this.options.manifest; - let manifest; - if (typeof manifestParameter === "string") { - const data = - /** @type {CompilationDataItem} */ - (this._compilationData.get(params)); - // If there was an error parsing the manifest - // file, exit now because the error will be added - // as a compilation error in the "compilation" hook. - if (data.error) { - return; - } - manifest = data.data; - } else { - manifest = manifestParameter; - } - if (manifest) { - if (!name) name = manifest.name; - if (!sourceType) sourceType = manifest.type; - if (!resolvedContent) resolvedContent = manifest.content; - } - } - /** @type {Externals} */ - const externals = {}; - const source = `dll-reference ${name}`; - externals[source] = /** @type {string} */ (name); - const normalModuleFactory = params.normalModuleFactory; - new ExternalModuleFactoryPlugin(sourceType || "var", externals).apply( - normalModuleFactory - ); - new DelegatedModuleFactoryPlugin({ - source, - type: this.options.type, - scope: this.options.scope, - context: - /** @type {string} */ - (this.options.context || compiler.options.context), - content: - /** @type {DllReferencePluginOptionsContent} */ - (resolvedContent), - extensions: this.options.extensions, - associatedObjectForCache: compiler.root - }).apply(normalModuleFactory); - }); - - compiler.hooks.compilation.tap( - "DllReferencePlugin", - (compilation, params) => { - if ("manifest" in this.options) { - const manifest = this.options.manifest; - if (typeof manifest === "string") { - const data = /** @type {CompilationDataItem} */ ( - this._compilationData.get(params) - ); - // If there was an error parsing the manifest file, add the - // error as a compilation error to make the compilation fail. - if (data.error) { - compilation.errors.push( - /** @type {DllManifestError} */ (data.error) - ); - } - compilation.fileDependencies.add(manifest); - } - } - } - ); - } -} - -class DllManifestError extends WebpackError { - /** - * @param {string} filename filename of the manifest - * @param {string} message error message - */ - constructor(filename, message) { - super(); - - this.name = "DllManifestError"; - this.message = `Dll manifest ${filename}\n${message}`; - } -} - -module.exports = DllReferencePlugin; diff --git a/webpack-lib/lib/DynamicEntryPlugin.js b/webpack-lib/lib/DynamicEntryPlugin.js deleted file mode 100644 index 5e185fbee0f..00000000000 --- a/webpack-lib/lib/DynamicEntryPlugin.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Naoyuki Kanezawa @nkzawa -*/ - -"use strict"; - -const EntryOptionPlugin = require("./EntryOptionPlugin"); -const EntryPlugin = require("./EntryPlugin"); -const EntryDependency = require("./dependencies/EntryDependency"); - -/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ -/** @typedef {import("../declarations/WebpackOptions").EntryDynamicNormalized} EntryDynamic */ -/** @typedef {import("../declarations/WebpackOptions").EntryItem} EntryItem */ -/** @typedef {import("../declarations/WebpackOptions").EntryStaticNormalized} EntryStatic */ -/** @typedef {import("./Compiler")} Compiler */ - -class DynamicEntryPlugin { - /** - * @param {string} context the context path - * @param {EntryDynamic} entry the entry value - */ - constructor(context, entry) { - this.context = context; - this.entry = entry; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DynamicEntryPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - EntryDependency, - normalModuleFactory - ); - } - ); - - compiler.hooks.make.tapPromise("DynamicEntryPlugin", compilation => - Promise.resolve(this.entry()) - .then(entry => { - 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( - /** - * @param {(value?: any) => 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(x => {}) - ); - } -} - -module.exports = DynamicEntryPlugin; diff --git a/webpack-lib/lib/EntryOptionPlugin.js b/webpack-lib/lib/EntryOptionPlugin.js deleted file mode 100644 index 3e290c186f2..00000000000 --- a/webpack-lib/lib/EntryOptionPlugin.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ -/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */ - -class EntryOptionPlugin { - /** - * @param {Compiler} compiler the compiler instance one is tapping into - * @returns {void} - */ - apply(compiler) { - compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => { - EntryOptionPlugin.applyEntryOption(compiler, context, entry); - return true; - }); - } - - /** - * @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); - for (const entry of descImport) { - new EntryPlugin(context, entry, options).apply(compiler); - } - } - } - } - - /** - * @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, - library: desc.library - }; - if (desc.layer !== undefined && !compiler.options.experiments.layers) { - throw new Error( - "'entryOptions.layer' is only allowed when 'experiments.layers' is enabled" - ); - } - 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/webpack-lib/lib/EntryPlugin.js b/webpack-lib/lib/EntryPlugin.js deleted file mode 100644 index 77c879705e8..00000000000 --- a/webpack-lib/lib/EntryPlugin.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - 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 */ - -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 || ""; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "EntryPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - EntryDependency, - normalModuleFactory - ); - } - ); - - const { entry, options, context } = this; - const dep = EntryPlugin.createDependency(entry, options); - - compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => { - compilation.addEntry(context, dep, options, err => { - callback(err); - }); - }); - } - - /** - * @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/webpack-lib/lib/Entrypoint.js b/webpack-lib/lib/Entrypoint.js deleted file mode 100644 index 7aa019e4d28..00000000000 --- a/webpack-lib/lib/Entrypoint.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ChunkGroup = require("./ChunkGroup"); - -/** @typedef {import("../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescription */ -/** @typedef {import("./Chunk")} Chunk */ - -/** @typedef {{ name?: string } & Omit} EntryOptions */ - -/** - * Entrypoint serves as an encapsulation primitive for chunks that are - * a part of a single ChunkGroup. They represent all bundles that need to be loaded for a - * single instance of a page. Multi-page application architectures will typically yield multiple Entrypoint objects - * inside of the compilation, whereas a Single Page App may only contain one with many lazy-loaded chunks. - */ -class Entrypoint extends ChunkGroup { - /** - * Creates an instance of Entrypoint. - * @param {EntryOptions | string} entryOptions the options for the entrypoint (or name) - * @param {boolean=} initial false, when the entrypoint is not initial loaded - */ - constructor(entryOptions, initial = true) { - if (typeof entryOptions === "string") { - entryOptions = { name: entryOptions }; - } - super({ - name: entryOptions.name - }); - this.options = entryOptions; - /** @type {Chunk=} */ - this._runtimeChunk = undefined; - /** @type {Chunk=} */ - this._entrypointChunk = undefined; - /** @type {boolean} */ - this._initial = initial; - } - - /** - * @returns {boolean} true, when this chunk group will be loaded on initial page load - */ - isInitial() { - return this._initial; - } - - /** - * Sets the runtimeChunk for an entrypoint. - * @param {Chunk} chunk the chunk being set as the runtime chunk. - * @returns {void} - */ - setRuntimeChunk(chunk) { - this._runtimeChunk = chunk; - } - - /** - * Fetches the chunk reference containing the webpack bootstrap code - * @returns {Chunk | null} returns the runtime chunk or null if there is none - */ - getRuntimeChunk() { - 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); - } - - /** - * @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); - } -} - -module.exports = Entrypoint; diff --git a/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js b/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js deleted file mode 100644 index 1a1ea9ece66..00000000000 --- a/webpack-lib/lib/EnvironmentNotSupportAsyncWarning.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Gengkun He @ahabhgk -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {"asyncWebAssembly" | "topLevelAwait" | "external promise" | "external script" | "external import" | "external module"} Feature */ - -class EnvironmentNotSupportAsyncWarning extends WebpackError { - /** - * Creates an instance of EnvironmentNotSupportAsyncWarning. - * @param {Module} module module - * @param {Feature} feature feature - */ - constructor(module, feature) { - const message = `The generated code contains 'async/await' because this module is using "${feature}". -However, your target environment does not appear to support 'async/await'. -As a result, the code may not run as expected or may cause runtime errors.`; - super(message); - - this.name = "EnvironmentNotSupportAsyncWarning"; - this.module = module; - } - - /** - * Creates an instance of EnvironmentNotSupportAsyncWarning. - * @param {Module} module module - * @param {RuntimeTemplate} runtimeTemplate compilation - * @param {Feature} feature feature - */ - static check(module, runtimeTemplate, feature) { - if (!runtimeTemplate.supportsAsyncFunction()) { - module.addWarning(new EnvironmentNotSupportAsyncWarning(module, feature)); - } - } -} - -makeSerializable( - EnvironmentNotSupportAsyncWarning, - "webpack/lib/EnvironmentNotSupportAsyncWarning" -); - -module.exports = EnvironmentNotSupportAsyncWarning; diff --git a/webpack-lib/lib/EnvironmentPlugin.js b/webpack-lib/lib/EnvironmentPlugin.js deleted file mode 100644 index eb9e37a6d4c..00000000000 --- a/webpack-lib/lib/EnvironmentPlugin.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Authors Simen Brekken @simenbrekken, Einar Löve @einarlove -*/ - -"use strict"; - -const DefinePlugin = require("./DefinePlugin"); -const WebpackError = require("./WebpackError"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./DefinePlugin").CodeValue} CodeValue */ - -class 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 = /** @type {Record} */ (keys[0]); - } else { - this.keys = /** @type {string[]} */ (keys); - this.defaultValues = {}; - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - /** @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 WebpackError( - `EnvironmentPlugin - ${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.errors.push(error); - }); - } - - definitions[`process.env.${key}`] = - value === undefined ? "undefined" : JSON.stringify(value); - } - - new DefinePlugin(definitions).apply(compiler); - } -} - -module.exports = EnvironmentPlugin; diff --git a/webpack-lib/lib/ErrorHelpers.js b/webpack-lib/lib/ErrorHelpers.js deleted file mode 100644 index 58c11554193..00000000000 --- a/webpack-lib/lib/ErrorHelpers.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const loaderFlag = "LOADER_EXECUTION"; - -const webpackOptionsFlag = "WEBPACK_OPTIONS"; - -/** - * @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 errorStack.join("\n"); -}; - -/** - * @param {string} stack stack trace - * @returns {string} stack trace without the loader execution flag included - */ -const cutOffLoaderExecution = stack => cutOffByFlag(stack, loaderFlag); - -/** - * @param {string} stack stack trace - * @returns {string} stack trace without the webpack options flag included - */ -const cutOffWebpackOptions = stack => cutOffByFlag(stack, webpackOptionsFlag); - -/** - * @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"); - - /** @type {string[]} */ - const result = []; - - for (const [idx, line] of stackSplitByLines.entries()) { - if (!line.includes(messageSplitByLines[idx])) result.push(line); - } - - return result.join("\n"); -}; - -/** - * @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; - } - const firstLine = stack.slice(0, nextLine); - return firstLine === message ? stack.slice(nextLine + 1) : stack; -}; - -/** - * @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; -}; - -/** - * @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.cutOffByFlag = cutOffByFlag; -module.exports.cutOffLoaderExecution = cutOffLoaderExecution; -module.exports.cutOffWebpackOptions = cutOffWebpackOptions; -module.exports.cutOffMultilineMessage = cutOffMultilineMessage; -module.exports.cutOffMessage = cutOffMessage; -module.exports.cleanUp = cleanUp; -module.exports.cleanUpWebpackOptions = cleanUpWebpackOptions; diff --git a/webpack-lib/lib/EvalDevToolModulePlugin.js b/webpack-lib/lib/EvalDevToolModulePlugin.js deleted file mode 100644 index a364c3f9d2f..00000000000 --- a/webpack-lib/lib/EvalDevToolModulePlugin.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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").OutputNormalized} OutputOptions */ -/** @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/). - */ -`); - -/** - * @typedef {object} EvalDevToolModulePluginOptions - * @property {OutputOptions["devtoolNamespace"]=} namespace namespace - * @property {string=} sourceUrlComment source url comment - * @property {OutputOptions["devtoolModuleFilenameTemplate"]=} moduleFilenameTemplate module filename template - */ - -class EvalDevToolModulePlugin { - /** - * @param {EvalDevToolModulePluginOptions=} options options - */ - constructor(options = {}) { - this.namespace = options.namespace || ""; - this.sourceUrlComment = options.sourceUrlComment || "\n//# sourceURL=[url]"; - this.moduleFilenameTemplate = - options.moduleFilenameTemplate || - "webpack://[namespace]/[resourcePath]?[loaders]"; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("EvalDevToolModulePlugin", compilation => { - const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); - hooks.renderModuleContent.tap( - "EvalDevToolModulePlugin", - (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 - )})` - : JSON.stringify(content + footer) - });` - ); - cache.set(source, result); - return result; - } - ); - hooks.inlineInRuntimeBailout.tap( - "EvalDevToolModulePlugin", - () => "the eval devtool is used." - ); - hooks.render.tap( - "EvalDevToolModulePlugin", - source => new ConcatSource(devtoolWarning, source) - ); - hooks.chunkHash.tap("EvalDevToolModulePlugin", (chunk, hash) => { - hash.update("EvalDevToolModulePlugin"); - hash.update("2"); - }); - if (compilation.outputOptions.trustedTypes) { - compilation.hooks.additionalModuleRuntimeRequirements.tap( - "EvalDevToolModulePlugin", - (module, set, context) => { - set.add(RuntimeGlobals.createScript); - } - ); - } - }); - } -} - -module.exports = EvalDevToolModulePlugin; diff --git a/webpack-lib/lib/EvalSourceMapDevToolPlugin.js b/webpack-lib/lib/EvalSourceMapDevToolPlugin.js deleted file mode 100644 index a4bb7fd61e5..00000000000 --- a/webpack-lib/lib/EvalSourceMapDevToolPlugin.js +++ /dev/null @@ -1,227 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").DevTool} DevToolOptions */ -/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./NormalModule").SourceMap} SourceMap */ - -/** @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/). - */ -`); - -class EvalSourceMapDevToolPlugin { - /** - * @param {SourceMapDevToolPluginOptions|string} inputOptions Options object - */ - constructor(inputOptions) { - /** @type {SourceMapDevToolPluginOptions} */ - let options; - if (typeof inputOptions === "string") { - options = { - append: inputOptions - }; - } else { - options = inputOptions; - } - this.sourceMapComment = - options.append && typeof options.append !== "function" - ? options.append - : "//# sourceURL=[module]\n//# sourceMappingURL=[url]"; - this.moduleFilenameTemplate = - options.moduleFilenameTemplate || - "webpack://[namespace]/[resource-path]?[hash]"; - this.namespace = options.namespace || ""; - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap( - "EvalSourceMapDevToolPlugin", - compilation => { - const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); - new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation); - const matchModule = ModuleFilenameHelpers.matchObject.bind( - ModuleFilenameHelpers, - options - ); - hooks.renderModuleContent.tap( - "EvalSourceMapDevToolPlugin", - (source, m, { chunk, runtimeTemplate, chunkGraph }) => { - const cachedSource = cache.get(source); - if (cachedSource !== undefined) { - return cachedSource; - } - - /** - * @param {Source} r result - * @returns {Source} result - */ - const result = r => { - cache.set(source, r); - return r; - }; - - if (m instanceof NormalModule) { - const module = /** @type {NormalModule} */ (m); - if (!matchModule(module.resource)) { - return result(source); - } - } else if (m instanceof ConcatenatedModule) { - const concatModule = /** @type {ConcatenatedModule} */ (m); - if (concatModule.rootModule instanceof NormalModule) { - const module = /** @type {NormalModule} */ ( - concatModule.rootModule - ); - if (!matchModule(module.resource)) { - return result(source); - } - } else { - return result(source); - } - } else { - return result(source); - } - - const namespace = compilation.getPath(this.namespace, { - chunk - }); - /** @type {SourceMap} */ - let sourceMap; - let content; - if (source.sourceAndMap) { - const sourceAndMap = source.sourceAndMap(options); - sourceMap = /** @type {SourceMap} */ (sourceAndMap.map); - content = sourceAndMap.source; - } else { - sourceMap = /** @type {SourceMap} */ (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 = /** @type {string} */ (compiler.options.context); - const root = compiler.root; - const modules = sourceMap.sources.map(source => { - if (!source.startsWith("webpack://")) return source; - source = makePathsAbsolute(context, source.slice(10), root); - 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.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 - )})` - : JSON.stringify(content + footer) - });` - ) - ); - } - ); - hooks.inlineInRuntimeBailout.tap( - "EvalDevToolModulePlugin", - () => "the eval-source-map devtool is used." - ); - hooks.render.tap( - "EvalSourceMapDevToolPlugin", - source => new ConcatSource(devtoolWarning, source) - ); - hooks.chunkHash.tap("EvalSourceMapDevToolPlugin", (chunk, hash) => { - hash.update("EvalSourceMapDevToolPlugin"); - hash.update("2"); - }); - if (compilation.outputOptions.trustedTypes) { - compilation.hooks.additionalModuleRuntimeRequirements.tap( - "EvalSourceMapDevToolPlugin", - (module, set, context) => { - set.add(RuntimeGlobals.createScript); - } - ); - } - } - ); - } -} - -module.exports = EvalSourceMapDevToolPlugin; diff --git a/webpack-lib/lib/ExportsInfo.js b/webpack-lib/lib/ExportsInfo.js deleted file mode 100644 index f55dcf2d92d..00000000000 --- a/webpack-lib/lib/ExportsInfo.js +++ /dev/null @@ -1,1624 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { equals } = require("./util/ArrayHelpers"); -const SortableSet = require("./util/SortableSet"); -const makeSerializable = require("./util/makeSerializable"); -const { forEachRuntime } = require("./util/runtime"); - -/** @typedef {import("./Dependency").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./ModuleGraphConnection")} ModuleGraphConnection */ -/** @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 */ - -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"); - -class RestoreProvidedData { - constructor( - exports, - otherProvided, - otherCanMangleProvide, - otherTerminalBinding - ) { - this.exports = exports; - this.otherProvided = otherProvided; - this.otherCanMangleProvide = otherCanMangleProvide; - this.otherTerminalBinding = otherTerminalBinding; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize({ write }) { - write(this.exports); - write(this.otherProvided); - write(this.otherCanMangleProvide); - write(this.otherTerminalBinding); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RestoreProvidedData} RestoreProvidedData - */ - static deserialize({ read }) { - return new RestoreProvidedData(read(), read(), read(), read()); - } -} - -makeSerializable( - RestoreProvidedData, - "webpack/lib/ModuleGraph", - "RestoreProvidedData" -); - -/** @typedef {Map} Exports */ -/** @typedef {string | string[] | false} UsedName */ - -class ExportsInfo { - constructor() { - /** @type {Exports} */ - this._exports = new Map(); - this._otherExportsInfo = new ExportInfo(null); - this._sideEffectsOnlyInfo = new ExportInfo("*side effects only*"); - this._exportsAreOrdered = false; - /** @type {ExportsInfo=} */ - this._redirectTo = undefined; - } - - /** - * @returns {Iterable} all owned exports in any order - */ - get ownedExports() { - return this._exports.values(); - } - - /** - * @returns {Iterable} all owned exports in order - */ - get orderedOwnedExports() { - if (!this._exportsAreOrdered) { - this._sortExports(); - } - return this._exports.values(); - } - - /** - * @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(); - } - - /** - * @returns {Iterable} all exports in order - */ - get orderedExports() { - if (!this._exportsAreOrdered) { - this._sortExports(); - } - if (this._redirectTo !== undefined) { - 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(); - } - - /** - * @returns {ExportInfo} the export info of unlisted exports - */ - get otherExportsInfo() { - if (this._redirectTo !== undefined) - return this._redirectTo.otherExportsInfo; - return this._otherExportsInfo; - } - - /** - * @param {Exports} exports exports - * @private - */ - _sortExportsMap(exports) { - if (exports.size > 1) { - 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; - } - - /** - * @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()) { - if (exportInfo.provided === undefined) { - exportInfo.provided = false; - } - if (exportInfo.canMangleProvide === undefined) { - exportInfo.canMangleProvide = true; - } - } - if (this._redirectTo !== undefined) { - this._redirectTo.setHasProvideInfo(); - } else { - if (this._otherExportsInfo.provided === undefined) { - this._otherExportsInfo.provided = false; - } - if (this._otherExportsInfo.canMangleProvide === undefined) { - this._otherExportsInfo.canMangleProvide = true; - } - } - } - - setHasUseInfo() { - for (const exportInfo of this._exports.values()) { - exportInfo.setHasUseInfo(); - } - this._sideEffectsOnlyInfo.setHasUseInfo(); - if (this._redirectTo !== undefined) { - this._redirectTo.setHasUseInfo(); - } else { - this._otherExportsInfo.setHasUseInfo(); - } - } - - /** - * @param {string} 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; - } - - /** - * @param {string} 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; - } - - /** - * @param {string} 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; - } - - /** - * @param {string[]} name export name - * @returns {ExportInfo | undefined} export info for this name - */ - getReadOnlyExportInfoRecursive(name) { - const exportInfo = this.getReadOnlyExportInfo(name[0]); - if (name.length === 1) return exportInfo; - if (!exportInfo.exportsInfo) return; - return exportInfo.exportsInfo.getReadOnlyExportInfoRecursive(name.slice(1)); - } - - /** - * @param {string[]=} name the export name - * @returns {ExportsInfo | undefined} the nested exports info - */ - getNestedExportsInfo(name) { - if (Array.isArray(name) && name.length > 0) { - const info = this.getReadOnlyExportInfo(name[0]); - if (!info.exportsInfo) return; - return info.exportsInfo.getNestedExportsInfo(name.slice(1)); - } - return this; - } - - /** - * @param {boolean=} canMangle true, if exports can still be mangled (defaults to false) - * @param {Set=} excludeExports list of unaffected exports - * @param {any=} 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; - } - - /** - * @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.setUsedConditionally( - used => used < UsageState.Unknown, - UsageState.Unknown, - runtime - ) - ) { - changed = true; - } - if (this._otherExportsInfo.canMangleUse !== false) { - this._otherExportsInfo.canMangleUse = false; - changed = true; - } - } - return changed; - } - - /** - * @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.setUsed(UsageState.NoInfo, runtime)) { - changed = true; - } - if (this._otherExportsInfo.canMangleUse !== false) { - this._otherExportsInfo.canMangleUse = false; - changed = true; - } - } - return changed; - } - - /** - * @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; - } - - /** - * @param {RuntimeSpec} runtime the runtime - * @returns {boolean} true, when something changed - */ - setUsedForSideEffectsOnly(runtime) { - return this._sideEffectsOnlyInfo.setUsedConditionally( - used => used === UsageState.Unused, - UsageState.Used, - runtime - ); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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) { - // eslint-disable-next-line no-constant-binary-expression - if (!this._redirectTo !== undefined) { - switch (this._otherExportsInfo.getUsed(runtime)) { - case UsageState.NoInfo: - return null; - case UsageState.Unknown: - case UsageState.OnlyPropertiesUsed: - case UsageState.Used: - return true; - } - } - 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)); - } - - /** - * @returns {null | true | string[]} list of exports when known - */ - getProvidedExports() { - // eslint-disable-next-line no-constant-binary-expression - if (!this._redirectTo !== undefined) { - switch (this._otherExportsInfo.provided) { - case undefined: - return null; - case null: - return true; - case true: - return true; - } - } - 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; - } - - /** - * @param {RuntimeSpec} runtime the runtime - * @returns {ExportInfo[]} exports that are relevant (not unused and potential provided) - */ - getRelevantExports(runtime) { - 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; - } - - /** - * @param {string | string[]} name the name of the export - * @returns {boolean | undefined | null} if the export is provided - */ - isExportProvided(name) { - if (Array.isArray(name)) { - const info = this.getReadOnlyExportInfo(name[0]); - if (info.exportsInfo && name.length > 1) { - return info.exportsInfo.isExportProvided(name.slice(1)); - } - return info.provided ? name.length === 1 || undefined : info.provided; - } - const info = this.getReadOnlyExportInfo(name); - return info.provided; - } - - /** - * @param {RuntimeSpec} runtime runtime - * @returns {string} key representing the usage - */ - getUsageKey(runtime) { - 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("|"); - } - - /** - * @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; - } - - /** - * @param {string | string[]} 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); - const info = this.getReadOnlyExportInfo(name[0]); - if (info.exportsInfo && name.length > 1) { - return info.exportsInfo.getUsed(name.slice(1), runtime); - } - return info.getUsed(runtime); - } - const info = this.getReadOnlyExportInfo(name); - return info.getUsed(runtime); - } - - /** - * @param {string | string[]} 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)) { - // TODO improve this - if (name.length === 0) { - if (!this.isUsed(runtime)) return false; - return name; - } - const info = this.getReadOnlyExportInfo(name[0]); - const x = info.getUsedName(name[0], runtime); - if (x === false) return false; - const arr = - /** @type {string[]} */ - (x === name[0] && name.length === 1 ? name : [x]); - if (name.length === 1) { - return arr; - } - if ( - info.exportsInfo && - info.getUsed(runtime) === UsageState.OnlyPropertiesUsed - ) { - const nested = info.exportsInfo.getUsedName(name.slice(1), runtime); - if (!nested) return false; - return arr.concat(nested); - } - return arr.concat(name.slice(1)); - } - const info = this.getReadOnlyExportInfo(name); - const usedName = info.getUsedName(name, runtime); - return usedName; - } - - /** - * @param {Hash} hash the hash - * @param {RuntimeSpec} runtime the runtime - * @returns {void} - */ - updateHash(hash, runtime) { - this._updateHash(hash, runtime, new Set()); - } - - /** - * @param {Hash} hash the hash - * @param {RuntimeSpec} runtime the runtime - * @param {Set} alreadyVisitedExportsInfo for circular references - * @returns {void} - */ - _updateHash(hash, runtime, alreadyVisitedExportsInfo) { - const set = new Set(alreadyVisitedExportsInfo); - set.add(this); - for (const exportInfo of this.orderedExports) { - if (exportInfo.hasInfo(this._otherExportsInfo, runtime)) { - exportInfo._updateHash(hash, runtime, set); - } - } - this._sideEffectsOnlyInfo._updateHash(hash, runtime, set); - this._otherExportsInfo._updateHash(hash, runtime, set); - if (this._redirectTo !== undefined) { - this._redirectTo._updateHash(hash, runtime, set); - } - } - - /** - * @returns {RestoreProvidedData} restore provided data - */ - getRestoreProvidedData() { - const otherProvided = this._otherExportsInfo.provided; - const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide; - const otherTerminalBinding = this._otherExportsInfo.terminalBinding; - const exports = []; - for (const exportInfo of this.orderedExports) { - if ( - exportInfo.provided !== otherProvided || - exportInfo.canMangleProvide !== otherCanMangleProvide || - exportInfo.terminalBinding !== otherTerminalBinding || - exportInfo.exportsInfoOwned - ) { - exports.push({ - name: exportInfo.name, - provided: exportInfo.provided, - canMangleProvide: exportInfo.canMangleProvide, - terminalBinding: exportInfo.terminalBinding, - exportsInfo: exportInfo.exportsInfoOwned - ? /** @type {NonNullable} */ - (exportInfo.exportsInfo).getRestoreProvidedData() - : undefined - }); - } - } - return new RestoreProvidedData( - exports, - otherProvided, - otherCanMangleProvide, - otherTerminalBinding - ); - } - - /** - * @param {{ otherProvided: any, otherCanMangleProvide: any, otherTerminalBinding: any, exports: any }} 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.terminalBinding = exp.terminalBinding; - if (exp.exportsInfo) { - const exportsInfo = exportInfo.createNestedExportsInfo(); - exportsInfo.restoreProvided(exp.exportsInfo); - } - } - if (wasEmpty) this._exportsAreOrdered = true; - } -} - -/** @typedef {{ module: Module, export: string[] }} TargetItemWithoutConnection */ -/** @typedef {{ module: Module, connection: ModuleGraphConnection, export: string[] | undefined }} TargetItem */ -/** @typedef {Map} Target */ - -class ExportInfo { - /** - * @param {string} name the original name of the export - * @param {ExportInfo=} initFrom init values from this ExportInfo - */ - constructor(name, initFrom) { - /** @type {string} */ - this.name = name; - /** - * @private - * @type {string | null} - */ - this._usedName = initFrom ? initFrom._usedName : null; - /** - * @private - * @type {UsageStateType | undefined} - */ - this._globalUsed = initFrom ? initFrom._globalUsed : undefined; - /** - * @private - * @type {Map} - */ - 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 {boolean | null | 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; - /** - * 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; - /** @type {boolean} */ - this.exportsInfoOwned = false; - /** @type {ExportsInfo | undefined} */ - this.exportsInfo = undefined; - /** @type {Target | undefined} */ - this._target = undefined; - if (initFrom && initFrom._target) { - this._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; - } - - // TODO webpack 5 remove - /** - * @private - * @param {*} v v - */ - set used(v) { - throw new Error("REMOVED"); - } - - // TODO webpack 5 remove - /** @private */ - get used() { - throw new Error("REMOVED"); - } - - // TODO webpack 5 remove - /** - * @private - * @param {*} v v - */ - set usedName(v) { - throw new Error("REMOVED"); - } - - // TODO webpack 5 remove - /** @private */ - get usedName() { - throw new Error("REMOVED"); - } - - 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}` - ); - } - - /** - * @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; - } - return changed; - } - - /** - * @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; - } - return changed; - } - - setHasUseInfo() { - if (!this._hasUseInRuntimeInfo) { - this._hasUseInRuntimeInfo = true; - } - if (this.canMangleUse === undefined) { - this.canMangleUse = true; - } - if (this.exportsInfoOwned) { - /** @type {ExportsInfo} */ - (this.exportsInfo).setHasUseInfo(); - } - } - - /** - * @param {function(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 => - this._usedInRuntime.set(/** @type {string} */ (runtime), newValue) - ); - return true; - } - } else { - let changed = false; - forEachRuntime(runtime, _runtime => { - const runtime = /** @type {string} */ (_runtime); - let oldValue = - /** @type {UsageStateType} */ - (this._usedInRuntime.get(runtime)); - if (oldValue === undefined) oldValue = UsageState.Unused; - if (newValue !== oldValue && condition(oldValue)) { - if (newValue === UsageState.Unused) { - this._usedInRuntime.delete(runtime); - } else { - this._usedInRuntime.set(runtime, newValue); - } - changed = true; - } - }); - if (changed) { - if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined; - return true; - } - } - return false; - } - - /** - * @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 => - this._usedInRuntime.set(/** @type {string} */ (runtime), newValue) - ); - return true; - } - } else { - let changed = false; - forEachRuntime(runtime, _runtime => { - const runtime = /** @type {string} */ (_runtime); - let oldValue = - /** @type {UsageStateType} */ - (this._usedInRuntime.get(runtime)); - if (oldValue === undefined) oldValue = UsageState.Unused; - if (newValue !== oldValue) { - if (newValue === UsageState.Unused) { - this._usedInRuntime.delete(runtime); - } else { - this._usedInRuntime.set(runtime, newValue); - } - changed = true; - } - }); - if (changed) { - if (this._usedInRuntime.size === 0) this._usedInRuntime = undefined; - return true; - } - } - return false; - } - - /** - * @param {any} 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; - } - - /** - * @param {any} key the key - * @param {ModuleGraphConnection} connection the target module if a single one - * @param {(string[] | 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 = new Map(); - this._target.set(key, { - connection, - export: /** @type {string[]} */ (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 {string[]} */ (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 {string[]} */ (exportName); - oldTarget.priority = priority; - this._maxTarget = undefined; - return true; - } - return false; - } - - /** - * @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; - } - - /** - * get used name - * @param {string | undefined} fallbackName fallback name for used exports with no name - * @param {RuntimeSpec} runtime check usage for this runtime only - * @returns {string | 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 && - Array.from(runtime).every( - runtime => !this._usedInRuntime.has(runtime) - ) - ) { - return false; - } - } - } - if (this._usedName !== null) return this._usedName; - return /** @type {string | false} */ (this.name || fallbackName); - } - - /** - * @returns {boolean} true, when a mangled name of this export is set - */ - hasUsedName() { - return this._usedName !== null; - } - - /** - * Sets the mangled name of this export - * @param {string} name the new name - * @returns {void} - */ - setUsedName(name) { - this._usedName = name; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {function(TargetItem): boolean} 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 - 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; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {function(Module): boolean} 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()); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {function(Module): boolean} validTargetModuleFilter a valid target module - * @param {Set} 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 - }; - 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.concat(target.export.slice(1)) - : target.export.slice(1) - }; - } - } - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target - * @returns {TargetItem | undefined} the target - */ - getTarget(moduleGraph, resolveTargetFilter = RETURNS_TRUE) { - const result = this._getTarget(moduleGraph, resolveTargetFilter, undefined); - if (result === CIRCULAR) return; - return result; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target - * @param {Set | undefined} alreadyVisited set of already visited export info to avoid circular references - * @returns {TargetItem | CIRCULAR | undefined} the target - */ - _getTarget(moduleGraph, resolveTargetFilter, alreadyVisited) { - /** - * @param {TargetItem | null} inputTarget unresolved target - * @param {Set} alreadyVisited set of already visited export info to avoid circular references - * @returns {TargetItem | 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 {TargetItem} */ - 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.concat( - /** @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 {function(TargetItem): boolean} resolveTargetFilter filter function to further resolve target - * @param {function(TargetItem): ModuleGraphConnection=} updateOriginalConnection updates the original connection instead of using the target connection - * @returns {TargetItem | 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 {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, - export: /** @type {NonNullable} */ (target.export), - priority: 0 - }); - return target; - } - - /** - * @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; - } - - /** - * @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) - ); - } - - /** - * @param {Hash} hash the hash - * @param {RuntimeSpec} runtime the runtime - * @returns {void} - */ - updateHash(hash, runtime) { - this._updateHash(hash, runtime, new Set()); - } - - /** - * @param {Hash} hash the hash - * @param {RuntimeSpec} runtime the runtime - * @param {Set} alreadyVisitedExportsInfo for circular references - */ - _updateHash(hash, runtime, alreadyVisitedExportsInfo) { - hash.update( - `${this._usedName || this.name}${this.getUsed(runtime)}${this.provided}${ - this.terminalBinding - }` - ); - 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.UsageState = UsageState; diff --git a/webpack-lib/lib/ExportsInfoApiPlugin.js b/webpack-lib/lib/ExportsInfoApiPlugin.js deleted file mode 100644 index faf4594bbd0..00000000000 --- a/webpack-lib/lib/ExportsInfoApiPlugin.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - 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 { - /** - * Apply the plugin - * @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() - ); - /** - * @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/webpack-lib/lib/ExternalModule.js b/webpack-lib/lib/ExternalModule.js deleted file mode 100644 index c3fa3357ada..00000000000 --- a/webpack-lib/lib/ExternalModule.js +++ /dev/null @@ -1,999 +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 ConcatenationScope = require("./ConcatenationScope"); -const EnvironmentNotSupportAsyncWarning = require("./EnvironmentNotSupportAsyncWarning"); -const { UsageState } = require("./ExportsInfo"); -const InitFragment = require("./InitFragment"); -const Module = require("./Module"); -const { - JS_TYPES, - CSS_URL_TYPES, - CSS_IMPORT_TYPES -} = require("./ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const Template = require("./Template"); -const StaticExportsDependency = require("./dependencies/StaticExportsDependency"); -const createHash = require("./util/createHash"); -const extractUrlAndGlobal = require("./util/extractUrlAndGlobal"); -const makeSerializable = require("./util/makeSerializable"); -const propertyAccess = require("./util/propertyAccess"); -const { register } = require("./util/serialization"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./ExportsInfo")} ExportsInfo */ -/** @typedef {import("./Generator").GenerateContext} GenerateContext */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ -/** @typedef {import("./javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./util/Hash")} Hash */ -/** @typedef {typeof import("./util/Hash")} HashConstructor */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** @typedef {{ attributes?: ImportAttributes, externalType: "import" | "module" | undefined }} ImportDependencyMeta */ -/** @typedef {{ layer?: string, supports?: string, media?: string }} CssImportDependencyMeta */ -/** @typedef {{ sourceType: "css-url" }} AssetDependencyMeta */ - -/** @typedef {ImportDependencyMeta | CssImportDependencyMeta | AssetDependencyMeta} DependencyMeta */ - -/** - * @typedef {object} SourceData - * @property {boolean=} iife - * @property {string=} init - * @property {string} expression - * @property {InitFragment[]=} chunkInitFragments - * @property {ReadOnlyRuntimeRequirements=} runtimeRequirements - */ - -const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); -const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([RuntimeGlobals.loadScript]); -const RUNTIME_REQUIREMENTS_FOR_MODULE = new Set([ - RuntimeGlobals.definePropertyGetters -]); -const EMPTY_RUNTIME_REQUIREMENTS = new Set([]); - -/** - * @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}` - }; -}; - -/** - * @param {string|string[]} 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 - )}` - }; -}; - -/** - * @param {string|string[]} moduleAndSpecifiers the module request - * @param {string} importMetaName import.meta name - * @param {boolean} needPrefix need to use `node:` prefix for `module` import - * @returns {SourceData} the generated source - */ -const getSourceForCommonJsExternalInNodeModule = ( - moduleAndSpecifiers, - importMetaName, - needPrefix -) => { - const chunkInitFragments = [ - new InitFragment( - `import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "${ - needPrefix ? "node:" : "" - }module";\n`, - InitFragment.STAGE_HARMONY_IMPORTS, - 0, - "external module node-commonjs" - ) - ]; - if (!Array.isArray(moduleAndSpecifiers)) { - return { - chunkInitFragments, - expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify( - moduleAndSpecifiers - )})` - }; - } - const moduleName = moduleAndSpecifiers[0]; - return { - chunkInitFragments, - expression: `__WEBPACK_EXTERNAL_createRequire(${importMetaName}.url)(${JSON.stringify( - moduleName - )})${propertyAccess(moduleAndSpecifiers, 1)}` - }; -}; - -/** - * @param {string|string[]} 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 importName = runtimeTemplate.outputOptions.importFunctionName; - if ( - !runtimeTemplate.supportsDynamicImport() && - (importName === "import" || importName === "module-import") - ) { - throw new Error( - "The target environment doesn't support 'import()' so it's not possible to use external type 'import'" - ); - } - 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" - )});` - }; -}; - -/** - * @param {string} key key - * @param {any | undefined} value value - * @returns {undefined | string} replaced value - */ -const importAssertionReplacer = (key, value) => { - if (key === "_isLegacyAssert") { - return; - } - - return value; -}; - -/** - * @extends {InitFragment} - */ -class ModuleExternalInitFragment extends InitFragment { - /** - * @param {string} request import source - * @param {string=} ident recomputed ident - * @param {ImportDependencyMeta=} dependencyMeta the dependency meta - * @param {string | HashConstructor=} hashFunction the hash function to use - */ - constructor(request, ident, dependencyMeta, hashFunction = "md4") { - if (ident === undefined) { - ident = Template.toIdentifier(request); - if (ident !== request) { - ident += `_${createHash(hashFunction) - .update(request) - .digest("hex") - .slice(0, 8)}`; - } - } - const identifier = `__WEBPACK_EXTERNAL_MODULE_${ident}__`; - super( - `import * as ${identifier} from ${JSON.stringify(request)}${ - dependencyMeta && dependencyMeta.attributes - ? dependencyMeta.attributes._isLegacyAssert - ? ` assert ${JSON.stringify( - dependencyMeta.attributes, - importAssertionReplacer - )}` - : ` with ${JSON.stringify(dependencyMeta.attributes)}` - : "" - };\n`, - InitFragment.STAGE_HARMONY_IMPORTS, - 0, - `external module import ${ident}` - ); - this._ident = ident; - this._request = request; - this._dependencyMeta = request; - this._identifier = identifier; - } - - getNamespaceIdentifier() { - return this._identifier; - } -} - -register( - ModuleExternalInitFragment, - "webpack/lib/ExternalModule", - "ModuleExternalInitFragment", - { - serialize(obj, { write }) { - write(obj._request); - write(obj._ident); - write(obj._dependencyMeta); - }, - deserialize({ read }) { - return new ModuleExternalInitFragment(read(), read(), read()); - } - } -); - -/** - * @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) { - 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(", ")} })`; - } -}; - -/** - * @param {string|string[]} 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 - * @returns {SourceData} the generated source - */ -const getSourceForModuleExternal = ( - moduleAndSpecifiers, - exportsInfo, - runtime, - runtimeTemplate, - dependencyMeta -) => { - if (!Array.isArray(moduleAndSpecifiers)) - moduleAndSpecifiers = [moduleAndSpecifiers]; - const initFragment = new ModuleExternalInitFragment( - moduleAndSpecifiers[0], - undefined, - dependencyMeta, - runtimeTemplate.outputOptions.hashFunction - ); - const baseAccess = `${initFragment.getNamespaceIdentifier()}${propertyAccess( - moduleAndSpecifiers, - 1 - )}`; - const moduleRemapping = generateModuleRemapping( - baseAccess, - exportsInfo, - runtime, - runtimeTemplate - ); - const expression = moduleRemapping || baseAccess; - return { - expression, - init: moduleRemapping - ? `var x = ${runtimeTemplate.basicFunction( - "y", - `var x = {}; ${RuntimeGlobals.definePropertyGetters}(x, y); return x` - )} \nvar y = ${runtimeTemplate.returningFunction( - runtimeTemplate.returningFunction("x"), - "x" - )}` - : undefined, - runtimeRequirements: moduleRemapping - ? RUNTIME_REQUIREMENTS_FOR_MODULE - : undefined, - chunkInitFragments: [initFragment] - }; -}; - -/** - * @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: "var __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();`, - "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", - "var 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 - }; -}; - -/** - * @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`; - -/** - * @param {string|number} 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 - }; -}; - -/** - * @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 */ - -class ExternalModule extends Module { - /** - * @param {string | string[] | RequestRecord} request request - * @param {string} type type - * @param {string} userRequest user request - * @param {DependencyMeta=} dependencyMeta dependency meta - */ - constructor(request, type, userRequest, dependencyMeta) { - super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); - - // Info from Factory - /** @type {string | string[] | Record} */ - this.request = request; - /** @type {string} */ - this.externalType = type; - /** @type {string} */ - this.userRequest = userRequest; - /** @type {DependencyMeta=} */ - this.dependencyMeta = dependencyMeta; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - if ( - this.externalType === "asset" && - this.dependencyMeta && - /** @type {AssetDependencyMeta} */ - (this.dependencyMeta).sourceType === "css-url" - ) { - return CSS_URL_TYPES; - } else if (this.externalType === "css-import") { - return CSS_IMPORT_TYPES; - } - - return JS_TYPES; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return this.userRequest; - } - - /** - * @param {Chunk} chunk the chunk which condition should be checked - * @param {Compilation} compilation the compilation - * @returns {boolean} true, if the chunk is ok for the module - */ - chunkCondition(chunk, { chunkGraph }) { - return this.externalType === "css-import" - ? true - : chunkGraph.getNumberOfEntryModules(chunk) > 0; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `external ${this._resolveExternalType(this.externalType)} ${JSON.stringify(this.request)}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `external ${JSON.stringify(this.request)}`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = { - async: false, - exportsType: undefined - }; - this.buildInfo = { - strict: true, - topLevelDeclarations: new Set(), - module: 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.module) { - 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(); - } - - /** - * restore unsafe cache data - * @param {object} unsafeCacheData data from getUnsafeCacheData - * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching - */ - restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) { - this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory); - } - - /** - * @param {ConcatenationBailoutReasonContext} context context - * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated - */ - getConcatenationBailoutReason({ moduleGraph }) { - switch (this.externalType) { - case "amd": - case "amd-require": - case "umd": - case "umd2": - case "system": - case "jsonp": - return `${this.externalType} externals can't be concatenated`; - } - return undefined; - } - - _getRequestAndExternalType() { - let { request, externalType } = this; - if (typeof request === "object" && !Array.isArray(request)) - request = request[externalType]; - externalType = this._resolveExternalType(externalType); - return { request, externalType }; - } - - /** - * Resolve the detailed external type from the raw external type. - * e.g. resolve "module" or "import" from "module-import" type - * @param {string} externalType raw external type - * @returns {string} 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; - } - - return "asset"; - } - - return externalType; - } - - /** - * @private - * @param {string | string[]} request request - * @param {string} 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 - * @returns {SourceData} the source data - */ - _getSourceData( - request, - externalType, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime, - dependencyMeta - ) { - switch (externalType) { - case "this": - case "window": - case "self": - return getSourceForGlobalVariableExternal(request, this.externalType); - case "global": - return getSourceForGlobalVariableExternal( - request, - runtimeTemplate.globalObject - ); - case "commonjs": - case "commonjs2": - case "commonjs-module": - case "commonjs-static": - return getSourceForCommonJsExternal(request); - case "node-commonjs": - return /** @type {BuildInfo} */ (this.buildInfo).module - ? getSourceForCommonJsExternalInNodeModule( - request, - /** @type {string} */ - (runtimeTemplate.outputOptions.importMetaName), - /** @type {boolean} */ - (runtimeTemplate.supportNodePrefixForCoreModules()) - ) - : getSourceForCommonJsExternal(request); - case "amd": - case "amd-require": - case "umd": - case "umd2": - 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).module)) { - 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) - ); - } - case "var": - case "promise": - case "const": - case "let": - case "assign": - default: - return getSourceForDefaultCase( - this.isOptional(moduleGraph), - request, - runtimeTemplate - ); - } - } - - /** - * @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": { - const sources = new Map(); - sources.set( - "javascript", - new RawSource(`module.exports = ${JSON.stringify(request)};`) - ); - const data = new Map(); - data.set("url", { javascript: request }); - return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data }; - } - case "css-url": { - const sources = new Map(); - const data = new Map(); - data.set("url", { "css-url": request }); - return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS, data }; - } - case "css-import": { - 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 - ); - - let sourceString = sourceData.expression; - if (sourceData.iife) - sourceString = `(function() { return ${sourceString}; }())`; - if (concatenationScope) { - sourceString = `${ - runtimeTemplate.supportsConst() ? "const" : "var" - } ${ConcatenationScope.NAMESPACE_OBJECT_EXPORT} = ${sourceString};`; - concatenationScope.registerNamespaceExport( - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - ); - } else { - sourceString = `module.exports = ${sourceString};`; - } - if (sourceData.init) - sourceString = `${sourceData.init}\n${sourceString}`; - - let data; - if (sourceData.chunkInitFragments) { - data = new Map(); - data.set("chunkInitFragments", sourceData.chunkInitFragments); - } - - const sources = new Map(); - if (this.useSourceMap || this.useSimpleSourceMap) { - sources.set( - "javascript", - new OriginalSource(sourceString, this.identifier()) - ); - } else { - sources.set("javascript", 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 - }; - } - } - } - - /** - * @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; - } - - /** - * @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 - )}` - ); - super.updateHash(hash, context); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.request); - write(this.externalType); - write(this.userRequest); - write(this.dependencyMeta); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.request = read(); - this.externalType = read(); - this.userRequest = read(); - this.dependencyMeta = read(); - - super.deserialize(context); - } -} - -makeSerializable(ExternalModule, "webpack/lib/ExternalModule"); - -module.exports = ExternalModule; diff --git a/webpack-lib/lib/ExternalModuleFactoryPlugin.js b/webpack-lib/lib/ExternalModuleFactoryPlugin.js deleted file mode 100644 index 853a88c0217..00000000000 --- a/webpack-lib/lib/ExternalModuleFactoryPlugin.js +++ /dev/null @@ -1,326 +0,0 @@ -/* - 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 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 { resolveByProperty, cachedSetProperty } = require("./util/cleverMerge"); - -/** @typedef {import("../declarations/WebpackOptions").ExternalItemFunctionData} ExternalItemFunctionData */ -/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ -/** @typedef {import("./Compilation").DepConstructor} DepConstructor */ -/** @typedef {import("./ExternalModule").DependencyMeta} DependencyMeta */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ - -const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9-]+ /; -const EMPTY_RESOLVE_OPTIONS = {}; - -// TODO webpack 6 remove this -const callDeprecatedExternals = util.deprecate( - /** - * @param {TODO} externalsFunction externals function - * @param {string} context context - * @param {string} request request - * @param {(err: Error | null | undefined, value: ExternalValue | undefined, ty: ExternalType | 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" -); - -const cache = new WeakMap(); - -/** - * @template {object} T - * @param {T} obj obj - * @param {TODO} layer layer - * @returns {Omit} result - */ -const resolveLayer = (obj, layer) => { - let map = cache.get(/** @type {object} */ (obj)); - if (map === undefined) { - map = new Map(); - cache.set(/** @type {object} */ (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 */ -/** @typedef {string | undefined} ExternalType */ - -class ExternalModuleFactoryPlugin { - /** - * @param {string | undefined} type default external type - * @param {Externals} externals externals config - */ - constructor(type, externals) { - this.type = type; - this.externals = externals; - } - - /** - * @param {NormalModuleFactory} normalModuleFactory the normal module factory - * @returns {void} - */ - apply(normalModuleFactory) { - const globalType = this.type; - normalModuleFactory.hooks.factorize.tapAsync( - "ExternalModuleFactoryPlugin", - (data, callback) => { - const context = data.context; - const contextInfo = data.contextInfo; - const dependency = data.dependencies[0]; - const dependencyType = data.dependencyType; - - /** - * @param {ExternalValue} value the external config - * @param {ExternalType | undefined} type type of external - * @param {function((Error | null)=, ExternalModule=): void} callback callback - * @returns {void} - */ - const handleExternal = (value, type, callback) => { - if (value === false) { - // Not externals, fallback to original factory - return callback(); - } - /** @type {string | string[] | Record} */ - 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 = 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 = firstItem.slice(0, idx); - externalConfig = [ - firstItem.slice(idx + 1), - ...externalConfig.slice(1) - ]; - } - } - - const resolvedType = /** @type {string} */ (type || globalType); - - // 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.assertions, - externalType - }; - } else if (dependency instanceof CssImportDependency) { - dependencyMeta = { - layer: dependency.layer, - supports: dependency.supports, - media: dependency.media - }; - } - - if ( - resolvedType === "asset" && - dependency instanceof CssUrlDependency - ) { - dependencyMeta = { sourceType: "css-url" }; - } - - callback( - null, - new ExternalModule( - externalConfig, - resolvedType, - dependency.request, - dependencyMeta - ) - ); - }; - - /** - * @param {Externals} externals externals config - * @param {function((Error | null)=, ExternalModule=): void} callback callback - * @returns {void} - */ - const handleExternals = (externals, callback) => { - if (typeof externals === "string") { - if (externals === dependency.request) { - return handleExternal(dependency.request, undefined, callback); - } - } else if (Array.isArray(externals)) { - let i = 0; - const next = () => { - /** @type {boolean | undefined} */ - let asyncFlag; - /** - * @param {(Error | null)=} err err - * @param {ExternalModule=} module module - * @returns {void} - */ - const handleExternalsAndCallback = (err, module) => { - if (err) return callback(err); - if (!module) { - if (asyncFlag) { - asyncFlag = false; - return; - } - return next(); - } - callback(null, module); - }; - - do { - asyncFlag = true; - if (i >= externals.length) return callback(); - handleExternals(externals[i++], handleExternalsAndCallback); - } while (!asyncFlag); - asyncFlag = false; - }; - - next(); - return; - } else if (externals instanceof RegExp) { - if (externals.test(dependency.request)) { - return handleExternal(dependency.request, undefined, callback); - } - } else if (typeof externals === "function") { - /** - * @param {Error | null | undefined} err err - * @param {ExternalValue=} value value - * @param {ExternalType=} 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) => { - 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, - /** @type {TODO} */ - (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") { - const resolvedExternals = resolveLayer( - externals, - contextInfo.issuerLayer - ); - if ( - Object.prototype.hasOwnProperty.call( - resolvedExternals, - dependency.request - ) - ) { - return handleExternal( - resolvedExternals[dependency.request], - undefined, - callback - ); - } - } - callback(); - }; - - handleExternals(this.externals, callback); - } - ); - } -} -module.exports = ExternalModuleFactoryPlugin; diff --git a/webpack-lib/lib/ExternalsPlugin.js b/webpack-lib/lib/ExternalsPlugin.js deleted file mode 100644 index 01e74690777..00000000000 --- a/webpack-lib/lib/ExternalsPlugin.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin"); - -/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ -/** @typedef {import("./Compiler")} Compiler */ - -class ExternalsPlugin { - /** - * @param {string | undefined} type default external type - * @param {Externals} externals externals config - */ - constructor(type, externals) { - this.type = type; - this.externals = externals; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compile.tap("ExternalsPlugin", ({ normalModuleFactory }) => { - new ExternalModuleFactoryPlugin(this.type, this.externals).apply( - normalModuleFactory - ); - }); - } -} - -module.exports = ExternalsPlugin; diff --git a/webpack-lib/lib/FalseIIFEUmdWarning.js b/webpack-lib/lib/FalseIIFEUmdWarning.js deleted file mode 100644 index 79eaa54ae03..00000000000 --- a/webpack-lib/lib/FalseIIFEUmdWarning.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Arka Pratim Chaudhuri @arkapratimc -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); - -class FalseIIFEUmdWarning extends WebpackError { - constructor() { - super(); - this.name = "FalseIIFEUmdWarning"; - this.message = - "Configuration:\nSetting 'output.iife' to 'false' is incompatible with 'output.library.type' set to 'umd'. This configuration may cause unexpected behavior, as UMD libraries are expected to use an IIFE (Immediately Invoked Function Expression) to support various module formats. Consider setting 'output.iife' to 'true' or choosing a different 'library.type' to ensure compatibility.\nLearn more: https://webpack.js.org/configuration/output/"; - } -} - -module.exports = FalseIIFEUmdWarning; diff --git a/webpack-lib/lib/FileSystemInfo.js b/webpack-lib/lib/FileSystemInfo.js deleted file mode 100644 index ed7f327a2c4..00000000000 --- a/webpack-lib/lib/FileSystemInfo.js +++ /dev/null @@ -1,4041 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { create: createResolver } = require("enhanced-resolve"); -const nodeModule = require("module"); -const asyncLib = require("neo-async"); -const { isAbsolute } = require("path"); -const AsyncQueue = require("./util/AsyncQueue"); -const StackedCacheMap = require("./util/StackedCacheMap"); -const createHash = require("./util/createHash"); -const { join, dirname, relative, lstatReadlinkAbsolute } = require("./util/fs"); -const makeSerializable = require("./util/makeSerializable"); -const processAsyncTree = require("./util/processAsyncTree"); - -/** @typedef {import("enhanced-resolve").Resolver} Resolver */ -/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */ -/** @typedef {import("enhanced-resolve").ResolveFunctionAsync} ResolveFunctionAsync */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./logging/Logger").Logger} Logger */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {typeof import("./util/Hash")} Hash */ -/** @typedef {import("./util/fs").IStats} IStats */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("./util/fs").PathLike} PathLike */ -/** @typedef {import("./util/fs").StringCallback} StringCallback */ -/** - * @template T - * @typedef {import("./util/AsyncQueue").Callback} ProcessorCallback - */ -/** - * @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; - -const EMPTY_SET = new Set(); - -const RBDT_RESOLVE_CJS = 0; -const RBDT_RESOLVE_ESM = 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_CJS | RBDT_RESOLVE_ESM | 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"); - -/** - * @typedef {object} FileSystemInfoEntry - * @property {number} safeTime - * @property {number=} timestamp - */ - -/** - * @typedef {object} ResolvedContextFileSystemInfoEntry - * @property {number} safeTime - * @property {string=} timestampHash - */ - -/** - * @typedef {object} ContextFileSystemInfoEntry - * @property {number} safeTime - * @property {string=} timestampHash - * @property {ResolvedContextFileSystemInfoEntry=} resolved - * @property {Set=} symlinks - */ - -/** - * @typedef {object} TimestampAndHash - * @property {number} safeTime - * @property {number=} timestamp - * @property {string} hash - */ - -/** - * @typedef {object} ResolvedContextTimestampAndHash - * @property {number} safeTime - * @property {string=} timestampHash - * @property {string} hash - */ - -/** @typedef {Set} Symlinks */ - -/** - * @typedef {object} ContextTimestampAndHash - * @property {number} safeTime - * @property {string=} timestampHash - * @property {string} hash - * @property {ResolvedContextTimestampAndHash=} resolved - * @property {Symlinks=} symlinks - */ - -/** - * @typedef {object} ContextHash - * @property {string} hash - * @property {string=} resolved - * @property {Symlinks=} symlinks - */ - -/** @typedef {Set} SnapshotContent */ - -/** - * @typedef {object} SnapshotOptimizationEntry - * @property {Snapshot} snapshot - * @property {number} shared - * @property {SnapshotContent | undefined} snapshotContent - * @property {Set | undefined} children - */ - -/** - * @typedef {object} ResolveBuildDependenciesResult - * @property {Set} files list of files - * @property {Set} directories list of directories - * @property {Set} missing list of missing entries - * @property {Map} resolveResults stored resolve results - * @property {object} resolveDependencies dependencies of the resolving - * @property {Set} resolveDependencies.files list of files - * @property {Set} resolveDependencies.directories list of directories - * @property {Set} resolveDependencies.missing list of missing entries - */ - -/** - * @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 { - /** - * @param {() => IteratorResult} next next - */ - constructor(next) { - this.next = next; - } -} - -/** - * @typedef {(snapshot: Snapshot) => (Map | Set | undefined)[]} GetMapsFunction - */ - -class 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 {(snapshot: Snapshot) => (Map | Set | undefined)[]} */ - let getMaps; - /** @type {(Map | Set | undefined)[]} */ - 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 = 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; - } - - /** - * @param {number} value start value - */ - setStartTime(value) { - this._flags = this._flags | 1; - this.startTime = value; - } - - /** - * @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; - } - - /** - * @param {FileTimestamps} value file timestamps - */ - setFileTimestamps(value) { - this._flags = this._flags | 2; - this.fileTimestamps = value; - } - - hasFileHashes() { - return (this._flags & 4) !== 0; - } - - /** - * @param {FileHashes} value file hashes - */ - setFileHashes(value) { - this._flags = this._flags | 4; - this.fileHashes = value; - } - - hasFileTshs() { - return (this._flags & 8) !== 0; - } - - /** - * @param {FileTshs} value file tshs - */ - setFileTshs(value) { - this._flags = this._flags | 8; - this.fileTshs = value; - } - - hasContextTimestamps() { - return (this._flags & 0x10) !== 0; - } - - /** - * @param {ContextTimestamps} value context timestamps - */ - setContextTimestamps(value) { - this._flags = this._flags | 0x10; - this.contextTimestamps = value; - } - - hasContextHashes() { - return (this._flags & 0x20) !== 0; - } - - /** - * @param {ContextHashes} value context hashes - */ - setContextHashes(value) { - this._flags = this._flags | 0x20; - this.contextHashes = value; - } - - hasContextTshs() { - return (this._flags & 0x40) !== 0; - } - - /** - * @param {ContextTshs} value context tshs - */ - setContextTshs(value) { - this._flags = this._flags | 0x40; - this.contextTshs = value; - } - - hasMissingExistence() { - return (this._flags & 0x80) !== 0; - } - - /** - * @param {MissingExistence} value context tshs - */ - setMissingExistence(value) { - this._flags = this._flags | 0x80; - this.missingExistence = value; - } - - hasManagedItemInfo() { - return (this._flags & 0x100) !== 0; - } - - /** - * @param {ManagedItemInfo} value managed item info - */ - setManagedItemInfo(value) { - this._flags = this._flags | 0x100; - this.managedItemInfo = value; - } - - hasManagedFiles() { - return (this._flags & 0x200) !== 0; - } - - /** - * @param {ManagedFiles} value managed files - */ - setManagedFiles(value) { - this._flags = this._flags | 0x200; - this.managedFiles = value; - } - - hasManagedContexts() { - return (this._flags & 0x400) !== 0; - } - - /** - * @param {ManagedContexts} value managed contexts - */ - setManagedContexts(value) { - this._flags = this._flags | 0x400; - this.managedContexts = value; - } - - hasManagedMissing() { - return (this._flags & 0x800) !== 0; - } - - /** - * @param {ManagedMissing} value managed missing - */ - setManagedMissing(value) { - this._flags = this._flags | 0x800; - this.managedMissing = value; - } - - hasChildren() { - return (this._flags & 0x1000) !== 0; - } - - /** - * @param {Children} value children - */ - setChildren(value) { - this._flags = this._flags | 0x1000; - this.children = value; - } - - /** - * @param {Snapshot} child children - */ - addChild(child) { - if (!this.hasChildren()) { - this.setChildren(new Set()); - } - /** @type {Children} */ - (this.children).add(child); - } - - /** - * @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); - } - - /** - * @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(); - } - - /** - * @param {GetMapsFunction} getMaps first - * @returns {Iterable} iterable - */ - _createIterable(getMaps) { - return new SnapshotIterable(this, getMaps); - } - - /** - * @returns {Iterable} iterable - */ - getFileIterable() { - if (this._cachedFileIterable === undefined) { - this._cachedFileIterable = this._createIterable(s => [ - s.fileTimestamps, - s.fileHashes, - s.fileTshs, - s.managedFiles - ]); - } - return this._cachedFileIterable; - } - - /** - * @returns {Iterable} iterable - */ - getContextIterable() { - if (this._cachedContextIterable === undefined) { - this._cachedContextIterable = this._createIterable(s => [ - s.contextTimestamps, - s.contextHashes, - s.contextTshs, - s.managedContexts - ]); - } - return this._cachedContextIterable; - } - - /** - * @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; - -/** - * @template U, T - * @typedef {U extends true ? Set : Map} SnapshotOptimizationValue - */ - -/** - * @template T - * @template {boolean} [U=false] - */ -class SnapshotOptimization { - /** - * @param {function(Snapshot): boolean} has has value - * @param {function(Snapshot): SnapshotOptimizationValue | undefined} get get value - * @param {function(Snapshot, 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; - } - - /** - * @param {Snapshot} newSnapshot snapshot - * @param {Set} capturedFiles files to snapshot/share - * @returns {void} - */ - optimize(newSnapshot, capturedFiles) { - /** - * @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); - }; - /** - * @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); - continue; - } 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; - } - 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); - continue; - } - } - 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 - 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; - } - 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; - } -} - -/** - * @param {string} str input - * @returns {string} result - */ -const parseString = str => { - if (str[0] === "'" || str[0] === "`") - str = `"${str.slice(1, -1).replace(/"/g, '\\"')}"`; - return JSON.parse(str); -}; - -/* istanbul ignore next */ -/** - * @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; -}; - -/** - * @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; -}; - -/** - * @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); -}; - -/** - * @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; -}; - -/** - * @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; -}; - -/** - * @template T - * @param {Set} source source - * @param {Set} target target - */ -const addAll = (source, target) => { - for (const key of source) target.add(key); -}; - -/** @typedef {Set} LoggedPaths */ - -/** - * Used to access information about the filesystem in a cached way - */ -class 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 {string | Hash=} options.hashFunction the hash function to use - */ - constructor( - fs, - { - unmanagedPaths = [], - managedPaths = [], - immutablePaths = [], - logger, - hashFunction = "md4" - } = {} - ) { - 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 = Array.from(unmanagedPaths); - this.unmanagedPathsWithSlash = /** @type {string[]} */ ( - _unmanagedPaths.filter(p => typeof p === "string") - ).map(p => join(fs, p, "_").slice(0, -1)); - this.unmanagedPathsRegExps = /** @type {RegExp[]} */ ( - _unmanagedPaths.filter(p => typeof p !== "string") - ); - - this.managedPaths = Array.from(managedPaths); - this.managedPathsWithSlash = /** @type {string[]} */ ( - this.managedPaths.filter(p => typeof p === "string") - ).map(p => join(fs, p, "_").slice(0, -1)); - - this.managedPathsRegExps = /** @type {RegExp[]} */ ( - this.managedPaths.filter(p => typeof p !== "string") - ); - this.immutablePaths = Array.from(immutablePaths); - this.immutablePathsWithSlash = /** @type {string[]} */ ( - this.immutablePaths.filter(p => typeof p === "string") - ).map(p => join(fs, p, "_").slice(0, -1)); - this.immutablePathsRegExps = /** @type {RegExp[]} */ ( - 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); - /** - * @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() - ); - } - - /** - * @param {string} path path - * @param {string} reason reason - * @param {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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {string} path file path - * @param {function((WebpackError | null)=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function - * @returns {void} - */ - getFileTimestamp(path, callback) { - const cache = this._fileTimestamps.get(path); - if (cache !== undefined) return callback(null, cache); - this.fileTimestampQueue.add(path, callback); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, (ResolvedContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function - * @returns {void} - */ - getContextTimestamp(path, callback) { - const cache = this._contextTimestamps.get(path); - if (cache !== undefined) { - if (cache === "ignore") return callback(null, "ignore"); - const resolved = getResolvedTimestamp(cache); - if (resolved !== undefined) return callback(null, resolved); - return this._resolveContextTimestamp( - /** @type {ResolvedContextFileSystemInfoEntry} */ - (cache), - callback - ); - } - this.contextTimestampQueue.add(path, (err, _entry) => { - if (err) return callback(err); - const entry = /** @type {ContextFileSystemInfoEntry} */ (_entry); - const resolved = getResolvedTimestamp(entry); - if (resolved !== undefined) return callback(null, resolved); - this._resolveContextTimestamp(entry, callback); - }); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, (ContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function - * @returns {void} - */ - _getUnresolvedContextTimestamp(path, callback) { - const cache = this._contextTimestamps.get(path); - if (cache !== undefined) return callback(null, cache); - this.contextTimestampQueue.add(path, callback); - } - - /** - * @param {string} path file path - * @param {function((WebpackError | null)=, (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); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, 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); - }); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, (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); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, (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); - }); - } - - /** - * @param {string} path context path - * @param {function((WebpackError | null)=, (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", "node"], - exportsFields: ["exports"], - fileSystem: this.fs - }); - const resolveCjsAsChild = createResolver({ - extensions: [".js", ".json", ".node"], - conditionNames: ["require", "node"], - exportsFields: [], - fileSystem: this.fs - }); - const resolveEsm = createResolver({ - extensions: [".js", ".json", ".node"], - fullySpecified: true, - conditionNames: ["import", "node"], - exportsFields: ["exports"], - fileSystem: this.fs - }); - return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild }; - } - - /** - * @param {string} context context directory - * @param {Iterable} deps dependencies - * @param {function((Error | null)=, ResolveBuildDependenciesResult=): void} callback callback function - * @returns {void} - */ - resolveBuildDependencies(context, deps, callback) { - const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } = - this._createBuildDependenciesResolvers(); - - /** @type {Set} */ - const files = new Set(); - /** @type {Set} */ - const fileSymlinks = new Set(); - /** @type {Set} */ - const directories = new Set(); - /** @type {Set} */ - const directorySymlinks = new Set(); - /** @type {Set} */ - const missing = new Set(); - /** @type {Set} */ - const resolveFiles = new Set(); - /** @type {Set} */ - const resolveDirectories = new Set(); - /** @type {Set} */ - const resolveMissing = new Set(); - /** @type {Map} */ - const resolveResults = new Map(); - const invalidResolveResults = new Set(); - const resolverContext = { - fileDependencies: resolveFiles, - contextDependencies: resolveDirectories, - missingDependencies: resolveMissing - }; - /** - * @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 */ - - /** - * @param {Job} job job - * @returns {`resolve commonjs file ${string}${string}`|`resolve esm file ${string}${string}`|`resolve esm ${string}${string}`|`resolve directory ${string}`|`file ${string}`|`unknown ${string} ${string}`|`resolve commonjs ${string}${string}`|`directory ${string}`|`file dependencies ${string}`|`directory dependencies ${string}`} result - */ - const jobToString = job => { - switch (job.type) { - case RBDT_RESOLVE_CJS: - return `resolve commonjs ${job.path}${expectedToString( - job.expected - )}`; - case RBDT_RESOLVE_ESM: - return `resolve esm ${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}`; - }; - /** - * @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_CJS, - context, - path: dep, - expected: undefined, - issuer: undefined - }) - ), - 20, - (job, push, callback) => { - const { type, context, path, expected } = job; - /** - * @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(); - } - ); - }; - /** - * @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(); - } - ); - }; - switch (type) { - case RBDT_RESOLVE_CJS: { - const isDirectory = /[\\/]$/.test(path); - if (isDirectory) { - resolveDirectory(path.slice(0, -1)); - } else { - resolveFile(path, "f", resolveCjs); - } - break; - } - case RBDT_RESOLVE_ESM: { - const isDirectory = /[\\/]$/.test(path); - if (isDirectory) { - resolveDirectory(path.slice(0, -1)); - } else { - resolveFile(path); - } - break; - } - case RBDT_RESOLVE_DIRECTORY: { - resolveDirectory(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)) { - 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 = require("es-module-lexer"); - 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); - for (const imp of imports) { - try { - let dependency; - if (imp.d === -1) { - // import ... from "..." - dependency = parseString( - source.substring(imp.s - 1, imp.e + 1) - ); - } else if (imp.d > -1) { - // import() - const expr = source.substring(imp.s, imp.e).trim(); - dependency = parseString(expr); - } else { - // e.g. import.meta - continue; - } - - // we should not track Node.js build dependencies - if (dependency.startsWith("node:")) continue; - if (builtinModules.has(dependency)) continue; - - push({ - type: RBDT_RESOLVE_ESM_FILE, - context, - path: dependency, - expected: imp.d > -1 ? false : undefined, - issuer: job - }); - } catch (err1) { - logger.warn( - `Parsing of ${path} for build dependencies failed at 'import(${source.substring( - 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); - let packageData; - try { - packageData = JSON.parse( - /** @type {Buffer} */ (content).toString("utf-8") - ); - } catch (parseErr) { - return callback(/** @type {Error} */ (parseErr)); - } - const depsObject = packageData.dependencies; - const optionalDepsObject = packageData.optionalDependencies; - const allDeps = new 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 - } - }); - } - ); - } - - /** - * @param {Map} resolveResults results from resolving - * @param {function((Error | null)=, 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; - } - }, - /** - * @param {Error | typeof INVALID=} err error or invalid flag - * @returns {void} - */ - err => { - if (err === INVALID) { - return callback(null, false); - } - if (err) { - return callback(err); - } - return callback(null, true); - } - ); - } - - /** - * @param {number | null | undefined} startTime when processing the files has started - * @param {Iterable | null} files all files - * @param {Iterable | null} directories all directories - * @param {Iterable | null} missing all missing files or directories - * @param {SnapshotOptions | null | undefined} options options object (for future extensions) - * @param {function(WebpackError | null, 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); - } - }; - /** - * @param {string} path path - * @param {Set} 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; - }; - /** - * @param {Iterable} items items - * @param {Set} managedSet managed set - * @returns {Set} result - */ - const captureNonManaged = (items, managedSet) => { - const capturedItems = new Set(); - for (const path of items) { - if (!checkManaged(path, managedSet)) capturedItems.add(path); - } - return capturedItems; - }; - /** - * @param {Set} capturedFiles captured files - */ - const processCapturedFiles = capturedFiles => { - 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) { - if (cache !== "ignore") { - fileTimestamps.set(path, 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)); - } - /** - * @param {Set} capturedDirectories captured directories - */ - const processCapturedDirectories = capturedDirectories => { - 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++; - /** - * @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); - let resolved; - if ( - cache !== undefined && - (resolved = getResolvedHash(cache)) !== undefined - ) { - contextHashes.set(path, resolved); - } else { - jobs++; - /** - * @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; - let resolved; - if ( - cache !== undefined && - (resolved = getResolvedTimestamp(cache)) !== undefined - ) { - contextTimestamps.set(path, resolved); - } else { - jobs++; - /** - * @param {(Error | null)=} err error - * @param {(FileSystemInfoEntry | "ignore" | null)=} 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 {FileSystemInfoEntry | null} */ - (entry) - ); - jobDone(); - } - }; - if (cache !== undefined) { - this._resolveContextTimestamp( - /** @type {ContextFileSystemInfoEntry} */ - (cache), - callback - ); - } else { - this.getContextTimestamp(path, callback); - } - } - } - break; - } - }; - if (directories) { - processCapturedDirectories( - captureNonManaged(directories, managedContexts) - ); - } - /** - * @param {Set} capturedMissing captured missing - */ - const processCapturedMissing = capturedMissing => { - this._missingExistenceOptimization.optimize(snapshot, capturedMissing); - for (const path of capturedMissing) { - const cache = this._fileTimestamps.get(path); - if (cache !== undefined) { - 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 - /** - * @param {Set} set set - * @param {function(Set): void} fn fn - */ - const process = (set, fn) => { - if (set.size === 0) return; - 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(); - } - - /** - * @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; - } - - /** - * @param {Snapshot} snapshot the snapshot made - * @param {function((WebpackError | null)=, boolean=): void} 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); - } - - /** - * @param {Snapshot} snapshot the snapshot made - * @param {function((WebpackError | null)=, boolean=): void} 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); - } - }; - /** - * @param {string} path path - * @param {WebpackError} err err - */ - const invalidWithError = (path, err) => { - if (this._remainingLogs > 0) { - this._log(path, "error occurred: %s", err); - } - invalid(); - }; - /** - * @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; - }; - /** - * @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; - }; - /** - * @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; - }; - /** - * @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()) { - /** - * @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) { - if (cache !== "ignore" && !checkFile(path, 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(); - } - }); - } - } - } - /** - * @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) { - if (cache === "ignore" || !checkFile(path, 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; - let resolved; - if ( - cache !== undefined && - (resolved = getResolvedTimestamp(cache)) !== undefined - ) { - if (!checkContext(path, resolved, ts)) { - invalid(); - return; - } - } else { - jobs++; - /** - * @param {(WebpackError | null)=} err error - * @param {(ResolvedContextFileSystemInfoEntry | "ignore" | null)=} 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 (cache !== undefined) { - this._resolveContextTimestamp( - /** @type {ContextFileSystemInfoEntry} */ - (cache), - callback - ); - } else { - this.getContextTimestamp(path, callback); - } - } - } - } - /** - * @param {string} path path - * @param {string | null} hash hash - */ - const processContextHashSnapshot = (path, hash) => { - const cache = this._contextHashes.get(path); - let resolved; - if ( - cache !== undefined && - (resolved = getResolvedHash(cache)) !== undefined - ) { - if (!checkHash(path, resolved, hash)) { - invalid(); - } - } else { - jobs++; - /** - * @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; - let resolved; - if ( - cache !== undefined && - (resolved = getResolvedTimestamp(cache)) !== undefined - ) { - if ( - !checkContext( - path, - /** @type {ResolvedContextFileSystemInfoEntry | null} */ - (resolved), - tsh, - false - ) - ) { - processContextHashSnapshot(path, tsh && tsh.hash); - } - } else { - jobs++; - /** - * @param {(WebpackError | null)=} err error - * @param {(ResolvedContextFileSystemInfoEntry | "ignore" | null)=} entry entry - * @returns {void} - */ - const callback = (err, entry) => { - if (err) return invalidWithError(path, err); - if ( - !checkContext( - path, - // TODO: test with `"ignore"` - /** @type {ResolvedContextFileSystemInfoEntry | null} */ - (entry), - tsh, - false - ) - ) { - processContextHashSnapshot(path, tsh && tsh.hash); - } - jobDone(); - }; - if (cache !== undefined) { - this._resolveContextTimestamp( - /** @type {ContextFileSystemInfoEntry} */ - (cache), - 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) { - 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); - } - } - - /** - * @type {Processor} - * @private - */ - _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); - 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); - }); - } - - /** - * @type {Processor} - * @private - */ - _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 = /** @type {string} */ (hash.digest("hex")); - - this._fileHashes.set(path, digest); - - callback(null, digest); - }); - } - - /** - * @param {string} path path - * @param {function(WebpackError | null, TimestampAndHash=) : void} callback callback - * @private - */ - _getFileTimestampAndHash(path, callback) { - /** - * @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, /** @type {TODO} */ (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)); - }); - } - } - - /** - * @template T - * @template ItemType - * @param {object} options options - * @param {string} options.path path - * @param {function(string): ItemType} options.fromImmutablePath called when context item is an immutable path - * @param {function(string): ItemType} options.fromManagedItem called when context item is a managed path - * @param {function(string, string, function((WebpackError | null)=, ItemType=): void): void} options.fromSymlink called when context item is a symlink - * @param {function(string, IStats, function((WebpackError | null)=, (ItemType | null)=): void): void} options.fromFile called when context item is a file - * @param {function(string, IStats, function((WebpackError | null)=, ItemType=): void): void} options.fromDirectory called when context item is a directory - * @param {function(string[], ItemType[]): T} options.reduce called from all context items - * @param {function((Error | null)=, (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); - } - ); - }); - } - - /** - * @type {Processor} - * @private - */ - _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) - return callback(null, cache === "ignore" ? 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) => { - 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 = /** @type {string} */ (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); - } - ); - } - - /** - * @param {ContextFileSystemInfoEntry} entry entry - * @param {function((WebpackError | null)=, (ResolvedContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback - * @returns {void} - */ - _resolveContextTimestamp(entry, callback) { - /** @type {string[]} */ - const hashes = []; - let safeTime = 0; - 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) 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: /** @type {string} */ (hash.digest("hex")) - }) - ); - } - ); - } - - /** - * @type {Processor} - * @private - */ - _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 || ""); - }); - }, - /** - * @param {string[]} files files - * @param {(string | ContextHash)[]} fileHashes hashes - * @returns {ContextHash} reduced hash - */ - reduce: (files, fileHashes) => { - 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: /** @type {string} */ (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); - } - ); - } - - /** - * @param {ContextHash} entry context hash - * @param {function(WebpackError | null, string=): void} callback callback - * @returns {void} - */ - _resolveContextHash(entry, callback) { - /** @type {string[]} */ - const hashes = []; - 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) 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 = /** @type {string} */ (hash.digest("hex"))) - ); - } - ); - } - - /** - * @type {Processor} - * @private - */ - _readContextTimestampAndHash(path, callback) { - /** - * @param {ContextFileSystemInfoEntry | "ignore" | null} 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 | null} */ (null), - fromManagedItem: info => ({ - safeTime: 0, - timestampHash: info, - hash: info || "" - }), - fromSymlink: (file, target, callback) => { - callback( - null, - /** @type {TODO} */ - ({ - 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); - }); - }, - /** - * @param {string[]} files files - * @param {(Partial & Partial | string | null)[]} results results - * @returns {ContextTimestampAndHash} tsh - */ - reduce: (files, results) => { - 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: /** @type {string} */ (tsHash.digest("hex")), - hash: /** @type {string} */ (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); - } - ); - } - } - - /** - * @param {ContextTimestampAndHash} entry entry - * @param {ProcessorCallback} callback callback - * @returns {void} - */ - _resolveContextTsh(entry, callback) { - /** @type {string[]} */ - const hashes = []; - /** @type {string[]} */ - const tsHashes = []; - let safeTime = 0; - 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) 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: /** @type {string} */ (tsHash.digest("hex")), - hash: /** @type {string} */ (hash.digest("hex")) - }) - ); - } - ); - } - - /** - * @type {Processor>} - * @private - */ - _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); - }); - } - - /** - * @type {Processor} - * @private - */ - _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)); - } - let data; - try { - data = JSON.parse(/** @type {Buffer} */ (content).toString("utf-8")); - } 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; - const map = new Map(); - for (const [path, info] of this._fileTimestamps) { - if (info) map.set(path, typeof info === "object" ? info.safeTime : null); - } - return (this._cachedDeprecatedFileTimestamps = map); - } - - getDeprecatedContextTimestamps() { - if (this._cachedDeprecatedContextTimestamps !== undefined) - return this._cachedDeprecatedContextTimestamps; - const map = new Map(); - for (const [path, info] of this._contextTimestamps) { - if (info) map.set(path, typeof info === "object" ? info.safeTime : null); - } - return (this._cachedDeprecatedContextTimestamps = map); - } -} - -module.exports = FileSystemInfo; -module.exports.Snapshot = Snapshot; diff --git a/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js b/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js deleted file mode 100644 index eb3ee4cf43d..00000000000 --- a/webpack-lib/lib/FlagAllModulesAsUsedPlugin.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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 { - /** - * @param {string} explanation explanation - */ - constructor(explanation) { - this.explanation = explanation; - } - - /** - * Apply the plugin - * @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); - if (module.factoryMeta === undefined) { - module.factoryMeta = {}; - } - /** @type {FactoryMeta} */ - (module.factoryMeta).sideEffectFree = false; - } - }); - }); - } -} - -module.exports = FlagAllModulesAsUsedPlugin; diff --git a/webpack-lib/lib/FlagDependencyExportsPlugin.js b/webpack-lib/lib/FlagDependencyExportsPlugin.js deleted file mode 100644 index aacbb3d2789..00000000000 --- a/webpack-lib/lib/FlagDependencyExportsPlugin.js +++ /dev/null @@ -1,420 +0,0 @@ -/* - 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"); - -/** @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("./Module")} Module */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ - -const PLUGIN_NAME = "FlagDependencyExportsPlugin"; -const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; - -class FlagDependencyExportsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - 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( - 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(); - } - // 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(); - } - const memCache = moduleMemCaches && moduleMemCaches.get(module); - const memCacheValue = memCache && memCache.get(this); - if (memCacheValue !== undefined) { - statRestoredFromMemCache++; - exportsInfo.restoreProvided(memCacheValue); - return callback(); - } - cache.get( - module.identifier(), - /** @type {BuildInfo} */ - (module.buildInfo).hash, - (err, result) => { - if (err) return callback(err); - - if (result !== undefined) { - statRestoredFromCache++; - exportsInfo.restoreProvided(result); - } else { - statNotCached++; - // Without cached info enqueue module for determining the exports - queue.enqueue(module); - exportsInfo.setHasProvideInfo(); - } - callback(); - } - ); - }, - 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; - - /** - * @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); - } - }; - - /** - * @param {Dependency} dep the dependency - * @returns {void} - */ - const processDependency = dep => { - const exportDesc = dep.getExports(moduleGraph); - if (!exportDesc) return; - exportsSpecsFromDependencies.set(dep, exportDesc); - }; - - /** - * @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 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) { - let name; - let canMangle = globalCanMangle; - let terminalBinding = globalTerminalBinding; - let exports; - let from = globalFrom; - let fromExport; - let priority = globalPriority; - let hidden = false; - 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.hidden !== undefined) - hidden = exportNameOrSpec.hidden; - } - const exportInfo = exportsInfo.getExportInfo(name); - - if ( - exportInfo.provided === false || - exportInfo.provided === null - ) { - exportInfo.provided = true; - changed = true; - } - - if ( - exportInfo.canMangleProvide !== false && - canMangle === false - ) { - exportInfo.canMangleProvide = false; - changed = true; - } - - if (terminalBinding && !exportInfo.terminalBinding) { - exportInfo.terminalBinding = 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); - 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); - moduleGraph.unfreeze(); - for (const [dep, exportsSpec] of exportsSpecsFromDependencies) { - processExportsSpec(dep, exportsSpec); - } - - if (cacheable) { - modulesToStore.add(module); - } - - if (changed) { - notifyDependencies(); - } - } - 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); - } - ); - } - ); - } - ); - - /** @type {WeakMap} */ - const providedExportsCache = new WeakMap(); - compilation.hooks.rebuildModule.tap(PLUGIN_NAME, module => { - providedExportsCache.set( - module, - moduleGraph.getExportsInfo(module).getRestoreProvidedData() - ); - }); - compilation.hooks.finishRebuildingModule.tap(PLUGIN_NAME, module => { - moduleGraph - .getExportsInfo(module) - .restoreProvided(providedExportsCache.get(module)); - }); - }); - } -} - -module.exports = FlagDependencyExportsPlugin; diff --git a/webpack-lib/lib/FlagDependencyUsagePlugin.js b/webpack-lib/lib/FlagDependencyUsagePlugin.js deleted file mode 100644 index 247dbf90528..00000000000 --- a/webpack-lib/lib/FlagDependencyUsagePlugin.js +++ /dev/null @@ -1,347 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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("./Chunk")} Chunk */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("./ExportsInfo")} ExportsInfo */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -const { NO_EXPORTS_REFERENCED, EXPORTS_OBJECT_REFERENCED } = Dependency; - -const PLUGIN_NAME = "FlagDependencyUsagePlugin"; -const PLUGIN_LOGGER_NAME = `webpack.${PLUGIN_NAME}`; - -class FlagDependencyUsagePlugin { - /** - * @param {boolean} global do a global analysis instead of per runtime - */ - constructor(global) { - this.global = global; - } - - /** - * Apply the plugin - * @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( - { 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<[Module, RuntimeSpec]>} */ - const queue = new TupleQueue(); - - /** - * @param {Module} module module to process - * @param {(string[] | ReferencedExport)[]} 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; - } - for (const usedExportInfo of usedExports) { - let usedExport; - let canMangle = true; - if (Array.isArray(usedExportInfo)) { - usedExport = usedExportInfo; - } else { - usedExport = usedExportInfo.name; - canMangle = usedExportInfo.canMangle !== 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; - } - 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 ( - !forceSideEffects && - module.factoryMeta !== undefined && - module.factoryMeta.sideEffectFree - ) { - return; - } - if (exportsInfo.setUsedForSideEffectsOnly(runtime)) { - queue.enqueue(module, runtime); - } - } - }; - - /** - * @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) => { - /** @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 ( - !this.global && - b.groupOptions && - b.groupOptions.entryOptions - ) { - processModule( - b, - 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 { - 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 - }); - } - } - } - } - } - } - - for (const [module, referencedExports] of map) { - if (Array.isArray(referencedExports)) { - processReferencedModule( - module, - referencedExports, - runtime, - forceSideEffects - ); - } else { - processReferencedModule( - module, - Array.from(referencedExports.values()), - runtime, - forceSideEffects - ); - } - } - }; - - logger.time("initialize exports usage"); - for (const module of modules) { - const exportsInfo = moduleGraph.getExportsInfo(module); - exportInfoToModuleMap.set(exportsInfo, module); - exportsInfo.setHasUseInfo(); - } - logger.timeEnd("initialize exports usage"); - - logger.time("trace exports usage in graph"); - - /** - * @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 [module, runtime] = /** @type {[Module, RuntimeSpec]} */ ( - queue.dequeue() - ); - processModule(module, runtime, false); - } - logger.timeEnd("trace exports usage in graph"); - } - ); - }); - } -} - -module.exports = FlagDependencyUsagePlugin; diff --git a/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js b/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js deleted file mode 100644 index d2826d12fb2..00000000000 --- a/webpack-lib/lib/FlagEntryExportAsUsedPlugin.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - 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 { - /** - * @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; - } - - /** - * Apply the plugin - * @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/webpack-lib/lib/Generator.js b/webpack-lib/lib/Generator.js deleted file mode 100644 index 2764305757c..00000000000 --- a/webpack-lib/lib/Generator.js +++ /dev/null @@ -1,155 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./ConcatenationScope")} ConcatenationScope */ -/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("./Module").RuntimeRequirements} RuntimeRequirements */ -/** @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 {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 {string} type which kind of code should be generated - * @property {function(): Map=} getData get access to the code generation data - */ - -/** - * @typedef {object} UpdateHashContext - * @property {NormalModule} module the module - * @property {ChunkGraph} chunkGraph - * @property {RuntimeSpec} runtime - * @property {RuntimeTemplate=} runtimeTemplate - */ - -class Generator { - /** - * @param {Record} map map of types - * @returns {ByTypeGenerator} generator by type - */ - static byType(map) { - return new ByTypeGenerator(map); - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * @abstract - * @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("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @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}`; - } - - /** - * @param {Hash} hash hash that will be modified - * @param {UpdateHashContext} updateHashContext context for updating hash - */ - updateHash(hash, { module, runtime }) { - // no nothing - } -} - -class ByTypeGenerator extends Generator { - /** - * @param {Record} map map of types - */ - constructor(map) { - super(); - this.map = map; - this._types = new Set(Object.keys(map)); - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return this._types; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type = "javascript") { - const t = type; - const generator = this.map[t]; - return generator ? generator.getSize(module, t) : 0; - } - - /** - * @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, generateContext); - } -} - -module.exports = Generator; diff --git a/webpack-lib/lib/GraphHelpers.js b/webpack-lib/lib/GraphHelpers.js deleted file mode 100644 index 65d7087281d..00000000000 --- a/webpack-lib/lib/GraphHelpers.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("./Module")} Module */ - -/** - * @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); - } -}; - -module.exports.connectChunkGroupAndChunk = connectChunkGroupAndChunk; -module.exports.connectChunkGroupParentAndChild = - connectChunkGroupParentAndChild; diff --git a/webpack-lib/lib/HarmonyLinkingError.js b/webpack-lib/lib/HarmonyLinkingError.js deleted file mode 100644 index 8259beca634..00000000000 --- a/webpack-lib/lib/HarmonyLinkingError.js +++ /dev/null @@ -1,16 +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; - } -}; diff --git a/webpack-lib/lib/HookWebpackError.js b/webpack-lib/lib/HookWebpackError.js deleted file mode 100644 index 84702401a37..00000000000 --- a/webpack-lib/lib/HookWebpackError.js +++ /dev/null @@ -1,91 +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 */ - -/** - * @template T - * @callback Callback - * @param {Error | null} err - * @param {T=} stats - * @returns {void} - */ - -class HookWebpackError extends WebpackError { - /** - * Creates an instance of HookWebpackError. - * @param {Error} error inner error - * @param {string} hook name of hook - */ - constructor(error, hook) { - super(error.message); - - this.name = "HookWebpackError"; - this.hook = hook; - this.error = error; - this.hideStack = true; - this.details = `caused by plugins in ${hook}\n${error.stack}`; - - this.stack += `\n-- inner error --\n${error.stack}`; - } -} - -module.exports = HookWebpackError; - -/** - * @param {Error} error an error - * @param {string} hook name of the hook - * @returns {WebpackError} a webpack error - */ -const makeWebpackError = (error, hook) => { - if (error instanceof WebpackError) return error; - return new HookWebpackError(error, hook); -}; -module.exports.makeWebpackError = makeWebpackError; - -/** - * @template T - * @param {function(WebpackError | null, T=): void} callback webpack error callback - * @param {string} hook name of hook - * @returns {Callback} generic callback - */ -const makeWebpackErrorCallback = (callback, hook) => (err, result) => { - if (err) { - if (err instanceof WebpackError) { - callback(err); - return; - } - callback(new HookWebpackError(err, hook)); - return; - } - callback(null, result); -}; - -module.exports.makeWebpackErrorCallback = makeWebpackErrorCallback; - -/** - * @template T - * @param {function(): T} fn function which will be wrapping in try catch - * @param {string} hook name of hook - * @returns {T} the result - */ -const tryRunOrWebpackError = (fn, hook) => { - let r; - try { - r = fn(); - } catch (err) { - if (err instanceof WebpackError) { - throw err; - } - throw new HookWebpackError(/** @type {Error} */ (err), hook); - } - return r; -}; - -module.exports.tryRunOrWebpackError = tryRunOrWebpackError; diff --git a/webpack-lib/lib/HotModuleReplacementPlugin.js b/webpack-lib/lib/HotModuleReplacementPlugin.js deleted file mode 100644 index 5eb7c76d0f9..00000000000 --- a/webpack-lib/lib/HotModuleReplacementPlugin.js +++ /dev/null @@ -1,875 +0,0 @@ -/* - 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 ChunkGraph = require("./ChunkGraph"); -const Compilation = require("./Compilation"); -const HotUpdateChunk = require("./HotUpdateChunk"); -const NormalModule = require("./NormalModule"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const WebpackError = require("./WebpackError"); -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 HotModuleReplacementRuntimeModule = require("./hmr/HotModuleReplacementRuntimeModule"); -const JavascriptParser = require("./javascript/JavascriptParser"); -const { - evaluateToIdentifier -} = require("./javascript/JavascriptParserHelpers"); -const { find, isSubset } = require("./util/SetHelpers"); -const TupleSet = require("./util/TupleSet"); -const { compareModulesById } = require("./util/comparators"); -const { - getRuntimeKey, - keyToRuntime, - forEachRuntime, - mergeRuntimeOwned, - subtractRuntime, - intersectRuntime -} = require("./util/runtime"); - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM, - WEBPACK_MODULE_TYPE_RUNTIME -} = require("./ModuleTypeConstants"); - -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ -/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Chunk").ChunkId} ChunkId */ -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./RuntimeModule")} RuntimeModule */ -/** @typedef {import("./javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("./javascript/JavascriptParserHelpers").Range} Range */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @typedef {object} HMRJavascriptParserHooks - * @property {SyncBailHook<[Expression | SpreadElement, string[]], void>} hotAcceptCallback - * @property {SyncBailHook<[CallExpression, string[]], void>} hotAcceptWithoutCallback - */ - -/** @typedef {{ updatedChunkIds: Set, removedChunkIds: Set, removedModules: Set, filename: string, assetInfo: AssetInfo }} HotUpdateMainContentByRuntimeItem */ -/** @typedef {Map} HotUpdateMainContentByRuntime */ - -/** @type {WeakMap} */ -const parserHooksMap = new WeakMap(); - -const PLUGIN_NAME = "HotModuleReplacementPlugin"; - -class HotModuleReplacementPlugin { - /** - * @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; - } - - /** - * @param {object=} options options - */ - constructor(options) { - this.options = options || {}; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _backCompat: backCompat } = compiler; - if (compiler.options.output.strictModuleErrorHandling === undefined) - compiler.options.output.strictModuleErrorHandling = true; - const runtimeRequirements = [RuntimeGlobals.module]; - - /** - * @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 {string[]} */ - 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.loc = Object.create( - /** @type {DependencyLocation} */ (expr.loc) - ); - dep.loc.index = 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; - }; - }; - - /** - * @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.loc = Object.create(/** @type {DependencyLocation} */ (expr.loc)); - dep.loc.index = idx; - module.addDependency(dep); - } - } - return true; - }; - - /** - * @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; - }; - - /** - * @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)); - }; - - /** - * @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 - ); - compilation.dependencyTemplates.set( - ModuleHotAcceptDependency, - new ModuleHotAcceptDependency.Template() - ); - compilation.dependencyFactories.set( - ModuleHotDeclineDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - ModuleHotDeclineDependency, - new ModuleHotDeclineDependency.Template() - ); - // #endregion - - // #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() - ); - // #endregion - - let hotIndex = 0; - /** @type {Record} */ - const fullHashChunkModuleHashes = {}; - /** @type {Record} */ - 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] = chunk.hash; - records.chunkRuntime[chunkId] = getRuntimeKey(chunk.runtime); - } - records.chunkModuleIds = {}; - for (const chunk of compilation.chunks) { - records.chunkModuleIds[/** @type {ChunkId} */ (chunk.id)] = - Array.from( - chunkGraph.getOrderedChunkModulesIterable( - chunk, - compareModulesById(chunkGraph) - ), - m => chunkGraph.getModuleId(m) - ); - } - }); - /** @type {TupleSet<[Module, Chunk]>} */ - const updatedModules = new TupleSet(); - /** @type {TupleSet<[Module, Chunk]>} */ - const fullHashModules = new TupleSet(); - /** @type {TupleSet<[Module, RuntimeSpec]>} */ - const nonCodeGeneratedModules = new TupleSet(); - compilation.hooks.fullHash.tap(PLUGIN_NAME, hash => { - const chunkGraph = compilation.chunkGraph; - const records = compilation.records; - for (const chunk of compilation.chunks) { - /** - * @param {Module} module module - * @returns {string} module hash - */ - const getModuleHash = module => { - if ( - compilation.codeGenerationResults.has(module, chunk.runtime) - ) { - return compilation.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 (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; - } - } - } - } - - 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 chunkGraph = compilation.chunkGraph; - const records = compilation.records; - if (records.hash === compilation.hash) return; - if ( - !records.chunkModuleHashes || - !records.chunkHashes || - !records.chunkModuleIds - ) { - return; - } - for (const [module, chunk] of fullHashModules) { - const key = `${chunk.id}|${module.identifier()}`; - const hash = nonCodeGeneratedModules.has(module, chunk.runtime) - ? chunkGraph.getModuleHash(module, chunk.runtime) - : compilation.codeGenerationResults.getHash( - module, - chunk.runtime - ); - if (records.chunkModuleHashes[key] !== hash) { - updatedModules.add(module, chunk); - } - chunkModuleHashes[key] = hash; - } - - /** @type {HotUpdateMainContentByRuntime} */ - const hotUpdateMainContentByRuntime = new Map(); - let allOldRuntime; - for (const key of Object.keys(records.chunkRuntime)) { - const runtime = keyToRuntime(records.chunkRuntime[key]); - allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime); - } - forEachRuntime(allOldRuntime, runtime => { - const { path: filename, info: assetInfo } = - compilation.getPathWithInfo( - /** @type {NonNullable} */ - (compilation.outputOptions.hotUpdateMainFilename), - { - hash: records.hash, - runtime - } - ); - hotUpdateMainContentByRuntime.set( - /** @type {string} */ (runtime), - { - updatedChunkIds: new Set(), - removedChunkIds: new Set(), - removedModules: 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 id = - /** @type {ModuleId} */ - (chunkGraph.getModuleId(module)); - allModules.set(id, module); - } - - // List of completely removed modules - /** @type {Set} */ - const completelyRemovedModules = new Set(); - - for (const key of Object.keys(records.chunkHashes)) { - const oldRuntime = keyToRuntime(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; - let newModules; - let newRuntimeModules; - let newFullHashModules; - let newDependentHashModules; - let newRuntime; - let removedFromRuntime; - const currentChunk = find( - compilation.chunks, - chunk => `${chunk.id}` === key - ); - if (currentChunk) { - chunkId = currentChunk.id; - newRuntime = intersectRuntime( - currentChunk.runtime, - allOldRuntime - ); - if (newRuntime === undefined) continue; - newModules = chunkGraph - .getChunkModules(currentChunk) - .filter(module => updatedModules.has(module, currentChunk)); - newRuntimeModules = Array.from( - chunkGraph.getChunkRuntimeModulesIterable(currentChunk) - ).filter(module => updatedModules.has(module, currentChunk)); - const fullHashModules = - chunkGraph.getChunkFullHashModulesIterable(currentChunk); - newFullHashModules = - fullHashModules && - Array.from(fullHashModules).filter(module => - updatedModules.has(module, currentChunk) - ); - const dependentHashModules = - chunkGraph.getChunkDependentHashModulesIterable(currentChunk); - newDependentHashModules = - dependentHashModules && - Array.from(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) - : compilation.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 - // We (incorrectly) assume that it's not in an overlapping runtime combination - // and dispose it from the main runtimes the chunk was removed from - forEachRuntime(removedFromRuntime, runtime => { - // If the module is still used in this runtime, do not dispose it - // This could create a bad runtime state where the module is still loaded, - // but no chunk which contains it. This means we don't receive further HMR updates - // to this module and that's bad. - // TODO force load one of the chunks which contains the module - for (const moduleRuntime of runtimes) { - if (typeof moduleRuntime === "string") { - if (moduleRuntime === runtime) return; - } else if ( - moduleRuntime !== undefined && - moduleRuntime.has(/** @type {string} */ (runtime)) - ) - return; - } - const item = - /** @type {HotUpdateMainContentByRuntimeItem} */ ( - hotUpdateMainContentByRuntime.get( - /** @type {string} */ (runtime) - ) - ); - 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); - } - chunkGraph.attachModules(hotUpdateChunk, newModules || []); - chunkGraph.attachRuntimeModules( - hotUpdateChunk, - newRuntimeModules || [] - ); - if (newFullHashModules) { - chunkGraph.attachFullHashModules( - hotUpdateChunk, - newFullHashModules - ); - } - if (newDependentHashModules) { - chunkGraph.attachDependentHashModules( - hotUpdateChunk, - newDependentHashModules - ); - } - const renderManifest = compilation.getRenderManifest({ - chunk: hotUpdateChunk, - hash: records.hash, - fullHash: records.hash, - outputOptions: compilation.outputOptions, - moduleTemplates: compilation.moduleTemplates, - dependencyTemplates: compilation.dependencyTemplates, - 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 completelyRemovedModulesArray = Array.from( - completelyRemovedModules - ); - const hotUpdateMainContentByFilename = new Map(); - for (const { - removedChunkIds, - removedModules, - updatedChunkIds, - filename, - assetInfo - } of hotUpdateMainContentByRuntime.values()) { - const old = hotUpdateMainContentByFilename.get(filename); - if ( - old && - (!isSubset(old.removedChunkIds, removedChunkIds) || - !isSubset(old.removedModules, removedModules) || - !isSubset(old.updatedChunkIds, updatedChunkIds)) - ) { - 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); - continue; - } - hotUpdateMainContentByFilename.set(filename, { - removedChunkIds, - removedModules, - updatedChunkIds, - assetInfo - }); - } - for (const [ - filename, - { removedChunkIds, removedModules, updatedChunkIds, assetInfo } - ] of hotUpdateMainContentByFilename) { - const hotUpdateMainJson = { - c: Array.from(updatedChunkIds), - r: Array.from(removedChunkIds), - m: - removedModules.size === 0 - ? completelyRemovedModulesArray - : completelyRemovedModulesArray.concat( - Array.from( - removedModules, - m => - /** @type {ModuleId} */ (chunkGraph.getModuleId(m)) - ) - ) - }; - - const source = new RawSource(JSON.stringify(hotUpdateMainJson)); - compilation.emitAsset(filename, source, { - hotModuleReplacement: true, - ...assetInfo - }); - } - } - ); - - 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() - ); - } - ); - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, parser => { - applyModuleHot(parser); - applyImportMetaHot(parser); - }); - normalModuleFactory.hooks.parser - .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 => { - module.hot = true; - return module; - }); - - NormalModule.getCompilationHooks(compilation).loader.tap( - PLUGIN_NAME, - context => { - context.hot = true; - } - ); - } - ); - } -} - -module.exports = HotModuleReplacementPlugin; diff --git a/webpack-lib/lib/HotUpdateChunk.js b/webpack-lib/lib/HotUpdateChunk.js deleted file mode 100644 index d939838527d..00000000000 --- a/webpack-lib/lib/HotUpdateChunk.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Chunk = require("./Chunk"); - -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./util/Hash")} Hash */ - -class HotUpdateChunk extends Chunk { - constructor() { - super(); - } -} - -module.exports = HotUpdateChunk; diff --git a/webpack-lib/lib/IgnoreErrorModuleFactory.js b/webpack-lib/lib/IgnoreErrorModuleFactory.js deleted file mode 100644 index 4fd73e7fa8b..00000000000 --- a/webpack-lib/lib/IgnoreErrorModuleFactory.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const ModuleFactory = require("./ModuleFactory"); - -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ - -/** - * Ignores error when module is unresolved - */ -class IgnoreErrorModuleFactory extends ModuleFactory { - /** - * @param {NormalModuleFactory} normalModuleFactory normalModuleFactory instance - */ - constructor(normalModuleFactory) { - super(); - - this.normalModuleFactory = normalModuleFactory; - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - this.normalModuleFactory.create(data, (err, result) => - callback(null, result) - ); - } -} - -module.exports = IgnoreErrorModuleFactory; diff --git a/webpack-lib/lib/IgnorePlugin.js b/webpack-lib/lib/IgnorePlugin.js deleted file mode 100644 index 8049ac129cb..00000000000 --- a/webpack-lib/lib/IgnorePlugin.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RawModule = require("./RawModule"); -const EntryDependency = require("./dependencies/EntryDependency"); -const createSchemaValidation = require("./util/create-schema-validation"); - -/** @typedef {import("../declarations/plugins/IgnorePlugin").IgnorePluginOptions} IgnorePluginOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */ - -const validate = createSchemaValidation( - require("../schemas/plugins/IgnorePlugin.check.js"), - () => require("../schemas/plugins/IgnorePlugin.json"), - { - name: "Ignore Plugin", - baseDataPath: "options" - } -); - -class IgnorePlugin { - /** - * @param {IgnorePluginOptions} options IgnorePlugin options - */ - constructor(options) { - validate(options); - this.options = options; - - /** - * @private - * @type {Function} - */ - this.checkIgnore = this.checkIgnore.bind(this); - } - - /** - * Note that if "contextRegExp" is given, both the "resourceRegExp" and "contextRegExp" have to match. - * @param {ResolveData} resolveData resolve data - * @returns {false|undefined} returns false when the request should be ignored, otherwise undefined - */ - checkIgnore(resolveData) { - if ( - "checkResource" in this.options && - this.options.checkResource && - this.options.checkResource(resolveData.request, resolveData.context) - ) { - return false; - } - - 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; - } - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.normalModuleFactory.tap("IgnorePlugin", nmf => { - nmf.hooks.beforeResolve.tap("IgnorePlugin", resolveData => { - const result = this.checkIgnore(resolveData); - - if ( - result === false && - resolveData.dependencies.length > 0 && - resolveData.dependencies[0] instanceof EntryDependency - ) { - resolveData.ignoredModule = new RawModule( - "", - "ignored-entry-module", - "(ignored-entry-module)" - ); - } - - return result; - }); - }); - compiler.hooks.contextModuleFactory.tap("IgnorePlugin", cmf => { - cmf.hooks.beforeResolve.tap("IgnorePlugin", this.checkIgnore); - }); - } -} - -module.exports = IgnorePlugin; diff --git a/webpack-lib/lib/IgnoreWarningsPlugin.js b/webpack-lib/lib/IgnoreWarningsPlugin.js deleted file mode 100644 index e844a8369e4..00000000000 --- a/webpack-lib/lib/IgnoreWarningsPlugin.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../declarations/WebpackOptions").IgnoreWarningsNormalized} IgnoreWarningsNormalized */ -/** @typedef {import("./Compiler")} Compiler */ - -class IgnoreWarningsPlugin { - /** - * @param {IgnoreWarningsNormalized} ignoreWarnings conditions to ignore warnings - */ - constructor(ignoreWarnings) { - this._ignoreWarnings = ignoreWarnings; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("IgnoreWarningsPlugin", compilation => { - compilation.hooks.processWarnings.tap("IgnoreWarningsPlugin", warnings => - warnings.filter( - warning => - !this._ignoreWarnings.some(ignore => ignore(warning, compilation)) - ) - ); - }); - } -} - -module.exports = IgnoreWarningsPlugin; diff --git a/webpack-lib/lib/InitFragment.js b/webpack-lib/lib/InitFragment.js deleted file mode 100644 index 7a5d9630771..00000000000 --- a/webpack-lib/lib/InitFragment.js +++ /dev/null @@ -1,185 +0,0 @@ -/* - 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 */ - -/** - * @template T - * @param {InitFragment} fragment the init fragment - * @param {number} index index - * @returns {[InitFragment, number]} tuple with both - */ -const extractFragmentIndex = (fragment, index) => [fragment, index]; - -/** - * @template T - * @param {[InitFragment, number]} a first pair - * @param {[InitFragment, 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; -}; - -/** - * @template GenerateContext - */ -class 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 {string=} 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; - } - - /** - * @param {GenerateContext} context context - * @returns {string | Source | undefined} the source code that will be included as initialization code - */ - getContent(context) { - return this.content; - } - - /** - * @param {GenerateContext} context context - * @returns {string|Source=} the source code that will be included at the end of the module - */ - getEndContent(context) { - return this.endContent; - } - - /** - * @template Context - * @template T - * @param {Source} source sources - * @param {InitFragment[]} 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. - const keyedFragments = new Map(); - for (const [fragment] of sortedFragments) { - if ( - typeof ( - /** @type {InitFragment & { mergeAll?: (fragments: InitFragment[]) => InitFragment[] }} */ - (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 oldValue = keyedFragments.get(fragment.key); - if (oldValue !== undefined) { - keyedFragments.set(fragment.key, fragment.merge(oldValue)); - continue; - } - } - keyedFragments.set(fragment.key || Symbol("fragment key"), fragment); - } - - const concatSource = new ConcatSource(); - const endContents = []; - for (let fragment of keyedFragments.values()) { - if (Array.isArray(fragment)) { - fragment = fragment[0].mergeAll(fragment); - } - concatSource.add(fragment.getContent(context)); - const endContent = fragment.getEndContent(context); - if (endContent) { - endContents.push(endContent); - } - } - - concatSource.add(source); - for (const content of endContents.reverse()) { - concatSource.add(content); - } - return concatSource; - } - return source; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.content); - write(this.stage); - write(this.position); - write(this.key); - write(this.endContent); - } - - /** - * @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.prototype.merge = - /** @type {TODO} */ - (undefined); - -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/webpack-lib/lib/InvalidDependenciesModuleWarning.js b/webpack-lib/lib/InvalidDependenciesModuleWarning.js deleted file mode 100644 index a69eed58d92..00000000000 --- a/webpack-lib/lib/InvalidDependenciesModuleWarning.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ - -class InvalidDependenciesModuleWarning extends WebpackError { - /** - * @param {Module} module module tied to dependency - * @param {Iterable} deps invalid dependencies - */ - constructor(module, deps) { - const orderedDeps = deps ? Array.from(deps).sort() : []; - const depsList = orderedDeps.map(dep => ` * ${JSON.stringify(dep)}`); - super(`Invalid dependencies have been reported by plugins or loaders for this module. All reported dependencies need to be absolute paths. -Invalid dependencies may lead to broken watching and caching. -As best effort we try to convert all invalid values to absolute paths and converting globs into context dependencies, but this is deprecated behavior. -Loaders: Pass absolute paths to this.addDependency (existing files), this.addMissingDependency (not existing files), and this.addContextDependency (directories). -Plugins: Pass absolute paths to fileDependencies (existing files), missingDependencies (not existing files), and contextDependencies (directories). -Globs: They are not supported. Pass absolute path to the directory as context dependencies. -The following invalid values have been reported: -${depsList.slice(0, 3).join("\n")}${ - depsList.length > 3 ? "\n * and more ..." : "" - }`); - - this.name = "InvalidDependenciesModuleWarning"; - this.details = depsList.slice(3).join("\n"); - this.module = module; - } -} - -makeSerializable( - InvalidDependenciesModuleWarning, - "webpack/lib/InvalidDependenciesModuleWarning" -); - -module.exports = InvalidDependenciesModuleWarning; diff --git a/webpack-lib/lib/JavascriptMetaInfoPlugin.js b/webpack-lib/lib/JavascriptMetaInfoPlugin.js deleted file mode 100644 index b8f77bea369..00000000000 --- a/webpack-lib/lib/JavascriptMetaInfoPlugin.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - 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 InnerGraph = require("./optimize/InnerGraph"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ - -const PLUGIN_NAME = "JavascriptMetaInfoPlugin"; - -class JavascriptMetaInfoPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - const handler = parser => { - parser.hooks.call.for("eval").tap(PLUGIN_NAME, () => { - const buildInfo = - /** @type {BuildInfo} */ - (parser.state.module.buildInfo); - buildInfo.moduleConcatenationBailout = "eval()"; - buildInfo.usingEval = true; - 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()) { - const freeInfo = parser.getFreeInfoFromVariable(name); - if (freeInfo === undefined) { - 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/webpack-lib/lib/LibManifestPlugin.js b/webpack-lib/lib/LibManifestPlugin.js deleted file mode 100644 index ab9d2fc57d8..00000000000 --- a/webpack-lib/lib/LibManifestPlugin.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const EntryDependency = require("./dependencies/EntryDependency"); -const { someInIterable } = require("./util/IterableHelpers"); -const { compareModulesById } = require("./util/comparators"); -const { dirname, mkdirp } = require("./util/fs"); - -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Compiler").IntermediateFileSystem} IntermediateFileSystem */ -/** @typedef {import("./Module").BuildMeta} BuildMeta */ - -/** - * @typedef {object} ManifestModuleData - * @property {string | number} id - * @property {BuildMeta} buildMeta - * @property {boolean | string[] | undefined} exports - */ - -/** - * @typedef {object} LibManifestPluginOptions - * @property {string=} context Context of requests in the manifest file (defaults to the webpack context). - * @property {boolean=} entryOnly If true, only entry points will be exposed (default: true). - * @property {boolean=} format If true, manifest json file (output) will be formatted. - * @property {string=} name Name of the exposed dll function (external name, use value of 'output.library'). - * @property {string} path Absolute path to the manifest json file (output). - * @property {string=} type Type of the dll bundle (external type, use value of 'output.libraryTarget'). - */ - -class LibManifestPlugin { - /** - * @param {LibManifestPluginOptions} options the options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.emit.tapAsync( - { - name: "LibManifestPlugin", - stage: 110 - }, - (compilation, callback) => { - const moduleGraph = compilation.moduleGraph; - // store used paths to detect issue and output an error. #18200 - const usedPaths = new Set(); - asyncLib.each( - Array.from(compilation.chunks), - (chunk, callback) => { - if (!chunk.canBeInitial()) { - callback(); - return; - } - const chunkGraph = compilation.chunkGraph; - const targetPath = compilation.getPath(this.options.path, { - chunk - }); - if (usedPaths.has(targetPath)) { - callback(new Error("each chunk must have a unique path")); - return; - } - usedPaths.add(targetPath); - const name = - this.options.name && - compilation.getPath(this.options.name, { - chunk, - contentHashType: "javascript" - }); - const content = Object.create(null); - for (const module of chunkGraph.getOrderedChunkModulesIterable( - chunk, - compareModulesById(chunkGraph) - )) { - if ( - this.options.entryOnly && - !someInIterable( - moduleGraph.getIncomingConnections(module), - c => c.dependency instanceof EntryDependency - ) - ) { - continue; - } - const ident = module.libIdent({ - context: - this.options.context || - /** @type {string} */ (compiler.options.context), - associatedObjectForCache: compiler.root - }); - if (ident) { - const exportsInfo = moduleGraph.getExportsInfo(module); - const providedExports = exportsInfo.getProvidedExports(); - /** @type {ManifestModuleData} */ - const data = { - id: /** @type {ModuleId} */ (chunkGraph.getModuleId(module)), - buildMeta: /** @type {BuildMeta} */ (module.buildMeta), - exports: Array.isArray(providedExports) - ? providedExports - : undefined - }; - content[ident] = data; - } - } - const manifest = { - name, - type: this.options.type, - content - }; - // Apply formatting to content if format flag is true; - const manifestContent = this.options.format - ? JSON.stringify(manifest, null, 2) - : JSON.stringify(manifest); - const buffer = Buffer.from(manifestContent, "utf8"); - const intermediateFileSystem = - /** @type {IntermediateFileSystem} */ ( - compiler.intermediateFileSystem - ); - mkdirp( - intermediateFileSystem, - dirname(intermediateFileSystem, targetPath), - err => { - if (err) return callback(err); - intermediateFileSystem.writeFile(targetPath, buffer, callback); - } - ); - }, - callback - ); - } - ); - } -} -module.exports = LibManifestPlugin; diff --git a/webpack-lib/lib/LibraryTemplatePlugin.js b/webpack-lib/lib/LibraryTemplatePlugin.js deleted file mode 100644 index 91cc4ab1440..00000000000 --- a/webpack-lib/lib/LibraryTemplatePlugin.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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 */ - -// TODO webpack 6 remove -class 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.library = { - type: target || "var", - name, - umdNamedDefine, - auxiliaryComment, - export: exportProperty - }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { output } = compiler.options; - output.library = this.library; - new EnableLibraryPlugin(this.library.type).apply(compiler); - } -} - -module.exports = LibraryTemplatePlugin; diff --git a/webpack-lib/lib/LoaderOptionsPlugin.js b/webpack-lib/lib/LoaderOptionsPlugin.js deleted file mode 100644 index 0cd6d7ad82b..00000000000 --- a/webpack-lib/lib/LoaderOptionsPlugin.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - 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 createSchemaValidation = require("./util/create-schema-validation"); - -/** @typedef {import("../declarations/plugins/LoaderOptionsPlugin").LoaderOptionsPluginOptions} LoaderOptionsPluginOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./ModuleFilenameHelpers").MatchObject} MatchObject */ - -const validate = createSchemaValidation( - require("../schemas/plugins/LoaderOptionsPlugin.check.js"), - () => require("../schemas/plugins/LoaderOptionsPlugin.json"), - { - name: "Loader Options Plugin", - baseDataPath: "options" - } -); - -class LoaderOptionsPlugin { - /** - * @param {LoaderOptionsPluginOptions & MatchObject} options options object - */ - constructor(options = {}) { - validate(options); - // If no options are set then generate empty options object - if (typeof options !== "object") options = {}; - if (!options.test) { - // This is mocking a RegExp object which always returns true - // TODO: Figure out how to do `as unknown as RegExp` for this line - // in JSDoc equivalent - /** @type {any} */ - const defaultTrueMockRegExp = { - test: () => true - }; - - /** @type {RegExp} */ - options.test = defaultTrueMockRegExp; - } - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap("LoaderOptionsPlugin", compilation => { - NormalModule.getCompilationHooks(compilation).loader.tap( - "LoaderOptionsPlugin", - (context, module) => { - const resource = module.resource; - if (!resource) return; - const i = resource.indexOf("?"); - if ( - ModuleFilenameHelpers.matchObject( - options, - i < 0 ? resource : resource.slice(0, i) - ) - ) { - for (const key of Object.keys(options)) { - if (key === "include" || key === "exclude" || key === "test") { - continue; - } - - /** @type {any} */ - (context)[key] = options[key]; - } - } - } - ); - }); - } -} - -module.exports = LoaderOptionsPlugin; diff --git a/webpack-lib/lib/LoaderTargetPlugin.js b/webpack-lib/lib/LoaderTargetPlugin.js deleted file mode 100644 index e7d3b38c18a..00000000000 --- a/webpack-lib/lib/LoaderTargetPlugin.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NormalModule = require("./NormalModule"); - -/** @typedef {import("./Compiler")} Compiler */ - -class LoaderTargetPlugin { - /** - * @param {string} target the target - */ - constructor(target) { - this.target = target; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("LoaderTargetPlugin", compilation => { - NormalModule.getCompilationHooks(compilation).loader.tap( - "LoaderTargetPlugin", - loaderContext => { - loaderContext.target = this.target; - } - ); - }); - } -} - -module.exports = LoaderTargetPlugin; diff --git a/webpack-lib/lib/MainTemplate.js b/webpack-lib/lib/MainTemplate.js deleted file mode 100644 index d05ebad2bf9..00000000000 --- a/webpack-lib/lib/MainTemplate.js +++ /dev/null @@ -1,382 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const util = require("util"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const memoize = require("./util/memoize"); - -/** @typedef {import("tapable").Tap} Tap */ -/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */ -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").Output} OutputOptions */ -/** @typedef {import("./ModuleTemplate")} ModuleTemplate */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("./Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */ -/** @typedef {import("./Module")} Module} */ -/** @typedef {import("./util/Hash")} Hash} */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates} */ -/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderContext} RenderContext} */ -/** @typedef {import("./javascript/JavascriptModulesPlugin").RenderBootstrapContext} RenderBootstrapContext} */ -/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkHashContext} ChunkHashContext} */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate} */ -/** @typedef {import("./ModuleGraph")} ModuleGraph} */ -/** @typedef {import("./ChunkGraph")} ChunkGraph} */ -/** @typedef {import("./Template").RenderManifestOptions} RenderManifestOptions} */ -/** @typedef {import("./Template").RenderManifestEntry} RenderManifestEntry} */ -/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath} */ -/** @typedef {import("./TemplatedPathPlugin").PathData} PathData} */ -/** - * @template T - * @typedef {import("tapable").IfSet} IfSet - */ - -const getJavascriptModulesPlugin = memoize(() => - require("./javascript/JavascriptModulesPlugin") -); -const getJsonpTemplatePlugin = memoize(() => - require("./web/JsonpTemplatePlugin") -); -const getLoadScriptRuntimeModule = memoize(() => - require("./runtime/LoadScriptRuntimeModule") -); - -// TODO webpack 6 remove this class -class MainTemplate { - /** - * @param {OutputOptions} outputOptions output options for the MainTemplate - * @param {Compilation} compilation the compilation - */ - constructor(outputOptions, compilation) { - /** @type {OutputOptions} */ - this._outputOptions = outputOptions || {}; - this.hooks = Object.freeze({ - renderManifest: { - tap: util.deprecate( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(RenderManifestEntry[], 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(string, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Chunk, string | undefined, ModuleTemplate, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Chunk, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(string, object, 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( - /** - * @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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Hash, 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", - "hash", - "moduleTemplate", - "dependencyTemplates" - ]), - /** @type {SyncWaterfallHook<[string, Chunk, string]>} */ - localVars: new SyncWaterfallHook(["source", "chunk", "hash"]), - /** @type {SyncWaterfallHook<[string, Chunk, string]>} */ - requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]), - /** @type {SyncWaterfallHook<[string, Chunk, string, string]>} */ - requireEnsure: new SyncWaterfallHook([ - "source", - "chunk", - "hash", - "chunkIdExpression" - ]), - 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; - } - }); - - this.renderCurrentHashCode = util.deprecate( - /** - * @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" - ); - - this.getPublicPath = util.deprecate( - /** - * @param {PathData} options context data - * @returns {string} interpolated path - */ options => - compilation.getAssetPath( - /** @type {string} */ - (compilation.outputOptions.publicPath), - options - ), - "MainTemplate.getPublicPath is deprecated (use Compilation.getAssetPath(compilation.outputOptions.publicPath, options) instead)", - "DEP_WEBPACK_MAIN_TEMPLATE_GET_PUBLIC_PATH" - ); - - this.getAssetPath = util.deprecate( - /** - * @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" - ); - - this.getAssetPathWithInfo = util.deprecate( - /** - * @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" - ); - } -} - -Object.defineProperty(MainTemplate.prototype, "requireFn", { - get: util.deprecate( - () => RuntimeGlobals.require, - `MainTemplate.requireFn is deprecated (use "${RuntimeGlobals.require}")`, - "DEP_WEBPACK_MAIN_TEMPLATE_REQUIRE_FN" - ) -}); - -Object.defineProperty(MainTemplate.prototype, "outputOptions", { - get: util.deprecate( - /** - * @this {MainTemplate} - * @returns {OutputOptions} output options - */ - function () { - return this._outputOptions; - }, - "MainTemplate.outputOptions is deprecated (use Compilation.outputOptions instead)", - "DEP_WEBPACK_MAIN_TEMPLATE_OUTPUT_OPTIONS" - ) -}); - -module.exports = MainTemplate; diff --git a/webpack-lib/lib/Module.js b/webpack-lib/lib/Module.js deleted file mode 100644 index b07066f38bc..00000000000 --- a/webpack-lib/lib/Module.js +++ /dev/null @@ -1,1198 +0,0 @@ -/* - 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 ModuleGraph = require("./ModuleGraph"); -const { JS_TYPES } = require("./ModuleSourceTypesConstants"); -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("../declarations/WebpackOptions").WebpackOptionsNormalized} 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("./ConcatenationScope")} ConcatenationScope */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./ExportsInfo").UsageStateType} UsageStateType */ -/** @typedef {import("./FileSystemInfo")} FileSystemInfo */ -/** @typedef {import("./FileSystemInfo").Snapshot} Snapshot */ -/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("./ModuleTypeConstants").ModuleTypes} ModuleTypes */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./util/Hash")} Hash */ -/** @template T @typedef {import("./util/LazySet")} LazySet */ -/** @template T @typedef {import("./util/SortableSet")} SortableSet */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @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 {ReadonlySet} SourceTypes */ - -// TODO webpack 6: compilation will be required in CodeGenerationContext -/** - * @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 {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 - */ - -/** - * @typedef {object} ConcatenationBailoutReasonContext - * @property {ModuleGraph} moduleGraph the module graph - * @property {ChunkGraph} chunkGraph the chunk graph - */ - -/** @typedef {Set} RuntimeRequirements */ -/** @typedef {ReadonlySet} ReadOnlyRuntimeRequirements */ - -/** - * @typedef {object} CodeGenerationResult - * @property {Map} sources the resulting sources for all source types - * @property {Map=} 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) - */ - -/** - * @typedef {object} LibIdentOptions - * @property {string} context absolute context path to which lib ident is relative to - * @property {object=} associatedObjectForCache object for caching - */ - -/** - * @typedef {object} KnownBuildMeta - * @property {string=} moduleArgument - * @property {string=} exportsArgument - * @property {boolean=} strict - * @property {string=} moduleConcatenationBailout - * @property {("default" | "namespace" | "flagged" | "dynamic")=} exportsType - * @property {(false | "redirect" | "redirect-warn")=} defaultObject - * @property {boolean=} strictHarmonyModule - * @property {boolean=} async - * @property {boolean=} sideEffectFree - * @property {Record=} exportsFinalName - */ - -/** - * @typedef {object} KnownBuildInfo - * @property {boolean=} cacheable - * @property {boolean=} parsed - * @property {LazySet=} fileDependencies - * @property {LazySet=} contextDependencies - * @property {LazySet=} missingDependencies - * @property {LazySet=} buildDependencies - * @property {ValueCacheVersions=} valueDependencies - * @property {TODO=} hash - * @property {Record=} assets - * @property {Map=} assetsInfo - * @property {(Snapshot | null)=} snapshot - */ - -/** @typedef {Map>} ValueCacheVersions */ - -/** - * @typedef {object} NeedBuildContext - * @property {Compilation} compilation - * @property {FileSystemInfo} fileSystemInfo - * @property {ValueCacheVersions} valueCacheVersions - */ - -/** @typedef {KnownBuildMeta & Record} BuildMeta */ -/** @typedef {KnownBuildInfo & Record} BuildInfo */ - -/** - * @typedef {object} FactoryMeta - * @property {boolean=} sideEffectFree - */ - -/** @typedef {{ factoryMeta: FactoryMeta | undefined, resolveOptions: ResolveOptions | undefined }} UnsafeCacheData */ - -const EMPTY_RESOLVE_OPTIONS = {}; - -let debugId = 1000; - -const DEFAULT_TYPES_UNKNOWN = new Set(["unknown"]); - -const deprecatedNeedRebuild = util.deprecate( - /** - * @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" -); - -/** @typedef {(requestShortener: RequestShortener) => string} OptimizationBailoutFunction */ - -class Module extends DependenciesBlock { - /** - * @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 {ModuleTypes} */ - this.type = type; - /** @type {string | null} */ - this.context = context; - /** @type {string | null} */ - this.layer = layer; - /** @type {boolean} */ - this.needId = true; - - // Unique Id - /** @type {number} */ - this.debugId = debugId++; - - // Info from Factory - /** @type {ResolveOptions | undefined} */ - this.resolveOptions = EMPTY_RESOLVE_OPTIONS; - /** @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; - - // Is in hot context, i.e. HotModuleReplacementPlugin.js enabled - /** @type {boolean} */ - this.hot = false; - // Info from Build - /** @type {WebpackError[] | undefined} */ - this._warnings = undefined; - /** @type {WebpackError[] | undefined} */ - this._errors = undefined; - /** @type {BuildMeta | undefined} */ - this.buildMeta = undefined; - /** @type {BuildInfo | undefined} */ - this.buildInfo = undefined; - /** @type {Dependency[] | undefined} */ - this.presentationalDependencies = undefined; - /** @type {Dependency[] | undefined} */ - this.codeGenerationDependencies = undefined; - } - - // TODO remove in webpack 6 - // BACKWARD-COMPAT START - /** - * @returns {ModuleId | null} module id - */ - get id() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.id", - "DEP_WEBPACK_MODULE_ID" - ).getModuleId(this); - } - - /** - * @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); - } - - /** - * @returns {string} the hash of the module - */ - get hash() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.hash", - "DEP_WEBPACK_MODULE_HASH" - ).getModuleHash(this, undefined); - } - - /** - * @returns {string} the shortened hash of the module - */ - get renderedHash() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.renderedHash", - "DEP_WEBPACK_MODULE_RENDERED_HASH" - ).getRenderedModuleHash(this, undefined); - } - - get profile() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.profile", - "DEP_WEBPACK_MODULE_PROFILE" - ).getProfile(this); - } - - set profile(value) { - ModuleGraph.getModuleGraphForModule( - this, - "Module.profile", - "DEP_WEBPACK_MODULE_PROFILE" - ).setProfile(this, value); - } - - /** - * @returns {number | null} the pre order index - */ - get index() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.index", - "DEP_WEBPACK_MODULE_INDEX" - ).getPreOrderIndex(this); - } - - /** - * @param {number} value the pre order index - */ - set index(value) { - ModuleGraph.getModuleGraphForModule( - this, - "Module.index", - "DEP_WEBPACK_MODULE_INDEX" - ).setPreOrderIndex(this, value); - } - - /** - * @returns {number | null} the post order index - */ - get index2() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.index2", - "DEP_WEBPACK_MODULE_INDEX2" - ).getPostOrderIndex(this); - } - - /** - * @param {number} value the post order index - */ - set index2(value) { - ModuleGraph.getModuleGraphForModule( - this, - "Module.index2", - "DEP_WEBPACK_MODULE_INDEX2" - ).setPostOrderIndex(this, value); - } - - /** - * @returns {number | null} the depth - */ - get depth() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.depth", - "DEP_WEBPACK_MODULE_DEPTH" - ).getDepth(this); - } - - /** - * @param {number} value the depth - */ - set depth(value) { - ModuleGraph.getModuleGraphForModule( - this, - "Module.depth", - "DEP_WEBPACK_MODULE_DEPTH" - ).setDepth(this, value); - } - - /** - * @returns {Module | null | undefined} issuer - */ - get issuer() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.issuer", - "DEP_WEBPACK_MODULE_ISSUER" - ).getIssuer(this); - } - - /** - * @param {Module | null} value issuer - */ - set issuer(value) { - ModuleGraph.getModuleGraphForModule( - this, - "Module.issuer", - "DEP_WEBPACK_MODULE_ISSUER" - ).setIssuer(this, value); - } - - get usedExports() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.usedExports", - "DEP_WEBPACK_MODULE_USED_EXPORTS" - ).getUsedExports(this, undefined); - } - - /** - * @deprecated - * @returns {(string | OptimizationBailoutFunction)[]} list - */ - get optimizationBailout() { - return ModuleGraph.getModuleGraphForModule( - this, - "Module.optimizationBailout", - "DEP_WEBPACK_MODULE_OPTIMIZATION_BAILOUT" - ).getOptimizationBailout(this); - } - - get optional() { - return this.isOptional( - ModuleGraph.getModuleGraphForModule( - this, - "Module.optional", - "DEP_WEBPACK_MODULE_OPTIONAL" - ) - ); - } - - /** - * @param {Chunk} chunk the chunk - * @returns {boolean} true, when the module was added - */ - addChunk(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; - } - - /** - * @param {Chunk} chunk the chunk - * @returns {void} - */ - removeChunk(chunk) { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.removeChunk", - "DEP_WEBPACK_MODULE_REMOVE_CHUNK" - ).disconnectChunkAndModule(chunk, this); - } - - /** - * @param {Chunk} chunk the chunk - * @returns {boolean} true, when the module is in the chunk - */ - isInChunk(chunk) { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.isInChunk", - "DEP_WEBPACK_MODULE_IS_IN_CHUNK" - ).isModuleInChunk(this, chunk); - } - - isEntryModule() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.isEntryModule", - "DEP_WEBPACK_MODULE_IS_ENTRY_MODULE" - ).isEntryModule(this); - } - - getChunks() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.getChunks", - "DEP_WEBPACK_MODULE_GET_CHUNKS" - ).getModuleChunks(this); - } - - getNumberOfChunks() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.getNumberOfChunks", - "DEP_WEBPACK_MODULE_GET_NUMBER_OF_CHUNKS" - ).getNumberOfModuleChunks(this); - } - - get chunksIterable() { - return ChunkGraph.getChunkGraphForModule( - this, - "Module.chunksIterable", - "DEP_WEBPACK_MODULE_CHUNKS_ITERABLE" - ).getOrderedModuleChunksIterable(this, compareChunksById); - } - - /** - * @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 - - /** - * @returns {string} name of the exports argument - */ - get exportsArgument() { - return (this.buildInfo && this.buildInfo.exportsArgument) || "exports"; - } - - /** - * @returns {string} name of the module argument - */ - get moduleArgument() { - return (this.buildInfo && this.buildInfo.moduleArgument) || "module"; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {boolean | undefined} strict the importing module is strict - * @returns {"namespace" | "default-only" | "default-with-named" | "dynamic"} 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"; - } - } - - /** - * @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 = []; - } - this.presentationalDependencies.push(presentationalDependency); - } - - /** - * @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 = []; - } - this.codeGenerationDependencies.push(codeGenerationDependency); - } - - /** - * Removes all dependencies and blocks - * @returns {void} - */ - clearDependenciesAndBlocks() { - if (this.presentationalDependencies !== undefined) { - this.presentationalDependencies.length = 0; - } - if (this.codeGenerationDependencies !== undefined) { - this.codeGenerationDependencies.length = 0; - } - super.clearDependenciesAndBlocks(); - } - - /** - * @param {WebpackError} warning the warning - * @returns {void} - */ - addWarning(warning) { - if (this._warnings === undefined) { - this._warnings = []; - } - this._warnings.push(warning); - } - - /** - * @returns {Iterable | undefined} list of warnings if any - */ - getWarnings() { - return this._warnings; - } - - /** - * @returns {number} number of warnings - */ - getNumberOfWarnings() { - return this._warnings !== undefined ? this._warnings.length : 0; - } - - /** - * @param {WebpackError} error the error - * @returns {void} - */ - addError(error) { - if (this._errors === undefined) { - this._errors = []; - } - this._errors.push(error); - } - - /** - * @returns {Iterable | undefined} list of errors if any - */ - getErrors() { - return this._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; - } - } - - /** - * @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 ( - !r.dependency || - !r.dependency.optional || - !r.isTargetActive(undefined) - ) { - return false; - } - hasConnections = true; - } - return hasConnections; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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 {string} for debugging - */ - toString() { - return `Module[${this.debugId}: ${this.identifier()}]`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} 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) - ); - } - - /** - * @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; - } - - /** - * @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); - } - - /** - * @returns {void} - */ - invalidateBuild() { - // should be overridden to support this feature - } - - /* istanbul ignore next */ - /** - * @abstract - * @returns {string} a unique identifier of the module - */ - identifier() { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @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 JS_TYPES; - } - - /** - * @abstract - * @deprecated Use codeGeneration() instead - * @param {DependencyTemplates} dependencyTemplates the dependency templates - * @param {RuntimeTemplate} runtimeTemplate the runtime template - * @param {string=} type the type of source that should be generated - * @returns {Source} generated source - */ - source(dependencyTemplates, runtimeTemplate, type = "javascript") { - if (this.codeGeneration === Module.prototype.codeGeneration) { - const AbstractMethodError = require("./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, - codeGenerationResults: undefined - }; - const sources = this.codeGeneration(codeGenContext).sources; - - return /** @type {Source} */ ( - type - ? sources.get(type) - : sources.get(/** @type {string} */ (first(this.getSourceTypes()))) - ); - } - - /* istanbul ignore next */ - /** - * @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("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return null; - } - - /** - * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) - */ - nameForCondition() { - return null; - } - - /** - * @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}`; - } - - /** - * @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; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - // Best override this method - const sources = new Map(); - for (const type of this.getSourceTypes()) { - if (type !== "unknown") { - sources.set( - type, - this.source( - context.dependencyTemplates, - context.runtimeTemplate, - type - ) - ); - } - } - return { - sources, - runtimeRequirements: new Set([ - RuntimeGlobals.module, - RuntimeGlobals.exports, - RuntimeGlobals.require - ]) - }; - } - - /** - * @param {Chunk} chunk the chunk which condition should be checked - * @param {Compilation} compilation the compilation - * @returns {boolean} true, if the chunk is ok for the module - */ - chunkCondition(chunk, compilation) { - return true; - } - - hasChunkCondition() { - return this.chunkCondition !== Module.prototype.chunkCondition; - } - - /** - * Assuming this module is in the cache. Update the (cached) module with - * the fresh module from the factory. Usually updates internal references - * and properties. - * @param {Module} module fresh module - * @returns {void} - */ - updateCacheModule(module) { - this.type = module.type; - this.layer = module.layer; - this.context = module.context; - this.factoryMeta = module.factoryMeta; - this.resolveOptions = module.resolveOptions; - } - - /** - * Module should be unsafe cached. Get data that's needed for that. - * This data will be passed to restoreFromUnsafeCache later. - * @returns {UnsafeCacheData} cached data - */ - getUnsafeCacheData() { - return { - factoryMeta: this.factoryMeta, - resolveOptions: this.resolveOptions - }; - } - - /** - * restore unsafe cache data - * @param {object} 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; - } - - /** - * @returns {Source | null} the original source for the module before webpack transformation - */ - originalSource() { - return null; - } - - /** - * @param {LazySet} fileDependencies set where file dependencies are added to - * @param {LazySet} contextDependencies set where context dependencies are added to - * @param {LazySet} missingDependencies set where missing dependencies are added to - * @param {LazySet} buildDependencies set where build dependencies are added to - */ - addCacheDependencies( - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - ) {} - - /** - * @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.hot); - 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); - } - - /** - * @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.hot = read(); - this._warnings = read(); - this._errors = read(); - this.buildMeta = read(); - this.buildInfo = read(); - this.presentationalDependencies = read(); - this.codeGenerationDependencies = read(); - super.deserialize(context); - } -} - -makeSerializable(Module, "webpack/lib/Module"); - -// TODO remove in webpack 6 -// eslint-disable-next-line no-warning-comments -// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 -Object.defineProperty(Module.prototype, "hasEqualsChunks", { - get() { - throw new Error( - "Module.hasEqualsChunks was renamed (use hasEqualChunks instead)" - ); - } -}); - -// TODO remove in webpack 6 -// eslint-disable-next-line no-warning-comments -// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 -Object.defineProperty(Module.prototype, "isUsed", { - get() { - throw new Error( - "Module.isUsed was renamed (use getUsedName, isExportUsed or isModuleUsed instead)" - ); - } -}); - -// TODO remove in webpack 6 -Object.defineProperty(Module.prototype, "errors", { - get: util.deprecate( - /** - * @this {Module} - * @returns {WebpackError[]} array - */ - function () { - if (this._errors === undefined) { - this._errors = []; - } - return this._errors; - }, - "Module.errors was removed (use getErrors instead)", - "DEP_WEBPACK_MODULE_ERRORS" - ) -}); - -// TODO remove in webpack 6 -Object.defineProperty(Module.prototype, "warnings", { - get: util.deprecate( - /** - * @this {Module} - * @returns {WebpackError[]} array - */ - function () { - if (this._warnings === undefined) { - this._warnings = []; - } - return this._warnings; - }, - "Module.warnings was removed (use getWarnings instead)", - "DEP_WEBPACK_MODULE_WARNINGS" - ) -}); - -// TODO remove in webpack 6 -// eslint-disable-next-line no-warning-comments -// @ts-ignore https://github.com/microsoft/TypeScript/issues/42919 -Object.defineProperty(Module.prototype, "used", { - get() { - throw new Error( - "Module.used was refactored (use ModuleGraph.getUsedExports instead)" - ); - }, - set(value) { - throw new Error( - "Module.used was refactored (use ModuleGraph.setUsedExports instead)" - ); - } -}); - -module.exports = Module; diff --git a/webpack-lib/lib/ModuleBuildError.js b/webpack-lib/lib/ModuleBuildError.js deleted file mode 100644 index b97daa14a18..00000000000 --- a/webpack-lib/lib/ModuleBuildError.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { cutOffLoaderExecution } = require("./ErrorHelpers"); -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ModuleBuildError extends WebpackError { - /** - * @param {string | Error&any} err error thrown - * @param {{from?: string|null}} info additional info - */ - constructor(err, { from = null } = {}) { - let message = "Module build failed"; - let details; - - message += from ? ` (from ${from}):\n` : ": "; - - 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; - - message += - typeof err.message === "string" && err.message ? err.message : err; - } - } else if (typeof err.message === "string" && err.message) { - message += err.message; - } else { - message += String(err); - } - } else { - message += String(err); - } - - super(message); - - this.name = "ModuleBuildError"; - this.details = details; - this.error = err; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.error); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.error = read(); - - super.deserialize(context); - } -} - -makeSerializable(ModuleBuildError, "webpack/lib/ModuleBuildError"); - -module.exports = ModuleBuildError; diff --git a/webpack-lib/lib/ModuleDependencyError.js b/webpack-lib/lib/ModuleDependencyError.js deleted file mode 100644 index bb7341db762..00000000000 --- a/webpack-lib/lib/ModuleDependencyError.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @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 {DependencyLocation} loc location of dependency - */ - constructor(module, err, loc) { - super(err.message); - - this.name = "ModuleDependencyError"; - this.details = - err && !(/** @type {any} */ (err).hideStack) - ? /** @type {string} */ (err.stack).split("\n").slice(1).join("\n") - : undefined; - this.module = module; - this.loc = loc; - /** error is not (de)serialized, so it might be undefined after deserialization */ - this.error = err; - - if (err && /** @type {any} */ (err).hideStack && err.stack) { - this.stack = /** @type {string} */ `${err.stack - .split("\n") - .slice(1) - .join("\n")}\n\n${this.stack}`; - } - } -} - -module.exports = ModuleDependencyError; diff --git a/webpack-lib/lib/ModuleDependencyWarning.js b/webpack-lib/lib/ModuleDependencyWarning.js deleted file mode 100644 index 2fc403b9d66..00000000000 --- a/webpack-lib/lib/ModuleDependencyWarning.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ - -class ModuleDependencyWarning extends WebpackError { - /** - * @param {Module} module module tied to dependency - * @param {Error} err error thrown - * @param {DependencyLocation} loc location of dependency - */ - constructor(module, err, loc) { - super(err ? err.message : ""); - - this.name = "ModuleDependencyWarning"; - this.details = - err && !(/** @type {any} */ (err).hideStack) - ? /** @type {string} */ (err.stack).split("\n").slice(1).join("\n") - : undefined; - this.module = module; - this.loc = loc; - /** error is not (de)serialized, so it might be undefined after deserialization */ - this.error = err; - - if (err && /** @type {any} */ (err).hideStack && err.stack) { - this.stack = /** @type {string} */ `${err.stack - .split("\n") - .slice(1) - .join("\n")}\n\n${this.stack}`; - } - } -} - -makeSerializable( - ModuleDependencyWarning, - "webpack/lib/ModuleDependencyWarning" -); - -module.exports = ModuleDependencyWarning; diff --git a/webpack-lib/lib/ModuleError.js b/webpack-lib/lib/ModuleError.js deleted file mode 100644 index f8227a8fc48..00000000000 --- a/webpack-lib/lib/ModuleError.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { cleanUp } = require("./ErrorHelpers"); -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ModuleError extends WebpackError { - /** - * @param {Error} err error thrown - * @param {{from?: string|null}} info additional info - */ - constructor(err, { from = null } = {}) { - let message = "Module Error"; - - message += from ? ` (from ${from}):\n` : ": "; - - if (err && typeof err === "object" && err.message) { - message += err.message; - } else if (err) { - message += err; - } - - super(message); - - this.name = "ModuleError"; - this.error = err; - this.details = - err && typeof err === "object" && err.stack - ? cleanUp(err.stack, this.message) - : undefined; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.error); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.error = read(); - - super.deserialize(context); - } -} - -makeSerializable(ModuleError, "webpack/lib/ModuleError"); - -module.exports = ModuleError; diff --git a/webpack-lib/lib/ModuleFactory.js b/webpack-lib/lib/ModuleFactory.js deleted file mode 100644 index 7b08be28be5..00000000000 --- a/webpack-lib/lib/ModuleFactory.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - 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 */ - -/** - * @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 {object} ModuleFactoryCreateDataContextInfo - * @property {string} issuer - * @property {string | null=} issuerLayer - * @property {string} compiler - */ - -/** - * @typedef {object} ModuleFactoryCreateData - * @property {ModuleFactoryCreateDataContextInfo} contextInfo - * @property {ResolveOptions=} resolveOptions - * @property {string} context - * @property {Dependency[]} dependencies - */ - -class ModuleFactory { - /* istanbul ignore next */ - /** - * @abstract - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } -} - -module.exports = ModuleFactory; diff --git a/webpack-lib/lib/ModuleFilenameHelpers.js b/webpack-lib/lib/ModuleFilenameHelpers.js deleted file mode 100644 index afe3d345338..00000000000 --- a/webpack-lib/lib/ModuleFilenameHelpers.js +++ /dev/null @@ -1,383 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NormalModule = require("./NormalModule"); -const createHash = require("./util/createHash"); -const memoize = require("./util/memoize"); - -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {typeof import("./util/Hash")} Hash */ - -/** @typedef {string | RegExp | (string | RegExp)[]} Matcher */ -/** @typedef {{test?: Matcher, include?: Matcher, exclude?: Matcher }} MatchObject */ - -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.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]"; -// 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]"; -ModuleFilenameHelpers.REGEXP_ALL_LOADERS = /\[all-?loaders\]/gi; -ModuleFilenameHelpers.LOADERS = "[loaders]"; -ModuleFilenameHelpers.REGEXP_LOADERS = /\[loaders\]/gi; -ModuleFilenameHelpers.QUERY = "[query]"; -ModuleFilenameHelpers.REGEXP_QUERY = /\[query\]/gi; -ModuleFilenameHelpers.ID = "[id]"; -ModuleFilenameHelpers.REGEXP_ID = /\[id\]/gi; -ModuleFilenameHelpers.HASH = "[hash]"; -ModuleFilenameHelpers.REGEXP_HASH = /\[hash\]/gi; -ModuleFilenameHelpers.NAMESPACE = "[namespace]"; -ModuleFilenameHelpers.REGEXP_NAMESPACE = /\[namespace\]/gi; - -/** @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.slice(idx); -}; - -/** - * 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.slice(0, idx); -}; - -/** - * Returns a function that returns a hash of the string - * @param {ReturnStringCallback} strFn the function to get the string - * @param {string | Hash=} hashFunction the hash function to use - * @returns {ReturnStringCallback} a function that returns the hash of the string - */ -const getHash = - (strFn, hashFunction = "md4") => - () => { - const hash = createHash(hashFunction); - hash.update(strFn()); - const digest = /** @type {string} */ (hash.digest("hex")); - return digest.slice(0, 4); - }; - -/** - * Returns a function that returns the string with the token replaced with the replacement - * @param {string|RegExp} test A regular expression string or Regular Expression object - * @returns {RegExp} A regular expression object - * @example - * ```js - * const test = asRegExp("test"); - * test.test("test"); // true - * - * const test2 = asRegExp(/test/); - * test2.test("test"); // true - * ``` - */ -const asRegExp = test => { - if (typeof test === "string") { - // Escape special characters in the string to prevent them from being interpreted as special characters in a regular expression. Do this by - // adding a backslash before each special character - test = new RegExp(`^${test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")}`); - } - return test; -}; - -/** - * @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 {object} the lazy access object - */ -const lazyObject = obj => { - const newObj = {}; - 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 newObj; -}; - -const SQUARE_BRACKET_TAG_REGEXP = /\[\\*([\w-]+)\\*\]/gi; - -/** - * @param {Module | string} module the module - * @param {TODO} options options - * @param {object} contextInfo context info - * @param {RequestShortener} contextInfo.requestShortener requestShortener - * @param {ChunkGraph} contextInfo.chunkGraph chunk graph - * @param {string | Hash=} contextInfo.hashFunction the hash function to use - * @returns {string} the filename - */ -ModuleFilenameHelpers.createFilename = ( - // eslint-disable-next-line default-param-last - module = "", - options, - { requestShortener, chunkGraph, hashFunction = "md4" } -) => { - const opts = { - namespace: "", - moduleFilenameTemplate: "", - ...(typeof options === "object" - ? options - : { - moduleFilenameTemplate: options - }) - }; - - let absoluteResourcePath; - let hash; - /** @type {ReturnStringCallback} */ - let identifier; - /** @type {ReturnStringCallback} */ - let moduleId; - /** @type {ReturnStringCallback} */ - let shortIdentifier; - if (typeof module === "string") { - shortIdentifier = - /** @type {ReturnStringCallback} */ - (memoize(() => requestShortener.shorten(module))); - identifier = shortIdentifier; - moduleId = () => ""; - absoluteResourcePath = () => module.split("!").pop(); - hash = getHash(identifier, hashFunction); - } else { - 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 - : module.identifier().split("!").pop(); - hash = getHash(identifier, hashFunction); - } - const resource = - /** @type {ReturnStringCallback} */ - (memoize(() => shortIdentifier().split("!").pop())); - - const loaders = getBefore(shortIdentifier, "!"); - const allLoaders = getBefore(identifier, "!"); - const query = getAfter(resource, "?"); - const resourcePath = () => { - const q = query().length; - return q === 0 ? resource() : resource().slice(0, -q); - }; - if (typeof opts.moduleFilenameTemplate === "function") { - return opts.moduleFilenameTemplate( - 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 - }) - ); - } - - // TODO webpack 6: consider removing alternatives without dashes - /** @type {Map} */ - 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_LOADERS_RESOURCE, - "[short-identifier]" - ) - .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); - - for (const [idx, item] of array.entries()) { - countMap[item] = countMap[item] || []; - countMap[item].push(idx); - posMap[item] = 0; - } - if (comparator) { - 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]++); - } - return item; - }); -}; - -/** - * 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 - * ``` - */ -ModuleFilenameHelpers.matchPart = (str, test) => { - if (!test) return true; - - if (Array.isArray(test)) { - return test.map(asRegExp).some(regExp => regExp.test(str)); - } - return asRegExp(test).test(str); -}; - -/** - * 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 && !ModuleFilenameHelpers.matchPart(str, obj.test)) { - return false; - } - if (obj.include && !ModuleFilenameHelpers.matchPart(str, obj.include)) { - return false; - } - if (obj.exclude && ModuleFilenameHelpers.matchPart(str, obj.exclude)) { - return false; - } - return true; -}; diff --git a/webpack-lib/lib/ModuleGraph.js b/webpack-lib/lib/ModuleGraph.js deleted file mode 100644 index 783c6e414d6..00000000000 --- a/webpack-lib/lib/ModuleGraph.js +++ /dev/null @@ -1,896 +0,0 @@ -/* - 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 SortableSet = require("./util/SortableSet"); -const WeakTupleMap = require("./util/WeakTupleMap"); - -/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./ExportsInfo").ExportInfo} ExportInfo */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleProfile")} ModuleProfile */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @callback OptimizationBailoutFunction - * @param {RequestShortener} requestShortener - * @returns {string} - */ - -const EMPTY_SET = new Set(); - -/** - * @param {SortableSet} set input - * @returns {readonly Map} mapped by origin module - */ -const getConnectionsByOriginModule = set => { - const map = new Map(); - /** @type {Module | 0} */ - let lastModule = 0; - /** @type {ModuleGraphConnection[] | undefined} */ - let lastList; - for (const connection of set) { - const { originModule } = connection; - if (lastModule === originModule) { - /** @type {ModuleGraphConnection[]} */ - (lastList).push(connection); - } else { - lastModule = /** @type {Module} */ (originModule); - const list = map.get(originModule); - if (list !== undefined) { - lastList = list; - list.push(connection); - } else { - const list = [connection]; - lastList = list; - map.set(originModule, list); - } - } - } - return map; -}; - -/** - * @param {SortableSet} set input - * @returns {readonly Map} mapped by module - */ -const getConnectionsByModule = set => { - const map = new Map(); - /** @type {Module | 0} */ - let lastModule = 0; - /** @type {ModuleGraphConnection[] | undefined} */ - let lastList; - for (const connection of set) { - const { module } = connection; - if (lastModule === module) { - /** @type {ModuleGraphConnection[]} */ - (lastList).push(connection); - } else { - lastModule = module; - const list = map.get(module); - if (list !== undefined) { - lastList = list; - list.push(connection); - } else { - const list = [connection]; - lastList = list; - map.set(module, list); - } - } - } - return map; -}; - -/** @typedef {SortableSet} IncomingConnections */ -/** @typedef {SortableSet} OutgoingConnections */ - -class ModuleGraphModule { - constructor() { - /** @type {IncomingConnections} */ - this.incomingConnections = new SortableSet(); - /** @type {OutgoingConnections | undefined} */ - this.outgoingConnections = undefined; - /** @type {Module | null | undefined} */ - this.issuer = undefined; - /** @type {(string | OptimizationBailoutFunction)[]} */ - 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; - } -} - -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 {Map> | undefined} - * @private - */ - this._moduleMemCaches = undefined; - - /** - * @type {string | undefined} - * @private - */ - this._cacheStage = undefined; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Dependency} dependency the dependency - * @returns {Module | undefined} parent module - */ - getParentModule(dependency) { - return dependency._parentModule; - } - - /** - * @param {Dependency} dependency the dependency - * @returns {DependenciesBlock | undefined} parent block - */ - getParentBlock(dependency) { - return dependency._parentDependenciesBlock; - } - - /** - * @param {Dependency} dependency the dependency - * @returns {number} index - */ - getParentBlockIndex(dependency) { - return dependency._parentDependenciesBlockIndex; - } - - /** - * @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, - 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); - } - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @returns {void} - */ - removeAllModuleAttributes() { - for (const mgm of this._moduleMap.values()) { - mgm.postOrderIndex = null; - mgm.preOrderIndex = null; - mgm.depth = null; - mgm.async = false; - } - } - - /** - * @param {Module} oldModule the old referencing module - * @param {Module} newModule the new referencing module - * @param {function(ModuleGraphConnection): boolean} 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); - } - } - } - - /** - * @param {Module} oldModule the old referencing module - * @param {Module} newModule the new referencing module - * @param {function(ModuleGraphConnection): boolean} 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); - } - } - } - } - } - - /** - * @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)); - } - - /** - * @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; - } - - /** - * @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 - ) { - 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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {Iterable} reasons why a module is included - */ - getIncomingConnections(module) { - const connections = this._getModuleGraphModule(module).incomingConnections; - return 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; - } - - /** - * @param {Module} module the module - * @returns {readonly Map} reasons why a module is included, in a map by source module - */ - getIncomingConnectionsByOriginModule(module) { - const connections = this._getModuleGraphModule(module).incomingConnections; - return connections.getFromUnorderedCache(getConnectionsByOriginModule); - } - - /** - * @param {Module} module the module - * @returns {readonly Map | undefined} connections to modules, in a map by module - */ - getOutgoingConnectionsByModule(module) { - const connections = this._getModuleGraphModule(module).outgoingConnections; - return connections === undefined - ? undefined - : connections.getFromUnorderedCache(getConnectionsByModule); - } - - /** - * @param {Module} module the module - * @returns {ModuleProfile | undefined} the module profile - */ - getProfile(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.profile; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {Module | null | undefined} the issuer module - */ - getIssuer(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.issuer; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {(string | OptimizationBailoutFunction)[]} optimization bailouts - */ - getOptimizationBailout(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.optimizationBailout; - } - - /** - * @param {Module} module the module - * @returns {true | string[] | null} the provided exports - */ - getProvidedExports(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.exports.getProvidedExports(); - } - - /** - * @param {Module} module the module - * @param {string | string[]} 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; - } - - /** - * @param {Module} module the module - * @returns {ExportsInfo} info about the exports - */ - getExportsInfo(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.exports; - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @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); - } - - /** - * @param {Module} module the module - * @returns {number | null} the index of the module - */ - getPreOrderIndex(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.preOrderIndex; - } - - /** - * @param {Module} module the module - * @returns {number | null} the index of the module - */ - getPostOrderIndex(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.postOrderIndex; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {number | null} the depth of the module - */ - getDepth(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.depth; - } - - /** - * @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; - } - - /** - * @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; - } - - /** - * @param {Module} module the module - * @returns {boolean} true, if the module is async - */ - isAsync(module) { - const mgm = this._getModuleGraphModule(module); - return mgm.async; - } - - /** - * @param {Module} module the module - * @returns {void} - */ - setAsync(module) { - const mgm = this._getModuleGraphModule(module); - mgm.async = true; - } - - /** - * @param {any} thing any thing - * @returns {object} metadata - */ - getMeta(thing) { - let meta = this._metaMap.get(thing); - if (meta === undefined) { - meta = Object.create(null); - this._metaMap.set(thing, /** @type {object} */ (meta)); - } - return /** @type {object} */ (meta); - } - - /** - * @param {any} thing any thing - * @returns {object | undefined} metadata - */ - getMetaIfExisting(thing) { - return this._metaMap.get(thing); - } - - /** - * @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; - } - - /** - * @template {any[]} T - * @template V - * @param {(moduleGraph: ModuleGraph, ...args: T) => V} fn computer - * @param {T} args arguments - * @returns {V} computed value or cached - */ - cached(fn, ...args) { - if (this._cache === undefined) return fn(this, ...args); - return this._cache.provide(fn, ...args, () => fn(this, ...args)); - } - - /** - * @param {Map>} moduleMemCaches mem caches for modules for better caching - */ - setModuleMemCaches(moduleMemCaches) { - this._moduleMemCaches = moduleMemCaches; - } - - /** - * @param {Dependency} dependency dependency - * @param {...any} args arguments, last argument is a function called with moduleGraph, dependency, ...args - * @returns {any} computed value or cached - */ - dependencyCacheProvide(dependency, ...args) { - /** @type {(moduleGraph: ModuleGraph, dependency: Dependency, ...args: any[]) => any} */ - const fn = 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 - /** - * @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( - /** - * @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 - /** - * @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 - /** - * @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/webpack-lib/lib/ModuleGraphConnection.js b/webpack-lib/lib/ModuleGraphConnection.js deleted file mode 100644 index 1f12ac9e5cc..00000000000 --- a/webpack-lib/lib/ModuleGraphConnection.js +++ /dev/null @@ -1,205 +0,0 @@ -/* - 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 */ - -/** - * @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; -}; - -/** - * @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 { - /** - * @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 | undefined} condition condition for the connection - */ - constructor( - originModule, - dependency, - module, - explanation, - weak = false, - condition = undefined - ) { - this.originModule = originModule; - this.resolvedOriginModule = originModule; - this.dependency = dependency; - this.resolvedModule = module; - this.module = module; - this.weak = weak; - this.conditional = Boolean(condition); - this._active = condition !== false; - /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState) | 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; - } - - /** - * @param {function(ModuleGraphConnection, RuntimeSpec): ConnectionState} condition condition for the connection - * @returns {void} - */ - addCondition(condition) { - if (this.conditional) { - const old = - /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ - (this.condition); - this.condition = (c, r) => - intersectConnectionStates(old(c, r), condition(c, r)); - } else if (this._active) { - this.conditional = true; - this.condition = condition; - } - } - - /** - * @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 Array.from(this.explanations).join(" "); - } - - /** - * @param {RuntimeSpec} runtime the runtime - * @returns {boolean} true, if the connection is active - */ - isActive(runtime) { - if (!this.conditional) return this._active; - - return ( - /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( - this.condition - )(this, runtime) !== false - ); - } - - /** - * @param {RuntimeSpec} runtime the runtime - * @returns {boolean} true, if the connection is active - */ - isTargetActive(runtime) { - if (!this.conditional) return this._active; - return ( - /** @type {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( - this.condition - )(this, runtime) === true - ); - } - - /** - * @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 {(function(ModuleGraphConnection, RuntimeSpec): ConnectionState)} */ ( - this.condition - )(this, runtime); - } - - /** - * @param {boolean} value active or not - * @returns {void} - */ - setActive(value) { - this.conditional = false; - this._active = value; - } - - // TODO webpack 5 remove - get active() { - throw new Error("Use getActiveState instead"); - } - - set active(value) { - throw new Error("Use setActive instead"); - } -} - -/** @typedef {typeof TRANSITIVE_ONLY} TRANSITIVE_ONLY */ -/** @typedef {typeof CIRCULAR_CONNECTION} CIRCULAR_CONNECTION */ - -module.exports = ModuleGraphConnection; -module.exports.addConnectionStates = addConnectionStates; -module.exports.TRANSITIVE_ONLY = /** @type {typeof TRANSITIVE_ONLY} */ ( - TRANSITIVE_ONLY -); -module.exports.CIRCULAR_CONNECTION = /** @type {typeof CIRCULAR_CONNECTION} */ ( - CIRCULAR_CONNECTION -); diff --git a/webpack-lib/lib/ModuleHashingError.js b/webpack-lib/lib/ModuleHashingError.js deleted file mode 100644 index 77c8f415aff..00000000000 --- a/webpack-lib/lib/ModuleHashingError.js +++ /dev/null @@ -1,29 +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 ModuleHashingError extends WebpackError { - /** - * Create a new ModuleHashingError - * @param {Module} module related module - * @param {Error} error Original error - */ - constructor(module, error) { - super(); - - this.name = "ModuleHashingError"; - this.error = error; - this.message = error.message; - this.details = error.stack; - this.module = module; - } -} - -module.exports = ModuleHashingError; diff --git a/webpack-lib/lib/ModuleInfoHeaderPlugin.js b/webpack-lib/lib/ModuleInfoHeaderPlugin.js deleted file mode 100644 index 994bfed88cb..00000000000 --- a/webpack-lib/lib/ModuleInfoHeaderPlugin.js +++ /dev/null @@ -1,314 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource, RawSource, CachedSource } = 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("./ModuleTemplate")} ModuleTemplate */ -/** @typedef {import("./RequestShortener")} RequestShortener */ - -/** - * @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; -}; - -/** - * @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 - 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` - ); - } - } -}; - -/** @type {WeakMap }>>} */ -const caches = new WeakMap(); - -class ModuleInfoHeaderPlugin { - /** - * @param {boolean=} verbose add more information like exports, runtime requirements and bailouts - */ - constructor(verbose = true) { - this._verbose = verbose; - } - - /** - * @param {Compiler} compiler the compiler - * @returns {void} - */ - apply(compiler) { - const { _verbose: verbose } = this; - compiler.hooks.compilation.tap("ModuleInfoHeaderPlugin", compilation => { - const javascriptHooks = - JavascriptModulesPlugin.getCompilationHooks(compilation); - javascriptHooks.renderModulePackage.tap( - "ModuleInfoHeaderPlugin", - ( - moduleSource, - module, - { chunk, chunkGraph, moduleGraph, runtimeTemplate } - ) => { - const { requestShortener } = runtimeTemplate; - 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( - "ModuleInfoHeaderPlugin", - (_chunk, hash) => { - hash.update("ModuleInfoHeaderPlugin"); - hash.update("1"); - } - ); - const cssHooks = CssModulesPlugin.getCompilationHooks(compilation); - cssHooks.renderModulePackage.tap( - "ModuleInfoHeaderPlugin", - (moduleSource, module, { runtimeTemplate }) => { - const { requestShortener } = runtimeTemplate; - 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("ModuleInfoHeaderPlugin", (_chunk, hash) => { - hash.update("ModuleInfoHeaderPlugin"); - hash.update("1"); - }); - }); - } - - /** - * @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/webpack-lib/lib/ModuleNotFoundError.js b/webpack-lib/lib/ModuleNotFoundError.js deleted file mode 100644 index 6fdf241dee8..00000000000 --- a/webpack-lib/lib/ModuleNotFoundError.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ - -const previouslyPolyfilledBuiltinModules = { - assert: "assert/", - buffer: "buffer/", - console: "console-browserify", - constants: "constants-browserify", - crypto: "crypto-browserify", - domain: "domain-browser", - events: "events/", - http: "stream-http", - https: "https-browserify", - os: "os-browserify/browser", - path: "path-browserify", - punycode: "punycode/", - process: "process/browser", - querystring: "querystring-es3", - stream: "stream-browserify", - _stream_duplex: "readable-stream/duplex", - _stream_passthrough: "readable-stream/passthrough", - _stream_readable: "readable-stream/readable", - _stream_transform: "readable-stream/transform", - _stream_writable: "readable-stream/writable", - string_decoder: "string_decoder/", - sys: "util/", - timers: "timers-browserify", - tty: "tty-browserify", - url: "url/", - util: "util/", - vm: "vm-browserify", - zlib: "browserify-zlib" -}; - -class ModuleNotFoundError extends WebpackError { - /** - * @param {Module | null} module module tied to dependency - * @param {Error&any} err error thrown - * @param {DependencyLocation} loc location of dependency - */ - constructor(module, err, loc) { - let message = `Module not found: ${err.toString()}`; - - // TODO remove in webpack 6 - const match = err.message.match(/Can't resolve '([^']+)'/); - if (match) { - const request = match[1]; - const alias = - previouslyPolyfilledBuiltinModules[ - /** @type {keyof previouslyPolyfilledBuiltinModules} */ (request) - ]; - if (alias) { - const pathIndex = alias.indexOf("/"); - const dependency = pathIndex > 0 ? alias.slice(0, pathIndex) : alias; - message += - "\n\n" + - "BREAKING CHANGE: " + - "webpack < 5 used to include polyfills for node.js core modules by default.\n" + - "This is no longer the case. Verify if you need this module and configure a polyfill for it.\n\n"; - message += - "If you want to include a polyfill, you need to:\n" + - `\t- add a fallback 'resolve.fallback: { "${request}": require.resolve("${alias}") }'\n` + - `\t- install '${dependency}'\n`; - message += - "If you don't want to include a polyfill, you can use an empty module like this:\n" + - `\tresolve.fallback: { "${request}": false }`; - } - } - - super(message); - - this.name = "ModuleNotFoundError"; - this.details = err.details; - this.module = module; - this.error = err; - this.loc = loc; - } -} - -module.exports = ModuleNotFoundError; diff --git a/webpack-lib/lib/ModuleParseError.js b/webpack-lib/lib/ModuleParseError.js deleted file mode 100644 index d14c763aec8..00000000000 --- a/webpack-lib/lib/ModuleParseError.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -const WASM_HEADER = Buffer.from([0x00, 0x61, 0x73, 0x6d]); - -class ModuleParseError extends WebpackError { - /** - * @param {string | Buffer} source source code - * @param {Error & any} err the parse error - * @param {string[]} loaders the loaders used - * @param {string} type module type - */ - constructor(source, err, loaders, type) { - let message = `Module parse failed: ${err && err.message}`; - let loc; - - if ( - ((Buffer.isBuffer(source) && source.slice(0, 4).equals(WASM_HEADER)) || - (typeof source === "string" && /^\0asm/.test(source))) && - !type.startsWith("webassembly") - ) { - message += - "\nThe module seem to be a WebAssembly module, but module is not flagged as WebAssembly module for webpack."; - message += - "\nBREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental feature."; - message += - "\nYou need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated)."; - message += - "\nFor files that transpile to WebAssembly, make sure to set the module type in the 'module.rules' section of the config (e. g. 'type: \"webassembly/async\"')."; - } else if (!loaders) { - message += - "\nYou may need an appropriate loader to handle this file type."; - } else if (loaders.length >= 1) { - message += `\nFile was processed with these loaders:${loaders - .map(loader => `\n * ${loader}`) - .join("")}`; - message += - "\nYou may need an additional loader to handle the result of these loaders."; - } else { - message += - "\nYou may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders"; - } - - if ( - err && - err.loc && - typeof err.loc === "object" && - typeof err.loc.line === "number" - ) { - const lineNumber = err.loc.line; - - if ( - Buffer.isBuffer(source) || - /[\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(/\r?\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 = { start: err.loc }; - } else if (err && err.stack) { - message += `\n${err.stack}`; - } - - super(message); - - this.name = "ModuleParseError"; - this.loc = loc; - this.error = err; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.error); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.error = read(); - - super.deserialize(context); - } -} - -makeSerializable(ModuleParseError, "webpack/lib/ModuleParseError"); - -module.exports = ModuleParseError; diff --git a/webpack-lib/lib/ModuleProfile.js b/webpack-lib/lib/ModuleProfile.js deleted file mode 100644 index 360991ab005..00000000000 --- a/webpack-lib/lib/ModuleProfile.js +++ /dev/null @@ -1,108 +0,0 @@ -/* - 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/webpack-lib/lib/ModuleRestoreError.js b/webpack-lib/lib/ModuleRestoreError.js deleted file mode 100644 index 2570862d421..00000000000 --- a/webpack-lib/lib/ModuleRestoreError.js +++ /dev/null @@ -1,44 +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 ModuleRestoreError extends WebpackError { - /** - * @param {Module} module module tied to dependency - * @param {string | Error} err error thrown - */ - constructor(module, err) { - let message = "Module restore failed: "; - /** @type {string | undefined} */ - const details = undefined; - if (err !== null && typeof err === "object") { - if (typeof err.stack === "string" && err.stack) { - const stack = err.stack; - message += stack; - } else if (typeof err.message === "string" && err.message) { - message += err.message; - } else { - message += err; - } - } else { - message += String(err); - } - - super(message); - - this.name = "ModuleRestoreError"; - /** @type {string | undefined} */ - this.details = details; - this.module = module; - this.error = err; - } -} - -module.exports = ModuleRestoreError; diff --git a/webpack-lib/lib/ModuleSourceTypesConstants.js b/webpack-lib/lib/ModuleSourceTypesConstants.js deleted file mode 100644 index ec5b6706d84..00000000000 --- a/webpack-lib/lib/ModuleSourceTypesConstants.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -/** - * @type {ReadonlySet} - */ -const NO_TYPES = new Set(); - -/** - * @type {ReadonlySet<"asset">} - */ -const ASSET_TYPES = new Set(["asset"]); - -/** - * @type {ReadonlySet<"asset" | "javascript" | "asset">} - */ -const ASSET_AND_JS_TYPES = new Set(["asset", "javascript"]); - -/** - * @type {ReadonlySet<"css-url" | "asset">} - */ -const ASSET_AND_CSS_URL_TYPES = new Set(["asset", "css-url"]); - -/** - * @type {ReadonlySet<"javascript" | "css-url" | "asset">} - */ -const ASSET_AND_JS_AND_CSS_URL_TYPES = new Set([ - "asset", - "javascript", - "css-url" -]); - -/** - * @type {ReadonlySet<"javascript">} - */ -const JS_TYPES = new Set(["javascript"]); - -/** - * @type {ReadonlySet<"javascript" | "css-export">} - */ -const JS_AND_CSS_EXPORT_TYPES = new Set(["javascript", "css-export"]); - -/** - * @type {ReadonlySet<"javascript" | "css-url">} - */ -const JS_AND_CSS_URL_TYPES = new Set(["javascript", "css-url"]); - -/** - * @type {ReadonlySet<"javascript" | "css">} - */ -const JS_AND_CSS_TYPES = new Set(["javascript", "css"]); - -/** - * @type {ReadonlySet<"css">} - */ -const CSS_TYPES = new Set(["css"]); - -/** - * @type {ReadonlySet<"css-url">} - */ -const CSS_URL_TYPES = new Set(["css-url"]); -/** - * @type {ReadonlySet<"css-import">} - */ -const CSS_IMPORT_TYPES = new Set(["css-import"]); - -/** - * @type {ReadonlySet<"webassembly">} - */ -const WEBASSEMBLY_TYPES = new Set(["webassembly"]); - -/** - * @type {ReadonlySet<"runtime">} - */ -const RUNTIME_TYPES = new Set(["runtime"]); - -/** - * @type {ReadonlySet<"remote" | "share-init">} - */ -const REMOTE_AND_SHARE_INIT_TYPES = new Set(["remote", "share-init"]); - -/** - * @type {ReadonlySet<"consume-shared">} - */ -const CONSUME_SHARED_TYPES = new Set(["consume-shared"]); - -/** - * @type {ReadonlySet<"share-init">} - */ -const SHARED_INIT_TYPES = new Set(["share-init"]); - -module.exports.NO_TYPES = NO_TYPES; -module.exports.JS_TYPES = JS_TYPES; -module.exports.JS_AND_CSS_TYPES = JS_AND_CSS_TYPES; -module.exports.JS_AND_CSS_URL_TYPES = JS_AND_CSS_URL_TYPES; -module.exports.JS_AND_CSS_EXPORT_TYPES = JS_AND_CSS_EXPORT_TYPES; -module.exports.ASSET_TYPES = ASSET_TYPES; -module.exports.ASSET_AND_JS_TYPES = ASSET_AND_JS_TYPES; -module.exports.ASSET_AND_CSS_URL_TYPES = ASSET_AND_CSS_URL_TYPES; -module.exports.ASSET_AND_JS_AND_CSS_URL_TYPES = ASSET_AND_JS_AND_CSS_URL_TYPES; -module.exports.CSS_TYPES = CSS_TYPES; -module.exports.CSS_URL_TYPES = CSS_URL_TYPES; -module.exports.CSS_IMPORT_TYPES = CSS_IMPORT_TYPES; -module.exports.WEBASSEMBLY_TYPES = WEBASSEMBLY_TYPES; -module.exports.RUNTIME_TYPES = RUNTIME_TYPES; -module.exports.REMOTE_AND_SHARE_INIT_TYPES = REMOTE_AND_SHARE_INIT_TYPES; -module.exports.CONSUME_SHARED_TYPES = CONSUME_SHARED_TYPES; -module.exports.SHARED_INIT_TYPES = SHARED_INIT_TYPES; diff --git a/webpack-lib/lib/ModuleStoreError.js b/webpack-lib/lib/ModuleStoreError.js deleted file mode 100644 index 26ca0c8b5d7..00000000000 --- a/webpack-lib/lib/ModuleStoreError.js +++ /dev/null @@ -1,43 +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 ModuleStoreError extends WebpackError { - /** - * @param {Module} module module tied to dependency - * @param {string | Error} err error thrown - */ - constructor(module, err) { - let message = "Module storing failed: "; - /** @type {string | undefined} */ - const details = undefined; - if (err !== null && typeof err === "object") { - if (typeof err.stack === "string" && err.stack) { - const stack = err.stack; - message += stack; - } else if (typeof err.message === "string" && err.message) { - message += err.message; - } else { - message += err; - } - } else { - message += String(err); - } - - super(message); - - this.name = "ModuleStoreError"; - this.details = /** @type {string | undefined} */ (details); - this.module = module; - this.error = err; - } -} - -module.exports = ModuleStoreError; diff --git a/webpack-lib/lib/ModuleTemplate.js b/webpack-lib/lib/ModuleTemplate.js deleted file mode 100644 index 799037710d7..00000000000 --- a/webpack-lib/lib/ModuleTemplate.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ -/** @typedef {import("./util/Hash")} Hash */ - -/** - * @template T - * @typedef {import("tapable").IfSet} IfSet - */ - -const getJavascriptModulesPlugin = memoize(() => - require("./javascript/JavascriptModulesPlugin") -); - -// TODO webpack 6: remove this class -class ModuleTemplate { - /** - * @param {RuntimeTemplate} runtimeTemplate the runtime template - * @param {Compilation} compilation the compilation - */ - constructor(runtimeTemplate, compilation) { - this._runtimeTemplate = runtimeTemplate; - this.type = "javascript"; - this.hooks = Object.freeze({ - content: { - tap: util.deprecate( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Module, ChunkRenderContext, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Module, ChunkRenderContext, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Module, ChunkRenderContext, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(Source, Module, ChunkRenderContext, 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( - /** - * @template AdditionalOptions - * @param {string | Tap & IfSet} options options - * @param {function(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" - ) - } - }); - } -} - -Object.defineProperty(ModuleTemplate.prototype, "runtimeTemplate", { - get: util.deprecate( - /** - * @this {ModuleTemplate} - * @returns {RuntimeTemplate} output options - */ - function () { - return this._runtimeTemplate; - }, - "ModuleTemplate.runtimeTemplate is deprecated (use Compilation.runtimeTemplate instead)", - "DEP_WEBPACK_CHUNK_TEMPLATE_OUTPUT_OPTIONS" - ) -}); - -module.exports = ModuleTemplate; diff --git a/webpack-lib/lib/ModuleTypeConstants.js b/webpack-lib/lib/ModuleTypeConstants.js deleted file mode 100644 index dee3ae9f001..00000000000 --- a/webpack-lib/lib/ModuleTypeConstants.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - 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<"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/raw-data-url">} - * TODO: Document what this asset type is for. See css-loader tests for its usage. - */ -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. - * TODO: Document this better. - */ -const WEBPACK_MODULE_TYPE_FALLBACK = "fallback-module"; - -/** - * @type {Readonly<"remote-module">} - * This is the module type used for the ModuleFederation feature's RemoteModule class. - * TODO: Document this better. - */ -const WEBPACK_MODULE_TYPE_REMOTE = "remote-module"; - -/** - * @type {Readonly<"provide-module">} - * This is the module type used for the ModuleFederation feature's ProvideModule class. - * TODO: Document this better. - */ -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"} CSSModuleTypes */ -/** @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 | AssetModuleTypes | WebpackModuleTypes | UnknownModuleTypes} ModuleTypes */ - -module.exports.ASSET_MODULE_TYPE = ASSET_MODULE_TYPE; -module.exports.ASSET_MODULE_TYPE_RAW_DATA_URL = ASSET_MODULE_TYPE_RAW_DATA_URL; -module.exports.ASSET_MODULE_TYPE_SOURCE = ASSET_MODULE_TYPE_SOURCE; -module.exports.ASSET_MODULE_TYPE_RESOURCE = ASSET_MODULE_TYPE_RESOURCE; -module.exports.ASSET_MODULE_TYPE_INLINE = ASSET_MODULE_TYPE_INLINE; -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; -module.exports.WEBASSEMBLY_MODULE_TYPE_ASYNC = WEBASSEMBLY_MODULE_TYPE_ASYNC; -module.exports.WEBASSEMBLY_MODULE_TYPE_SYNC = WEBASSEMBLY_MODULE_TYPE_SYNC; -module.exports.CSS_MODULE_TYPE = CSS_MODULE_TYPE; -module.exports.CSS_MODULE_TYPE_GLOBAL = CSS_MODULE_TYPE_GLOBAL; -module.exports.CSS_MODULE_TYPE_MODULE = CSS_MODULE_TYPE_MODULE; -module.exports.CSS_MODULE_TYPE_AUTO = CSS_MODULE_TYPE_AUTO; -module.exports.WEBPACK_MODULE_TYPE_RUNTIME = WEBPACK_MODULE_TYPE_RUNTIME; -module.exports.WEBPACK_MODULE_TYPE_FALLBACK = WEBPACK_MODULE_TYPE_FALLBACK; -module.exports.WEBPACK_MODULE_TYPE_REMOTE = WEBPACK_MODULE_TYPE_REMOTE; -module.exports.WEBPACK_MODULE_TYPE_PROVIDE = WEBPACK_MODULE_TYPE_PROVIDE; -module.exports.WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE = - WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE; -module.exports.WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY = - WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY; diff --git a/webpack-lib/lib/ModuleWarning.js b/webpack-lib/lib/ModuleWarning.js deleted file mode 100644 index 9b45a9afdd0..00000000000 --- a/webpack-lib/lib/ModuleWarning.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { cleanUp } = require("./ErrorHelpers"); -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ModuleWarning extends WebpackError { - /** - * @param {Error} warning error thrown - * @param {{from?: string|null}} info additional info - */ - constructor(warning, { from = null } = {}) { - let message = "Module Warning"; - - message += from ? ` (from ${from}):\n` : ": "; - - if (warning && typeof warning === "object" && warning.message) { - message += warning.message; - } else if (warning) { - message += String(warning); - } - - super(message); - - this.name = "ModuleWarning"; - this.warning = warning; - this.details = - warning && typeof warning === "object" && warning.stack - ? cleanUp(warning.stack, this.message) - : undefined; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.warning); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.warning = read(); - - super.deserialize(context); - } -} - -makeSerializable(ModuleWarning, "webpack/lib/ModuleWarning"); - -module.exports = ModuleWarning; diff --git a/webpack-lib/lib/MultiCompiler.js b/webpack-lib/lib/MultiCompiler.js deleted file mode 100644 index 8c72da319d9..00000000000 --- a/webpack-lib/lib/MultiCompiler.js +++ /dev/null @@ -1,659 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const { SyncHook, MultiHook } = require("tapable"); - -const ConcurrentCompilationError = require("./ConcurrentCompilationError"); -const MultiStats = require("./MultiStats"); -const MultiWatching = require("./MultiWatching"); -const WebpackError = require("./WebpackError"); -const ArrayQueue = require("./util/ArrayQueue"); - -/** @template T @typedef {import("tapable").AsyncSeriesHook} AsyncSeriesHook */ -/** @template T @template R @typedef {import("tapable").SyncBailHook} SyncBailHook */ -/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Stats")} Stats */ -/** @typedef {import("./Watching")} Watching */ -/** @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 */ - -/** - * @template T - * @callback Callback - * @param {Error | null} err - * @param {T=} result - */ - -/** - * @callback RunWithDependenciesHandler - * @param {Compiler} compiler - * @param {Callback} callback - */ - -/** - * @typedef {object} MultiCompilerOptions - * @property {number=} parallelism how many Compilers are allows to run at the same time in parallel - */ - -module.exports = class MultiCompiler { - /** - * @param {Compiler[] | Record} compilers child compilers - * @param {MultiCompilerOptions} options options - */ - constructor(compilers, options) { - if (!Array.isArray(compilers)) { - /** @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; - for (let index = 0; index < this.compilers.length; index++) { - const compiler = this.compilers[index]; - const compilerIndex = index; - let compilerDone = false; - // eslint-disable-next-line no-loop-func - compiler.hooks.done.tap("MultiCompiler", stats => { - if (!compilerDone) { - compilerDone = true; - doneCompilers++; - } - compilerStats[compilerIndex] = stats; - if (doneCompilers === this.compilers.length) { - this.hooks.done.call( - new MultiStats(/** @type {Stats[]} */ (compilerStats)) - ); - } - }); - // eslint-disable-next-line no-loop-func - compiler.hooks.invalid.tap("MultiCompiler", () => { - if (compilerDone) { - compilerDone = false; - doneCompilers--; - } - }); - } - this._validateCompilersOptions(); - } - - _validateCompilersOptions() { - if (this.compilers.length < 2) return; - /** - * @param {Compiler} compiler compiler - * @param {WebpackError} warning warning - */ - const addWarning = (compiler, warning) => { - compiler.hooks.thisCompilation.tap("MultiCompiler", compilation => { - compilation.warnings.push(warning); - }); - }; - const cacheNames = new Set(); - for (const compiler of this.compilers) { - if (compiler.options.cache && "name" in compiler.options.cache) { - const name = 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() { - let commonPath = this.compilers[0].outputPath; - for (const compiler of this.compilers) { - while ( - compiler.outputPath.indexOf(commonPath) !== 0 && - /[/\\]/.test(commonPath) - ) { - commonPath = commonPath.replace(/[/\\][^/\\]*$/, ""); - } - } - - if (!commonPath && this.compilers[0].outputPath[0] === "/") return "/"; - return commonPath; - } - - get inputFileSystem() { - throw new Error("Cannot read inputFileSystem of a MultiCompiler"); - } - - /** - * @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"); - } - - /** - * @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"); - } - - /** - * @param {WatchFileSystem} value the new watch file system - */ - set watchFileSystem(value) { - for (const compiler of this.compilers) { - compiler.watchFileSystem = value; - } - } - - /** - * @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"); - } - - /** - * @param {string | (function(): 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); - } - - /** - * @param {Compiler} compiler the child compiler - * @param {string[]} dependencies its dependencies - * @returns {void} - */ - setDependencies(compiler, dependencies) { - this.dependencies.set(compiler, 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 = []; - /** - * @param {Compiler} compiler compiler - * @returns {boolean} target was found - */ - const targetFound = compiler => { - for (const edge of edges) { - if (edge.target === compiler) { - return true; - } - } - return false; - }; - /** - * @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) { - 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 { - edges.add({ - source, - target - }); - } - } - } - } - /** @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) { - if (edge.source === current) { - edges.delete(edge); - const target = edge.target; - if (!targetFound(target)) { - stack.push(target); - } - } - } - } - if (edges.size > 0) { - /** @type {string[]} */ - const lines = Array.from(edges) - .sort(sortEdges) - .map(edge => `${edge.source.name} -> ${edge.target.name}`); - lines.unshift("Circular dependency found in compiler dependencies."); - errors.unshift(lines.join("\n")); - } - if (errors.length > 0) { - const message = errors.join("\n"); - callback(new Error(message)); - return false; - } - return true; - } - - // TODO webpack 6 remove - /** - * @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) { - const fulfilledNames = new Set(); - let remainingCompilers = compilers; - /** - * @param {string} d dependency - * @returns {boolean} when dependency was fulfilled - */ - const isDependencyFulfilled = d => fulfilledNames.has(d); - /** - * @returns {Compiler[]} compilers - */ - const getReadyCompilers = () => { - const readyCompilers = []; - const list = remainingCompilers; - remainingCompilers = []; - for (const c of list) { - const dependencies = this.dependencies.get(c); - const ready = - !dependencies || dependencies.every(isDependencyFulfilled); - if (ready) { - readyCompilers.push(c); - } else { - remainingCompilers.push(c); - } - } - return readyCompilers; - }; - /** - * @param {Callback} callback callback - * @returns {void} - */ - const runCompilers = callback => { - if (remainingCompilers.length === 0) return callback(null); - asyncLib.map( - getReadyCompilers(), - (compiler, callback) => { - fn(compiler, err => { - if (err) return callback(err); - fulfilledNames.add(compiler.name); - runCompilers(callback); - }); - }, - (err, results) => { - callback(err, /** @type {TODO} */ (results)); - } - ); - }; - runCompilers(callback); - } - - /** - * @template SetupResult - * @param {function(Compiler, number, Callback, function(): boolean, function(): void, function(): void): SetupResult} setup setup a single compiler - * @param {function(Compiler, SetupResult, 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); - /** - * @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(); - }; - /** - * @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); - } - }; - /** - * @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); - } - }; - /** - * @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") - ) { - 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; - } - - /** - * @param {WatchOptions|WatchOptions[]} watchOptions the watcher's options - * @param {Callback} handler signals when the call finishes - * @returns {MultiWatching} a compiler watcher - */ - watch(watchOptions, handler) { - if (this.running) { - return handler(new ConcurrentCompilationError()); - } - this.running = true; - - if (this.validateDependencies(handler)) { - const watchings = this._runGraph( - (compiler, idx, callback, isBlocked, setChanged, setInvalid) => { - const watching = compiler.watch( - Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions, - callback - ); - if (watching) { - watching._onInvalid = setInvalid; - watching._onChange = setChanged; - watching._isBlocked = isBlocked; - } - return watching; - }, - (compiler, watching, callback) => { - if (compiler.watching !== watching) return; - if (!watching.running) watching.invalidate(); - }, - handler - ); - return new MultiWatching(watchings, this); - } - - return new MultiWatching([], this); - } - - /** - * @param {Callback} callback signals when the call finishes - * @returns {void} - */ - run(callback) { - if (this.running) { - return callback(new ConcurrentCompilationError()); - } - this.running = true; - - if (this.validateDependencies(callback)) { - this._runGraph( - () => {}, - (compiler, setupResult, callback) => compiler.run(callback), - (err, stats) => { - this.running = false; - - if (callback !== undefined) { - return callback(err, stats); - } - } - ); - } - } - - purgeInputFileSystem() { - for (const compiler of this.compilers) { - if (compiler.inputFileSystem && compiler.inputFileSystem.purge) { - compiler.inputFileSystem.purge(); - } - } - } - - /** - * @param {Callback} callback signals when the compiler closes - * @returns {void} - */ - close(callback) { - asyncLib.each( - this.compilers, - (compiler, callback) => { - compiler.close(callback); - }, - error => { - callback(error); - } - ); - } -}; diff --git a/webpack-lib/lib/MultiStats.js b/webpack-lib/lib/MultiStats.js deleted file mode 100644 index bf4771a5fef..00000000000 --- a/webpack-lib/lib/MultiStats.js +++ /dev/null @@ -1,208 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const identifierUtils = require("./util/identifier"); - -/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ -/** @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 */ - -/** - * @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; -}; - -/** @typedef {{ version: boolean, hash: boolean, errorsCount: boolean, warningsCount: boolean, errors: boolean, warnings: boolean, children: NormalizedStatsOptions[] }} ChildOptions */ - -class MultiStats { - /** - * @param {Stats[]} stats the child stats - */ - constructor(stats) { - this.stats = stats; - } - - get hash() { - return this.stats.map(stat => stat.hash).join(""); - } - - /** - * @returns {boolean} true if a child compilation encountered an error - */ - hasErrors() { - return this.stats.some(stat => stat.hasErrors()); - } - - /** - * @returns {boolean} true if a child compilation had a warning - */ - hasWarnings() { - return this.stats.some(stat => stat.hasWarnings()); - } - - /** - * @param {string | boolean | StatsOptions | undefined} 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; - 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 - }; - } - - /** - * @param {(string | boolean | StatsOptions)=} 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; - }); - if (childOptions.version) { - obj.version = obj.children[0].version; - } - if (childOptions.hash) { - obj.hash = obj.children.map(j => j.hash).join(""); - } - /** - * @param {StatsCompilation} j stats error - * @param {StatsError} obj Stats error - * @returns {TODO} 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); - } - } - return obj; - } - - /** - * @param {(string | boolean | StatsOptions)=} options stats options - * @returns {string} string output - */ - toString(options) { - 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"); - } -} - -module.exports = MultiStats; diff --git a/webpack-lib/lib/MultiWatching.js b/webpack-lib/lib/MultiWatching.js deleted file mode 100644 index cfe95f17b28..00000000000 --- a/webpack-lib/lib/MultiWatching.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - 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 */ - -/** - * @template T - * @callback Callback - * @param {(Error | null)=} err - * @param {T=} result - */ - -class MultiWatching { - /** - * @param {Watching[]} watchings child compilers' watchers - * @param {MultiCompiler} compiler the compiler - */ - constructor(watchings, compiler) { - this.watchings = watchings; - this.compiler = compiler; - } - - /** - * @param {Callback=} callback signals when the build has completed again - * @returns {void} - */ - invalidate(callback) { - if (callback) { - asyncLib.each( - this.watchings, - (watching, callback) => watching.invalidate(callback), - callback - ); - } 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.resume(); - } - } - - /** - * @param {Callback} callback signals when the watcher is closed - * @returns {void} - */ - close(callback) { - asyncLib.each( - this.watchings, - (watching, finishedCallback) => { - watching.close(finishedCallback); - }, - err => { - this.compiler.hooks.watchClose.call(); - if (typeof callback === "function") { - this.compiler.running = false; - callback(err); - } - } - ); - } -} - -module.exports = MultiWatching; diff --git a/webpack-lib/lib/NoEmitOnErrorsPlugin.js b/webpack-lib/lib/NoEmitOnErrorsPlugin.js deleted file mode 100644 index a84eb56c753..00000000000 --- a/webpack-lib/lib/NoEmitOnErrorsPlugin.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("./Compiler")} Compiler */ - -class NoEmitOnErrorsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.shouldEmit.tap("NoEmitOnErrorsPlugin", compilation => { - if (compilation.getStats().hasErrors()) return false; - }); - compiler.hooks.compilation.tap("NoEmitOnErrorsPlugin", compilation => { - compilation.hooks.shouldRecord.tap("NoEmitOnErrorsPlugin", () => { - if (compilation.getStats().hasErrors()) return false; - }); - }); - } -} - -module.exports = NoEmitOnErrorsPlugin; diff --git a/webpack-lib/lib/NoModeWarning.js b/webpack-lib/lib/NoModeWarning.js deleted file mode 100644 index fdd3fadf9c6..00000000000 --- a/webpack-lib/lib/NoModeWarning.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"); - -module.exports = class NoModeWarning extends WebpackError { - constructor() { - super(); - - 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/"; - } -}; diff --git a/webpack-lib/lib/NodeStuffInWebError.js b/webpack-lib/lib/NodeStuffInWebError.js deleted file mode 100644 index 02b048ec4fd..00000000000 --- a/webpack-lib/lib/NodeStuffInWebError.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ - -class NodeStuffInWebError extends WebpackError { - /** - * @param {DependencyLocation} loc loc - * @param {string} expression expression - * @param {string} description description - */ - constructor(loc, expression, description) { - super( - `${JSON.stringify( - expression - )} has been used, it will be undefined in next major version. -${description}` - ); - - this.name = "NodeStuffInWebError"; - this.loc = loc; - } -} - -makeSerializable(NodeStuffInWebError, "webpack/lib/NodeStuffInWebError"); - -module.exports = NodeStuffInWebError; diff --git a/webpack-lib/lib/NodeStuffPlugin.js b/webpack-lib/lib/NodeStuffPlugin.js deleted file mode 100644 index 87a5cd61405..00000000000 --- a/webpack-lib/lib/NodeStuffPlugin.js +++ /dev/null @@ -1,275 +0,0 @@ -/* - 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 -} = require("./ModuleTypeConstants"); -const NodeStuffInWebError = require("./NodeStuffInWebError"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const CachedConstDependency = require("./dependencies/CachedConstDependency"); -const ConstDependency = require("./dependencies/ConstDependency"); -const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency"); -const { - evaluateToString, - expressionIsUnsupported -} = require("./javascript/JavascriptParserHelpers"); -const { relative } = require("./util/fs"); -const { parseResource } = require("./util/identifier"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Dependency")} Dependency */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./NormalModule")} NormalModule */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -const PLUGIN_NAME = "NodeStuffPlugin"; - -class NodeStuffPlugin { - /** - * @param {NodeOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - ExternalModuleDependency, - new ExternalModuleDependency.Template() - ); - - /** - * @param {JavascriptParser} parser the parser - * @param {JavascriptParserOptions} parserOptions options - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.node === false) return; - - let localOptions = options; - if (parserOptions.node) { - localOptions = { ...localOptions, ...parserOptions.node }; - } - - if (localOptions.global !== false) { - const withWarning = localOptions.global === "warn"; - parser.hooks.expression.for("global").tap(PLUGIN_NAME, expr => { - const dep = new ConstDependency( - RuntimeGlobals.global, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.global] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - - // TODO webpack 6 remove - 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 = new ConstDependency( - RuntimeGlobals.global, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.global] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return false; - }); - } - - /** - * @param {string} expressionName expression name - * @param {(module: NormalModule) => string} fn function - * @param {string=} warning warning - * @returns {void} - */ - const setModuleConstant = (expressionName, fn, warning) => { - parser.hooks.expression - .for(expressionName) - .tap(PLUGIN_NAME, expr => { - const dep = new CachedConstDependency( - JSON.stringify(fn(parser.state.module)), - /** @type {Range} */ (expr.range), - expressionName - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - - // TODO webpack 6 remove - if (warning) { - parser.state.module.addWarning( - new NodeStuffInWebError(dep.loc, expressionName, warning) - ); - } - - return true; - }); - }; - - /** - * @param {string} expressionName expression name - * @param {(value: string) => string} fn function - * @returns {void} - */ - const setUrlModuleConstant = (expressionName, fn) => { - parser.hooks.expression - .for(expressionName) - .tap(PLUGIN_NAME, expr => { - const dep = new ExternalModuleDependency( - "url", - [ - { - name: "fileURLToPath", - value: "__webpack_fileURLToPath__" - } - ], - undefined, - fn("__webpack_fileURLToPath__"), - /** @type {Range} */ (expr.range), - expressionName - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - - return true; - }); - }; - - /** - * @param {string} expressionName expression name - * @param {string} value value - * @param {string=} warning warning - * @returns {void} - */ - const setConstant = (expressionName, value, warning) => - setModuleConstant(expressionName, () => value, warning); - - const context = compiler.context; - if (localOptions.__filename) { - switch (localOptions.__filename) { - case "mock": - setConstant("__filename", "/index.js"); - break; - case "warn-mock": - setConstant( - "__filename", - "/index.js", - "__filename is a Node.js feature and isn't available in browsers." - ); - break; - case "node-module": - setUrlModuleConstant( - "__filename", - functionName => `${functionName}(import.meta.url)` - ); - break; - case true: - setModuleConstant("__filename", module => - relative( - /** @type {InputFileSystem} */ (compiler.inputFileSystem), - context, - module.resource - ) - ); - break; - } - - 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); - }); - } - if (localOptions.__dirname) { - switch (localOptions.__dirname) { - case "mock": - setConstant("__dirname", "/"); - break; - case "warn-mock": - setConstant( - "__dirname", - "/", - "__dirname is a Node.js feature and isn't available in browsers." - ); - break; - case "node-module": - setUrlModuleConstant( - "__dirname", - functionName => - `${functionName}(import.meta.url + "/..").slice(0, -1)` - ); - break; - case true: - setModuleConstant("__dirname", module => - relative( - /** @type {InputFileSystem} */ (compiler.inputFileSystem), - context, - /** @type {string} */ (module.context) - ) - ); - break; - } - - parser.hooks.evaluateIdentifier - .for("__dirname") - .tap(PLUGIN_NAME, expr => { - if (!parser.state.module) return; - return evaluateToString( - /** @type {string} */ (parser.state.module.context) - )(expr); - }); - } - parser.hooks.expression - .for("require.extensions") - .tap( - PLUGIN_NAME, - expressionIsUnsupported( - parser, - "require.extensions is not supported by webpack. Use a loader instead." - ) - ); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - } - ); - } -} - -module.exports = NodeStuffPlugin; diff --git a/webpack-lib/lib/NormalModule.js b/webpack-lib/lib/NormalModule.js deleted file mode 100644 index 2bf3606a805..00000000000 --- a/webpack-lib/lib/NormalModule.js +++ /dev/null @@ -1,1654 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const parseJson = require("json-parse-even-better-errors"); -const { getContext, runLoaders } = require("loader-runner"); -const querystring = require("querystring"); -const { HookMap, SyncHook, AsyncSeriesBailHook } = require("tapable"); -const { - CachedSource, - OriginalSource, - RawSource, - SourceMapSource -} = require("webpack-sources"); -const Compilation = require("./Compilation"); -const HookWebpackError = require("./HookWebpackError"); -const Module = require("./Module"); -const ModuleBuildError = require("./ModuleBuildError"); -const ModuleError = require("./ModuleError"); -const ModuleGraphConnection = require("./ModuleGraphConnection"); -const ModuleParseError = require("./ModuleParseError"); -const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants"); -const ModuleWarning = require("./ModuleWarning"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const UnhandledSchemeError = require("./UnhandledSchemeError"); -const WebpackError = require("./WebpackError"); -const formatLocation = require("./formatLocation"); -const LazySet = require("./util/LazySet"); -const { isSubset } = require("./util/SetHelpers"); -const { getScheme } = require("./util/URLAbsoluteSpecifier"); -const { - compareLocations, - concatComparators, - compareSelect, - keepOriginalOrder -} = require("./util/comparators"); -const createHash = require("./util/createHash"); -const { createFakeHook } = require("./util/deprecation"); -const { join } = require("./util/fs"); -const { - contextify, - absolutify, - makePathsRelative -} = require("./util/identifier"); -const makeSerializable = require("./util/makeSerializable"); -const memoize = require("./util/memoize"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").Mode} Mode */ -/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Generator")} Generator */ -/** @typedef {import("./Module").BuildInfo} BuildInfo */ -/** @typedef {import("./Module").BuildMeta} BuildMeta */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("./Module").KnownBuildInfo} KnownBuildInfo */ -/** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module").UnsafeCacheData} UnsafeCacheData */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ -/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("./ModuleTypeConstants").JavaScriptModuleTypes} JavaScriptModuleTypes */ -/** @typedef {import("./NormalModuleFactory")} NormalModuleFactory */ -/** @typedef {import("./NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ -/** @typedef {import("./Parser")} Parser */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolveContext} ResolveContext */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./logging/Logger").Logger} WebpackLogger */ -/** @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/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./util/createHash").Algorithm} Algorithm */ -/** - * @template T - * @typedef {import("./util/deprecation").FakeHook} FakeHook - */ - -/** @typedef {{[k: string]: any}} ParserOptions */ -/** @typedef {{[k: string]: any}} GeneratorOptions */ - -/** @typedef {UnsafeCacheData & { parser: undefined | Parser, parserOptions: undefined | ParserOptions, generator: undefined | Generator, generatorOptions: undefined | GeneratorOptions }} NormalModuleUnsafeCacheData */ - -/** - * @template T - * @typedef {import("../declarations/LoaderContext").LoaderContext} LoaderContext - */ - -/** - * @template T - * @typedef {import("../declarations/LoaderContext").NormalModuleLoaderContext} NormalModuleLoaderContext - */ - -/** - * @typedef {object} SourceMap - * @property {number} version - * @property {string[]} sources - * @property {string} mappings - * @property {string=} file - * @property {string=} sourceRoot - * @property {string[]=} sourcesContent - * @property {string[]=} names - * @property {string=} debugId - */ - -const getInvalidDependenciesModuleWarning = memoize(() => - require("./InvalidDependenciesModuleWarning") -); -const getValidate = memoize(() => require("schema-utils").validate); - -const ABSOLUTE_PATH_REGEX = /^([a-zA-Z]:\\|\\\\|\/)/; - -/** - * @typedef {object} LoaderItem - * @property {string} loader - * @property {any} options - * @property {string?} ident - * @property {string?} type - */ - -/** - * @param {string} context absolute context path - * @param {string} source a source path - * @param {object=} 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 - )}`; -}; - -/** - * @param {string} context absolute context path - * @param {SourceMap} sourceMap a source map - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {SourceMap} new source map - */ -const contextifySourceMap = (context, sourceMap, associatedObjectForCache) => { - if (!Array.isArray(sourceMap.sources)) return sourceMap; - const { sourceRoot } = sourceMap; - /** @type {function(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("utf-8"); - } - return input; -}; - -/** - * @param {string | Buffer} input the input - * @returns {Buffer} the converted buffer - */ -const asBuffer = input => { - if (!Buffer.isBuffer(input)) { - return Buffer.from(input, "utf-8"); - } - return input; -}; - -class NonErrorEmittedError extends WebpackError { - /** - * @param {any} error value which is not an instance of Error - */ - constructor(error) { - super(); - - this.name = "NonErrorEmittedError"; - this.message = `(Emitted value instead of an instance of Error) ${error}`; - } -} - -makeSerializable( - NonErrorEmittedError, - "webpack/lib/NormalModule", - "NonErrorEmittedError" -); - -/** - * @typedef {object} NormalModuleCompilationHooks - * @property {SyncHook<[LoaderContext, NormalModule]>} loader - * @property {SyncHook<[LoaderItem[], NormalModule, LoaderContext]>} beforeLoaders - * @property {SyncHook<[NormalModule]>} beforeParse - * @property {SyncHook<[NormalModule]>} beforeSnapshot - * @property {HookMap>>} readResourceForScheme - * @property {HookMap], string | Buffer | null>>} readResource - * @property {AsyncSeriesBailHook<[NormalModule, NeedBuildContext], boolean>} needBuild - */ - -/** - * @typedef {object} NormalModuleCreateData - * @property {string=} layer an optional layer in which the module is - * @property {JavaScriptModuleTypes | ""} 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 {Record=} resourceResolveData resource resolve data - * @property {string} context context directory for resolving - * @property {string=} matchResource path + query of the matched resource (virtual) - * @property {Parser} parser the parser used - * @property {ParserOptions=} parserOptions the options of the parser used - * @property {Generator} generator the generator used - * @property {GeneratorOptions=} generatorOptions the options of the generator used - * @property {ResolveOptions=} resolveOptions options used for resolving requests from this module - */ - -/** @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"]) - ), - 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, - generatorOptions, - resolveOptions - }) { - super(type, context || getContext(resource), layer); - - // Info from Factory - /** @type {string} */ - this.request = request; - /** @type {string} */ - this.userRequest = userRequest; - /** @type {string} */ - this.rawRequest = rawRequest; - /** @type {boolean} */ - this.binary = /^(asset|webassembly)\b/.test(type); - /** @type {undefined | Parser} */ - this.parser = parser; - /** @type {undefined | ParserOptions} */ - this.parserOptions = parserOptions; - /** @type {undefined | Generator} */ - this.generator = generator; - /** @type {undefined | GeneratorOptions} */ - this.generatorOptions = generatorOptions; - /** @type {string} */ - this.resource = resource; - this.resourceResolveData = resourceResolveData; - /** @type {string | undefined} */ - this.matchResource = matchResource; - /** @type {LoaderItem[]} */ - this.loaders = loaders; - if (resolveOptions !== undefined) { - // already declared in super class - this.resolveOptions = resolveOptions; - } - - // Info from Build - /** @type {WebpackError | null} */ - this.error = null; - /** - * @private - * @type {Source | null} - */ - this._source = null; - /** - * @private - * @type {Map | undefined} - */ - this._sourceSizes = undefined; - /** - * @private - * @type {undefined | SourceTypes} - */ - this._sourceTypes = undefined; - - // Cache - this._lastSuccessfulBuildMeta = {}; - this._forceBuild = true; - this._isEvaluatingSideEffects = false; - /** @type {WeakSet | undefined} */ - this._addedSideEffectsBailout = undefined; - /** @type {Map} */ - this._codeGeneratorData = new Map(); - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - 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}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return /** @type {string} */ (requestShortener.shorten(this.userRequest)); - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - let ident = contextify( - options.context, - this.userRequest, - options.associatedObjectForCache - ); - if (this.layer) ident = `(${this.layer})/${ident}`; - return ident; - } - - /** - * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) - */ - nameForCondition() { - const resource = this.matchResource || this.resource; - const idx = resource.indexOf("?"); - 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) { - 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; - } - - /** - * 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; - } - - /** - * 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 = - /** @type {NormalModuleUnsafeCacheData} */ - (super.getUnsafeCacheData()); - data.parserOptions = this.parserOptions; - data.generatorOptions = this.generatorOptions; - return data; - } - - /** - * restore unsafe cache data - * @param {NormalModuleUnsafeCacheData} unsafeCacheData data from getUnsafeCacheData - * @param {NormalModuleFactory} normalModuleFactory the normal module factory handling the unsafe caching - */ - restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory) { - this._restoreFromUnsafeCache(unsafeCacheData, normalModuleFactory); - } - - /** - * restore unsafe cache data - * @param {object} 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 | SourceMap)=} sourceMap an optional source map - * @param {object=} 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 {SourceMap} */ (sourceMap), - associatedObjectForCache - ) - ); - } - } - - return new RawSource(content); - } - - /** - * @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").NormalModuleLoaderContext} loader context - */ - _createLoaderContext(resolver, options, compilation, fs, hooks) { - const { requestShortener } = compilation.runtimeTemplate; - const getCurrentLoaderName = () => { - const currentLoader = this.getCurrentLoader(loaderContext); - if (!currentLoader) return "(not in loader scope)"; - return requestShortener.shorten(currentLoader.loader); - }; - /** - * @returns {ResolveContext} resolve context - */ - const getResolveContext = () => ({ - fileDependencies: { - add: d => /** @type {TODO} */ (loaderContext).addDependency(d) - }, - contextDependencies: { - add: d => /** @type {TODO} */ (loaderContext).addContextDependency(d) - }, - missingDependencies: { - add: d => /** @type {TODO} */ (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 {(string | typeof import("./util/Hash"))=} type type - * @returns {Hash} hash - */ - createHash: type => - createHash( - type || - /** @type {Algorithm} */ - (compilation.outputOptions.hashFunction) - ) - }; - /** @type {import("../declarations/LoaderContext").NormalModuleLoaderContext} */ - const loaderContext = { - version: 2, - getOptions: schema => { - const loader = this.getCurrentLoader(loaderContext); - - let { options } = /** @type {LoaderItem} */ (loader); - - if (typeof options === "string") { - if (options.startsWith("{") && options.endsWith("}")) { - try { - options = parseJson(options); - } catch (err) { - throw new Error( - `Cannot parse string options: ${/** @type {Error} */ (err).message}` - ); - } - } else { - options = querystring.parse(options, "&", "=", { - maxKeys: 0 - }); - } - } - - if (options === null || options === undefined) { - options = {}; - } - - if (schema) { - let name = "Loader"; - let baseDataPath = "options"; - let match; - if (schema.title && (match = /^(.+) (.+)$/.exec(schema.title))) { - [, name, baseDataPath] = match; - } - getValidate()(schema, options, { - name, - baseDataPath - }); - } - - return options; - }, - emitWarning: warning => { - if (!(warning instanceof Error)) { - warning = new NonErrorEmittedError(warning); - } - this.addWarning( - new ModuleWarning(warning, { - from: getCurrentLoaderName() - }) - ); - }, - emitError: error => { - if (!(error instanceof Error)) { - error = new NonErrorEmittedError(error); - } - this.addError( - new ModuleError(error, { - from: getCurrentLoaderName() - }) - ); - }, - getLogger: name => { - const currentLoader = this.getCurrentLoader(loaderContext); - return compilation.getLogger(() => - [currentLoader && currentLoader.loader, name, this.identifier()] - .filter(Boolean) - .join("|") - ); - }, - resolve(context, request, callback) { - resolver.resolve({}, context, request, getResolveContext(), callback); - }, - getResolve(options) { - const child = options ? resolver.withOptions(options) : resolver; - return (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); - } - ); - }); - } - }; - }, - emitFile: (name, content, sourceMap, assetInfo) => { - const buildInfo = /** @type {BuildInfo} */ (this.buildInfo); - - if (!buildInfo.assets) { - buildInfo.assets = Object.create(null); - buildInfo.assetsInfo = new Map(); - } - - const assets = - /** @type {NonNullable} */ - (buildInfo.assets); - const assetsInfo = - /** @type {NonNullable} */ - (buildInfo.assetsInfo); - - assets[name] = this.createSourceForAsset( - /** @type {string} */ (options.context), - name, - content, - sourceMap, - compilation.compiler.root - ); - assetsInfo.set(name, assetInfo); - }, - addBuildDependency: dep => { - const buildInfo = /** @type {BuildInfo} */ (this.buildInfo); - - if (buildInfo.buildDependencies === undefined) { - buildInfo.buildDependencies = new LazySet(); - } - buildInfo.buildDependencies.add(dep); - }, - utils, - rootContext: /** @type {string} */ (options.context), - webpack: true, - sourceMap: Boolean(this.useSourceMap), - mode: options.mode || "production", - hashFunction: /** @type {TODO} */ (options.output.hashFunction), - hashDigest: /** @type {string} */ (options.output.hashDigest), - hashDigestLength: /** @type {number} */ (options.output.hashDigestLength), - hashSalt: /** @type {string} */ (options.output.hashSalt), - _module: this, - _compilation: compilation, - _compiler: compilation.compiler, - fs - }; - - Object.assign(loaderContext, options.loader); - - hooks.loader.call(/** @type {LoaderContext} */ (loaderContext), this); - - return loaderContext; - } - - // TODO remove `loaderContext` in webpack@6 - /** - * @param {TODO} loaderContext loader context - * @param {number} index index - * @returns {LoaderItem | null} loader - */ - getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) { - if ( - this.loaders && - this.loaders.length && - index < this.loaders.length && - index >= 0 && - this.loaders[index] - ) { - return this.loaders[index]; - } - return null; - } - - /** - * @param {string} context the compilation context - * @param {string | Buffer} content the content - * @param {(string | SourceMapSource | null)=} sourceMap an optional source map - * @param {object=} 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(content); - } - - // from here on we assume we have an identifier - const identifier = this.identifier(); - - if (this.useSourceMap && sourceMap) { - return new SourceMapSource( - content, - contextifySourceUrl(context, identifier, associatedObjectForCache), - contextifySourceMap( - context, - /** @type {TODO} */ (sourceMap), - associatedObjectForCache - ) - ); - } - - if (this.useSourceMap || this.useSimpleSourceMap) { - return new OriginalSource( - content, - contextifySourceUrl(context, identifier, associatedObjectForCache) - ); - } - - return new RawSource(content); - } - - /** - * @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 {function((WebpackError | null)=): void} callback callback function - * @returns {void} - */ - _doBuild(options, compilation, resolver, fs, hooks, callback) { - const loaderContext = this._createLoaderContext( - resolver, - options, - compilation, - fs, - hooks - ); - - /** @typedef {[string | Buffer, string | SourceMapSource, Record]} Result */ - - /** - * @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 = /** @type {Result} */ (_result); - 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( - /** @type {string} */ (options.context), - isBinaryModule ? asBuffer(source) : asString(source), - sourceMap, - compilation.compiler.root - ); - if (this._sourceSizes !== undefined) this._sourceSizes.clear(); - this._ast = - typeof extraInfo === "object" && - extraInfo !== null && - extraInfo.webpackAST !== undefined - ? extraInfo.webpackAST - : null; - return callback(); - }; - - const buildInfo = /** @type {BuildInfo} */ (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 {LoaderContext} */ (loaderContext) - ); - } catch (err) { - processResult(/** @type {Error} */ (err)); - return; - } - - if (this.loaders.length > 0) { - /** @type {BuildInfo} */ - (this.buildInfo).buildDependencies = new LazySet(); - } - - runLoaders( - { - resource: this.resource, - loaders: this.loaders, - context: loaderContext, - /** - * @param {LoaderContext} loaderContext the loader context - * @param {string} resourcePath the resource Path - * @param {(err: Error | null, result?: string | Buffer) => void} callback callback - */ - processResource: (loaderContext, resourcePath, callback) => { - const resource = loaderContext.resource; - const scheme = getScheme(resource); - hooks.readResource - .for(scheme) - .callAsync(loaderContext, (err, result) => { - if (err) return callback(err); - if (typeof result !== "string" && !result) { - return callback( - new UnhandledSchemeError( - /** @type {string} */ - (scheme), - resource - ) - ); - } - return callback(null, result); - }); - } - }, - (err, result) => { - // Cleanup loaderContext to avoid leaking memory in ICs - loaderContext._compilation = - loaderContext._compiler = - loaderContext._module = - // eslint-disable-next-line no-warning-comments - // @ts-ignore - loaderContext.fs = - undefined; - - if (!result) { - /** @type {BuildInfo} */ - (this.buildInfo).cacheable = false; - return processResult( - err || new Error("No result from loader-runner processing"), - null - ); - } - - const buildInfo = /** @type {BuildInfo} */ (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); - } - buildInfo.cacheable = buildInfo.cacheable && result.cacheable; - processResult(err, result.result); - } - ); - } - - /** - * @param {WebpackError} error the error - * @returns {void} - */ - markModuleAsErrored(error) { - // Restore build meta from successful build to keep importing state - this.buildMeta = { ...this._lastSuccessfulBuildMeta }; - this.error = error; - this.addError(error); - } - - /** - * @param {TODO} 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.startsWith(rule); - } - - if (typeof rule === "function") { - return rule(content); - } - // we assume rule is a regexp - return rule.test(content); - } - - /** - * @param {TODO} 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. - if (!noParseRule) { - return false; - } - - // we only have one rule to check - if (!Array.isArray(noParseRule)) { - // returns "true" if the module is !not! to be parsed - return this.applyNoParseRule(noParseRule, request); - } - - for (let i = 0; i < noParseRule.length; i++) { - const rule = noParseRule[i]; - // early exit on first truthy match - // this module is !not! to be parsed - if (this.applyNoParseRule(rule, request)) { - return true; - } - } - // no match found, so this module !should! be parsed - return false; - } - - /** - * @param {Compilation} compilation compilation - * @private - */ - _initBuildHash(compilation) { - const hash = createHash( - /** @type {Algorithm} */ - (compilation.outputOptions.hashFunction) - ); - if (this._source) { - hash.update("source"); - this._source.updateHash(hash); - } - hash.update("meta"); - hash.update(JSON.stringify(this.buildMeta)); - /** @type {BuildInfo} */ - (this.buildInfo).hash = /** @type {string} */ (hash.digest("hex")); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this._forceBuild = false; - this._source = null; - if (this._sourceSizes !== undefined) this._sourceSizes.clear(); - this._sourceTypes = undefined; - this._ast = null; - this.error = null; - this.clearWarningsAndErrors(); - this.clearDependenciesAndBlocks(); - this.buildMeta = {}; - this.buildInfo = { - cacheable: false, - parsed: true, - fileDependencies: undefined, - contextDependencies: undefined, - missingDependencies: undefined, - buildDependencies: undefined, - valueDependencies: undefined, - hash: undefined, - assets: undefined, - assetsInfo: undefined - }; - - 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); - this._initBuildHash(compilation); - return callback(); - } - - /** - * @param {Error} e error - * @returns {void} - */ - const handleParseError = e => { - const source = /** @type {Source} */ (this._source).source(); - const loaders = this.loaders.map(item => - contextify( - /** @type {string} */ (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 = () => { - this.dependencies.sort( - concatComparators( - compareSelect(a => a.loc, compareLocations), - keepOriginalOrder(this.dependencies) - ) - ); - this._initBuildHash(compilation); - this._lastSuccessfulBuildMeta = - /** @type {BuildMeta} */ - (this.buildMeta); - return handleBuildDone(); - }; - - const handleBuildDone = () => { - try { - hooks.beforeSnapshot.call(this); - } catch (err) { - this.markModuleAsErrored(/** @type {WebpackError} */ (err)); - return callback(); - } - - const snapshotOptions = compilation.options.snapshot.module; - const { cacheable } = /** @type {BuildInfo} */ (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 {LazySet} deps deps - */ - const checkDependencies = deps => { - for (const dep of deps) { - if (!ABSOLUTE_PATH_REGEX.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_REGEX.test(absolute)) { - (depWithoutGlob !== dep - ? /** @type {NonNullable} */ - ( - /** @type {BuildInfo} */ (this.buildInfo) - .contextDependencies - ) - : deps - ).add(absolute); - } - } catch (_err) { - // ignore - } - } - } - }; - const buildInfo = /** @type {BuildInfo} */ (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) { - this.markModuleAsErrored(err); - return; - } - buildInfo.fileDependencies = undefined; - buildInfo.contextDependencies = undefined; - buildInfo.missingDependencies = undefined; - buildInfo.snapshot = snapshot; - return callback(); - } - ); - }; - - try { - hooks.beforeParse.call(this); - } catch (err) { - this.markModuleAsErrored(/** @type {WebpackError} */ (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 {BuildInfo} */ - (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 - }); - } catch (parseErr) { - handleParseError(/** @type {Error} */ (parseErr)); - return; - } - handleParseResult(); - }); - } - - /** - * @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); - } - - /** - * @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; - } - if (this.buildMeta !== undefined && this.buildMeta.sideEffectFree) { - if (this._isEvaluatingSideEffects) - return ModuleGraphConnection.CIRCULAR_CONNECTION; - this._isEvaluatingSideEffects = true; - /** @type {ConnectionState} */ - let current = false; - for (const dep of this.dependencies) { - const state = dep.getModuleEvaluationSideEffectsState(moduleGraph); - if (state === true) { - if ( - this._addedSideEffectsBailout === undefined - ? ((this._addedSideEffectsBailout = new WeakSet()), true) - : !this._addedSideEffectsBailout.has(moduleGraph) - ) { - this._addedSideEffectsBailout.add(moduleGraph); - moduleGraph - .getOptimizationBailout(this) - .push( - () => - `Dependency (${ - dep.type - }) with side effects at ${formatLocation(dep.loc)}` - ); - } - this._isEvaluatingSideEffects = false; - return true; - } else if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) { - current = ModuleGraphConnection.addConnectionStates(current, state); - } - } - this._isEvaluatingSideEffects = false; - // When caching is implemented here, make sure to not cache when - // at least one circular connection was in the loop above - return current; - } - return true; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - if (this._sourceTypes === undefined) { - this._sourceTypes = /** @type {Generator} */ (this.generator).getTypes( - this - ); - } - return this._sourceTypes; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime, - concatenationScope, - codeGenerationResults, - sourceTypes - }) { - /** @type {Set} */ - const runtimeRequirements = new Set(); - - const { parsed } = /** @type {BuildInfo} */ (this.buildInfo); - - if (!parsed) { - runtimeRequirements.add(RuntimeGlobals.module); - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.thisAsExports); - } - - /** @type {function(): Map} */ - const getData = () => this._codeGeneratorData; - - const sources = new Map(); - for (const type of sourceTypes || chunkGraph.getModuleSourceTypes(this)) { - const source = this.error - ? new RawSource( - `throw new Error(${JSON.stringify(this.error.message)});` - ) - : /** @type {Generator} */ (this.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 - }; - return resultEntry; - } - - /** - * @returns {Source | null} the original source for the module before webpack transformation - */ - originalSource() { - return this._source; - } - - /** - * @returns {void} - */ - invalidateBuild() { - this._forceBuild = true; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} 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 {BuildInfo} */ (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)); - }); - }); - } - - /** - * @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; - } - - /** - * @param {LazySet} fileDependencies set where file dependencies are added to - * @param {LazySet} contextDependencies set where context dependencies are added to - * @param {LazySet} missingDependencies set where missing dependencies are added to - * @param {LazySet} buildDependencies set where build dependencies are added to - */ - addCacheDependencies( - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - ) { - const { snapshot, buildDependencies: buildDeps } = - /** @type {BuildInfo} */ (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 {BuildInfo} */ (this.buildInfo); - if (fileDeps !== undefined) fileDependencies.addAll(fileDeps); - if (contextDeps !== undefined) contextDependencies.addAll(contextDeps); - if (missingDeps !== undefined) missingDependencies.addAll(missingDeps); - } - if (buildDeps !== undefined) { - buildDependencies.addAll(buildDeps); - } - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - hash.update(/** @type {BuildInfo} */ (this.buildInfo).hash); - /** @type {Generator} */ - (this.generator).updateHash(hash, { - module: this, - ...context - }); - super.updateHash(hash, 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); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {TODO} Module - */ - static deserialize(context) { - const obj = new NormalModule({ - // 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) - }); - obj.deserialize(context); - return obj; - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this._source = read(); - this.error = read(); - this._lastSuccessfulBuildMeta = read(); - this._forceBuild = read(); - this._codeGeneratorData = read(); - super.deserialize(context); - } -} - -makeSerializable(NormalModule, "webpack/lib/NormalModule"); - -module.exports = NormalModule; diff --git a/webpack-lib/lib/NormalModuleFactory.js b/webpack-lib/lib/NormalModuleFactory.js deleted file mode 100644 index 546bd593ac4..00000000000 --- a/webpack-lib/lib/NormalModuleFactory.js +++ /dev/null @@ -1,1326 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { getContext } = require("loader-runner"); -const asyncLib = require("neo-async"); -const { - AsyncSeriesBailHook, - SyncWaterfallHook, - SyncBailHook, - SyncHook, - HookMap -} = 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 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 { - parseResource, - parseResourceWithoutFragment -} = require("./util/identifier"); - -/** @typedef {import("../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("./Generator")} Generator */ -/** @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").ResolveContext} ResolveContext */ -/** @typedef {import("./ResolverFactory").ResolveRequest} ResolveRequest */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./dependencies/ModuleDependency")} ModuleDependency */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -/** @typedef {Pick} ModuleSettings */ -/** @typedef {Partial} CreateData */ - -/** - * @typedef {object} ResolveData - * @property {ModuleFactoryCreateData["contextInfo"]} contextInfo - * @property {ModuleFactoryCreateData["resolveOptions"]} resolveOptions - * @property {string} context - * @property {string} request - * @property {Record | undefined} assertions - * @property {ModuleDependency[]} dependencies - * @property {string} dependencyType - * @property {CreateData} createData - * @property {LazySet} fileDependencies - * @property {LazySet} missingDependencies - * @property {LazySet} contextDependencies - * @property {Module=} ignoredModule - * @property {boolean} cacheable allow to use the unsafe cache - */ - -/** - * @typedef {object} ResourceData - * @property {string} resource - * @property {string=} path - * @property {string=} query - * @property {string=} fragment - * @property {string=} context - */ - -/** @typedef {ResourceData & { data: Record }} ResourceDataWithData */ - -/** - * @typedef {object} ParsedLoaderRequest - * @property {string} loader loader - * @property {string|undefined} options options - */ - -/** - * @template T - * @callback Callback - * @param {(Error | null)=} err - * @param {T=} stats - * @returns {void} - */ - -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 = /^[^.]/; - -/** - * @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}`; - } - 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}?${JSON.stringify(data.options)}`; -}; - -/** - * @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; -}; - -/** - * @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); - } -}; - -/** - * @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("/"); - 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 -/** - * @param {string} name name - * @param {TODO} hook hook - * @returns {string} result - */ -const deprecationChangedHookMessage = (name, hook) => { - const names = hook.taps - .map( - /** - * @param {TODO} tapped tapped - * @returns {string} name - */ - tapped => tapped.name - ) - .join(", "); - - return ( - `NormalModuleFactory.${name} (${names}) is no longer a waterfall hook, but a bailing hook instead. ` + - "Do not return the passed object, but modify it instead. " + - "Returning false will ignore the request and results in no module created." - ); -}; - -const ruleSetCompiler = new RuleSetCompiler([ - new BasicMatcherRulePlugin("test", "resource"), - new BasicMatcherRulePlugin("scheme"), - new BasicMatcherRulePlugin("mimetype"), - new BasicMatcherRulePlugin("dependency"), - new BasicMatcherRulePlugin("include", "resource"), - new BasicMatcherRulePlugin("exclude", "resource", true), - new BasicMatcherRulePlugin("resource"), - new BasicMatcherRulePlugin("resourceQuery"), - new BasicMatcherRulePlugin("resourceFragment"), - new BasicMatcherRulePlugin("realResource"), - new BasicMatcherRulePlugin("issuer"), - new BasicMatcherRulePlugin("compiler"), - new BasicMatcherRulePlugin("issuerLayer"), - new ObjectMatcherRulePlugin("assert", "assertions", value => { - if (value) { - return /** @type {any} */ (value)._isLegacyAssert !== undefined; - } - - return false; - }), - new ObjectMatcherRulePlugin("with", "assertions", value => { - if (value) { - return !(/** @type {any} */ (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 UseEffectRulePlugin() -]); - -class NormalModuleFactory extends ModuleFactory { - /** - * @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 {object} param.associatedObjectForCache an object to which the cache will be attached - * @param {boolean=} param.layers enable layers - */ - constructor({ - context, - fs, - resolverFactory, - options, - associatedObjectForCache, - layers = false - }) { - super(); - 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<[ResolveData["createData"], ResolveData], Module | void>} */ - createModule: new AsyncSeriesBailHook(["createData", "resolveData"]), - /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData]>} */ - module: new SyncWaterfallHook(["module", "createData", "resolveData"]), - /** @type {HookMap>} */ - createParser: new HookMap(() => new SyncBailHook(["parserOptions"])), - /** @type {HookMap>} */ - parser: new HookMap(() => new SyncHook(["parser", "parserOptions"])), - /** @type {HookMap>} */ - createGenerator: new HookMap( - () => new SyncBailHook(["generatorOptions"]) - ), - /** @type {HookMap>} */ - generator: new HookMap( - () => new SyncHook(["generator", "generatorOptions"]) - ), - /** @type {HookMap>} */ - createModuleClass: new HookMap( - () => new SyncBailHook(["createData", "resolveData"]) - ) - }); - this.resolverFactory = resolverFactory; - this.ruleSet = ruleSetCompiler.compile([ - { - rules: options.defaultRules - }, - { - rules: options.rules - } - ]); - this.context = context || ""; - 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(); - - const cacheParseResource = parseResource.bindCache( - associatedObjectForCache - ); - const cachedParseResourceWithoutFragment = - parseResourceWithoutFragment.bindCache(associatedObjectForCache); - this._parseResourceWithoutFragment = cachedParseResourceWithoutFragment; - - this.hooks.factorize.tapAsync( - { - name: "NormalModuleFactory", - stage: 100 - }, - (resolveData, callback) => { - this.hooks.resolve.callAsync(resolveData, (err, result) => { - if (err) return callback(err); - - // Ignored - if (result === false) return callback(); - - // direct module - if (result instanceof Module) return callback(null, result); - - if (typeof result === "object") - throw new Error( - `${deprecationChangedHookMessage( - "resolve", - this.hooks.resolve - )} Returning a Module object will result in this module used as result.` - ); - - this.hooks.afterResolve.callAsync(resolveData, (err, result) => { - if (err) return callback(err); - - if (typeof result === "object") - throw new Error( - deprecationChangedHookMessage( - "afterResolve", - this.hooks.afterResolve - ) - ); - - // Ignored - if (result === false) return callback(); - - const createData = resolveData.createData; - - 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( - /** @type {ModuleSettings} */ - (createData.settings).type - ) - .call(createData, resolveData); - - if (!createdModule) { - createdModule = /** @type {Module} */ ( - new NormalModule( - /** @type {NormalModuleCreateData} */ - (createData) - ) - ); - } - } - - createdModule = this.hooks.module.call( - createdModule, - createData, - resolveData - ); - - return callback(null, createdModule); - } - ); - }); - }); - } - ); - this.hooks.resolve.tapAsync( - { - name: "NormalModuleFactory", - stage: 100 - }, - (data, callback) => { - const { - contextInfo, - context, - dependencies, - dependencyType, - request, - assertions, - resolveOptions, - fileDependencies, - missingDependencies, - contextDependencies - } = data; - const loaderResolver = this.getResolver("loader"); - - /** @type {ResourceData | undefined} */ - let matchResourceData; - /** @type {string} */ - let unresolvedResource; - /** @type {ParsedLoaderRequest[]} */ - let elements; - let noPreAutoLoaders = false; - let noAutoLoaders = false; - let noPrePostAutoLoaders = false; - - 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]; - if (matchResource.charCodeAt(0) === 46) { - // 46 === ".", 47 === "/" - const secondChar = matchResource.charCodeAt(1); - if ( - secondChar === 47 || - (secondChar === 46 && matchResource.charCodeAt(2) === 47) - ) { - // if matchResources startsWith ../ or ./ - matchResource = join(this.fs, context, matchResource); - } - } - matchResourceData = { - resource: matchResource, - ...cacheParseResource(matchResource) - }; - requestWithoutMatchResource = request.slice( - matchResourceMatch[0].length - ); - } - - 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); - - // translate option idents - try { - for (const item of /** @type {LoaderItem[]} */ (loaders)) { - if (typeof item.options === "string" && item.options[0] === "?") { - 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 (identErr) { - return callback(/** @type {Error} */ (identErr)); - } - - if (!resourceData) { - // ignored - return callback(null, dependencies[0].createIgnoredModule(context)); - } - - const userRequest = - (matchResourceData !== undefined - ? `${matchResourceData.resource}!=!` - : "") + - stringifyLoadersAndResource( - /** @type {LoaderItem[]} */ (loaders), - resourceData.resource - ); - - /** @type {ModuleSettings} */ - const settings = {}; - const useLoadersPost = []; - const useLoaders = []; - const useLoadersPre = []; - - // handle .webpack[] suffix - let resource; - 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, - assertions, - 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 ( - typeof r.value === "object" && - r.value !== null && - typeof settings[ - /** @type {keyof ModuleSettings} */ (r.type) - ] === "object" && - settings[/** @type {keyof ModuleSettings} */ (r.type)] !== null - ) { - settings[r.type] = cachedCleverMerge( - settings[/** @type {keyof ModuleSettings} */ (r.type)], - r.value - ); - } else { - settings[r.type] = r.value; - } - } - } - - /** @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 {string} */ (settings.type); - const resolveOptions = settings.resolve; - const layer = settings.layer; - if (layer !== undefined && !layers) { - return callback( - new Error( - "'Rule.layer' is only allowed when 'experiments.layers' is enabled" - ) - ); - } - 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 - }); - } 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(); - } - ); - - /** - * @param {string} context context - */ - const defaultResolve = context => { - if (/^($|\?)/.test(unresolvedResource)) { - resourceData = { - resource: unresolvedResource, - data: {}, - ...cacheParseResource(unresolvedResource) - }; - 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, - unresolvedResource, - normalResolver, - resolveContext, - (err, _resolvedResource, resolvedResourceResolveData) => { - if (err) return continueCallback(err); - if (_resolvedResource !== false) { - const resolvedResource = - /** @type {string} */ - (_resolvedResource); - resourceData = { - resource: resolvedResource, - data: - /** @type {ResolveRequest} */ - (resolvedResourceResolveData), - ...cacheParseResource(resolvedResource) - }; - } - 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(); - } - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const dependencies = /** @type {ModuleDependency[]} */ (data.dependencies); - const context = data.context || this.context; - const resolveOptions = data.resolveOptions || EMPTY_RESOLVE_OPTIONS; - const dependency = dependencies[0]; - const request = dependency.request; - const assertions = dependency.assertions; - const contextInfo = data.contextInfo; - const fileDependencies = new LazySet(); - const missingDependencies = new LazySet(); - const contextDependencies = new LazySet(); - const dependencyType = - (dependencies.length > 0 && dependencies[0].category) || ""; - /** @type {ResolveData} */ - const resolveData = { - contextInfo, - resolveOptions, - context, - request, - assertions, - 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 === false) { - /** @type {ModuleFactoryResult} * */ - const factoryResult = { - fileDependencies, - missingDependencies, - contextDependencies, - cacheable: resolveData.cacheable - }; - - if (resolveData.ignoredModule) { - factoryResult.module = resolveData.ignoredModule; - } - - return callback(null, factoryResult); - } - - 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); - }); - }); - } - - /** - * @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 = Array.from( - 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); - } - ); - } - - /** - * @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)); - } - ); - } - - /** - * @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, - (item, callback) => { - resolver.resolve( - contextInfo, - context, - item.loader, - resolveContext, - (err, result, resolveRequest) => { - if ( - err && - /^[^/]*$/.test(item.loader) && - !item.loader.endsWith("-loader") - ) { - return resolver.resolve( - contextInfo, - context, - `${item.loader}-loader`, - resolveContext, - err2 => { - if (!err2) { - err.message = - `${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` + - " see https://webpack.js.org/migrate/3/#automatic-loader-module-name-extension-removed"; - } - callback(err); - } - ); - } - if (err) return callback(err); - - 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 {ResolveRequest} */ - (resolveRequest).descriptionFileData.type; - const resolved = { - loader: parsedResult.path, - type, - options: - item.options === undefined - ? parsedResult.query - ? parsedResult.query.slice(1) - : undefined - : item.options, - ident: - item.options === undefined - ? undefined - : /** @type {string} */ (item.ident) - }; - - return callback(null, /** @type {LoaderItem} */ (resolved)); - } - ); - }, - /** @type {Callback} */ (callback) - ); - } - - /** - * @param {string} type type - * @param {ParserOptions} parserOptions parser options - * @returns {Parser} parser - */ - getParser(type, parserOptions = EMPTY_PARSER_OPTIONS) { - let cache = this.parserCache.get(type); - - if (cache === undefined) { - cache = new WeakMap(); - this.parserCache.set(type, cache); - } - - let parser = cache.get(parserOptions); - - if (parser === undefined) { - parser = this.createParser(type, parserOptions); - cache.set(parserOptions, parser); - } - - return parser; - } - - /** - * @param {string} type type - * @param {ParserOptions} parserOptions parser options - * @returns {Parser} 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; - } - - /** - * @param {string} type type of generator - * @param {GeneratorOptions} generatorOptions generator options - * @returns {Generator} generator - */ - getGenerator(type, generatorOptions = EMPTY_GENERATOR_OPTIONS) { - let cache = this.generatorCache.get(type); - - if (cache === undefined) { - cache = new WeakMap(); - this.generatorCache.set(type, cache); - } - - let generator = cache.get(generatorOptions); - - if (generator === undefined) { - generator = this.createGenerator(type, generatorOptions); - cache.set(generatorOptions, generator); - } - - return generator; - } - - /** - * @param {string} type type of generator - * @param {GeneratorOptions} generatorOptions generator options - * @returns {Generator} generator - */ - createGenerator(type, generatorOptions = {}) { - generatorOptions = mergeGlobalOptions( - this._globalGeneratorOptions, - type, - generatorOptions - ); - const generator = this.hooks.createGenerator - .for(type) - .call(generatorOptions); - if (!generator) { - throw new Error(`No generator registered for ${type}`); - } - this.hooks.generator.for(type).call(generator, generatorOptions); - return generator; - } - - /** - * @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); - } -} - -module.exports = NormalModuleFactory; diff --git a/webpack-lib/lib/NormalModuleReplacementPlugin.js b/webpack-lib/lib/NormalModuleReplacementPlugin.js deleted file mode 100644 index fb44e088db1..00000000000 --- a/webpack-lib/lib/NormalModuleReplacementPlugin.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { join, dirname } = require("./util/fs"); - -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -/** @typedef {function(import("./NormalModuleFactory").ResolveData): void} ModuleReplacer */ - -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; - } - - /** - * Apply the plugin - * @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 (resourceRegExp.test(result.request)) { - if (typeof newResource === "function") { - newResource(result); - } else { - result.request = newResource; - } - } - }); - nmf.hooks.afterResolve.tap("NormalModuleReplacementPlugin", 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 { - createData.resource = join( - fs, - dirname(fs, /** @type {string} */ (createData.resource)), - newResource - ); - } - } - } - }); - } - ); - } -} - -module.exports = NormalModuleReplacementPlugin; diff --git a/webpack-lib/lib/NullFactory.js b/webpack-lib/lib/NullFactory.js deleted file mode 100644 index 50f3471be46..00000000000 --- a/webpack-lib/lib/NullFactory.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleFactory = require("./ModuleFactory"); - -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ - -class NullFactory extends ModuleFactory { - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - return callback(); - } -} -module.exports = NullFactory; diff --git a/webpack-lib/lib/OptimizationStages.js b/webpack-lib/lib/OptimizationStages.js deleted file mode 100644 index 102d613c5aa..00000000000 --- a/webpack-lib/lib/OptimizationStages.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -module.exports.STAGE_BASIC = -10; -module.exports.STAGE_DEFAULT = 0; -module.exports.STAGE_ADVANCED = 10; diff --git a/webpack-lib/lib/OptionsApply.js b/webpack-lib/lib/OptionsApply.js deleted file mode 100644 index b7a3941543b..00000000000 --- a/webpack-lib/lib/OptionsApply.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./Compiler")} Compiler */ - -class OptionsApply { - /** - * @param {WebpackOptions} options options object - * @param {Compiler} compiler compiler object - * @returns {WebpackOptions} options object - */ - process(options, compiler) { - return options; - } -} - -module.exports = OptionsApply; diff --git a/webpack-lib/lib/Parser.js b/webpack-lib/lib/Parser.js deleted file mode 100644 index 892c5fcd329..00000000000 --- a/webpack-lib/lib/Parser.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./NormalModule")} NormalModule */ - -/** @typedef {Record} PreparsedAst */ - -/** - * @typedef {object} ParserStateBase - * @property {string | Buffer} source - * @property {NormalModule} current - * @property {NormalModule} module - * @property {Compilation} compilation - * @property {{[k: string]: any}} options - */ - -/** @typedef {Record & ParserStateBase} ParserState */ - -class Parser { - /* istanbul ignore next */ - /** - * @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("./AbstractMethodError"); - throw new AbstractMethodError(); - } -} - -module.exports = Parser; diff --git a/webpack-lib/lib/PlatformPlugin.js b/webpack-lib/lib/PlatformPlugin.js deleted file mode 100644 index ae601ae8b45..00000000000 --- a/webpack-lib/lib/PlatformPlugin.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - 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 */ - -/** - * Should be used only for "target === false" or - * when you want to overwrite platform target properties - */ -class PlatformPlugin { - /** - * @param {Partial} platform target properties - */ - constructor(platform) { - /** @type {Partial} */ - this.platform = platform; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.environment.tap("PlatformPlugin", () => { - compiler.platform = { - ...compiler.platform, - ...this.platform - }; - }); - } -} - -module.exports = PlatformPlugin; diff --git a/webpack-lib/lib/PrefetchPlugin.js b/webpack-lib/lib/PrefetchPlugin.js deleted file mode 100644 index 4f09fc0c3dc..00000000000 --- a/webpack-lib/lib/PrefetchPlugin.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - 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 */ - -class PrefetchPlugin { - /** - * @param {string} context context or request if context is not set - * @param {string} [request] request - */ - constructor(context, request) { - if (request) { - this.context = context; - this.request = request; - } else { - this.context = null; - this.request = context; - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "PrefetchPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - PrefetchDependency, - normalModuleFactory - ); - } - ); - compiler.hooks.make.tapAsync("PrefetchPlugin", (compilation, callback) => { - compilation.addModuleChain( - this.context || compiler.context, - new PrefetchDependency(this.request), - err => { - callback(err); - } - ); - }); - } -} - -module.exports = PrefetchPlugin; diff --git a/webpack-lib/lib/ProgressPlugin.js b/webpack-lib/lib/ProgressPlugin.js deleted file mode 100644 index b8be13916cc..00000000000 --- a/webpack-lib/lib/ProgressPlugin.js +++ /dev/null @@ -1,709 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Compiler = require("./Compiler"); -const MultiCompiler = require("./MultiCompiler"); -const NormalModule = require("./NormalModule"); -const createSchemaValidation = require("./util/create-schema-validation"); -const { contextify } = require("./util/identifier"); - -/** @typedef {import("tapable").Tap} Tap */ -/** @typedef {import("../declarations/plugins/ProgressPlugin").HandlerFunction} HandlerFunction */ -/** @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 */ - -/** - * @template T, K, R - * @typedef {import("./util/AsyncQueue")} AsyncQueue - */ - -/** - * @typedef {object} CountsData - * @property {number} modulesCount modules count - * @property {number} dependenciesCount dependencies count - */ - -const validate = createSchemaValidation( - require("../schemas/plugins/ProgressPlugin.check.js"), - () => require("../schemas/plugins/ProgressPlugin.json"), - { - name: "Progress Plugin", - baseDataPath: "options" - } -); - -/** - * @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); - -/** - * @param {boolean | null | undefined} profile need profile - * @param {Logger} logger logger - * @returns {defaultHandler} default handler - */ -const createDefaultHandler = (profile, logger) => { - /** @type {{ value: string | undefined, time: number }[]} */ - const lastStateInfo = []; - - /** - * @param {number} percentage percentage - * @param {string} msg message - * @param {...string} args additional arguments - */ - const defaultHandler = (percentage, msg, ...args) => { - if (profile) { - if (percentage === 0) { - lastStateInfo.length = 0; - } - const fullState = [msg, ...args]; - const state = fullState.map(s => s.replace(/\d+\/\d+ /g, "")); - const now = Date.now(); - const len = Math.max(state.length, lastStateInfo.length); - for (let i = len; i >= 0; i--) { - const stateItem = i < state.length ? state[i] : undefined; - const lastStateItem = - i < lastStateInfo.length ? lastStateInfo[i] : undefined; - if (lastStateItem) { - if (stateItem !== lastStateItem.value) { - const diff = now - lastStateItem.time; - if (lastStateItem.value) { - let reportState = lastStateItem.value; - if (i > 0) { - reportState = `${lastStateInfo[i - 1].value} > ${reportState}`; - } - const stateMsg = `${" | ".repeat(i)}${diff} ms ${reportState}`; - const d = diff; - // This depends on timing so we ignore it for coverage - /* eslint-disable no-lone-blocks */ - /* istanbul ignore next */ - { - if (d > 10000) { - logger.error(stateMsg); - } else if (d > 1000) { - logger.warn(stateMsg); - } else if (d > 10) { - logger.info(stateMsg); - } else if (d > 5) { - logger.log(stateMsg); - } else { - logger.debug(stateMsg); - } - } - /* eslint-enable no-lone-blocks */ - } - if (stateItem === undefined) { - lastStateInfo.length = i; - } else { - lastStateItem.value = stateItem; - lastStateItem.time = now; - lastStateInfo.length = i + 1; - } - } - } else { - lastStateInfo[i] = { - value: stateItem, - time: now - }; - } - } - } - 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"]; - -/** - * @callback ReportProgress - * @param {number} p percentage - * @param {...string} args additional arguments - * @returns {void} - */ - -/** @type {WeakMap} */ -const progressReporters = new WeakMap(); - -class ProgressPlugin { - /** - * @param {Compiler} compiler the current compiler - * @returns {ReportProgress | undefined} a progress reporter, if any - */ - static getReporter(compiler) { - return progressReporters.get(compiler); - } - - /** - * @param {ProgressPluginArgument} options options - */ - constructor(options = {}) { - if (typeof options === "function") { - options = { - handler: options - }; - } - - validate(options); - options = { ...ProgressPlugin.defaultOptions, ...options }; - - this.profile = options.profile; - this.handler = options.handler; - this.modulesCount = options.modulesCount; - this.dependenciesCount = options.dependenciesCount; - this.showEntries = options.entries; - this.showModules = options.modules; - this.showDependencies = options.dependencies; - this.showActiveModules = options.activeModules; - this.percentBy = options.percentBy; - } - - /** - * @param {Compiler | MultiCompiler} compiler webpack compiler - * @returns {void} - */ - apply(compiler) { - const handler = - this.handler || - createDefaultHandler( - this.profile, - compiler.getInfrastructureLogger("webpack.Progress") - ); - if (compiler instanceof MultiCompiler) { - this._applyOnMultiCompiler(compiler, handler); - } else if (compiler instanceof Compiler) { - this._applyOnCompiler(compiler, handler); - } - } - - /** - * @param {MultiCompiler} compiler webpack multi-compiler - * @param {HandlerFunction} 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); - } - } - - /** - * @param {Compiler} compiler webpack compiler - * @param {HandlerFunction} handler function that executes for every progress step - * @returns {void} - */ - _applyOnCompiler(compiler, handler) { - 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; - 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); - 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 - ); - } - - const percentage = 0.1 + percentageFactor * 0.55; - - if (currentLoader) { - items.push( - `import loader ${contextify( - compiler.context, - currentLoader, - compiler.root - )}` - ); - } else { - 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(); - }; - - /** - * @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(); - }; - - /** - * @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 - /** - * @param {Module} module the module - */ - const moduleBuild = module => { - const ident = module.identifier(); - if (ident) { - activeModules.add(ident); - lastActiveModule = ident; - update(); - } - }; - - /** - * @param {Dependency} entry entry dependency - * @param {EntryOptions} options options object - */ - const entryAdd = (entry, options) => { - entriesCount++; - if (entriesCount < 5 || entriesCount % 10 === 0) updateThrottled(); - }; - - /** - * @param {Module} module the module - */ - const moduleDone = module => { - doneModules++; - if (showActiveModules) { - const ident = module.identifier(); - if (ident) { - activeModules.delete(ident); - if (lastActiveModule === ident) { - lastActiveModule = ""; - for (const m of activeModules) { - lastActiveModule = m; - } - update(); - return; - } - } - } - if (doneModules < 50 || doneModules % 100 === 0) updateThrottled(); - }; - - /** - * @param {Dependency} entry entry dependency - * @param {EntryOptions} options options object - */ - const entryDone = (entry, options) => { - doneEntries++; - update(); - }; - - const cache = compiler - .getCache("ProgressPlugin") - .getItemCache("counts", null); - - /** @type {Promise | undefined} */ - let cacheGetPromise; - - compiler.hooks.beforeCompile.tap("ProgressPlugin", () => { - if (!cacheGetPromise) { - cacheGetPromise = cache.getPromise().then( - data => { - if (data) { - lastModulesCount = lastModulesCount || data.modulesCount; - lastDependenciesCount = - lastDependenciesCount || data.dependenciesCount; - } - return data; - }, - err => { - // Ignore error - } - ); - } - }); - - compiler.hooks.afterCompile.tapPromise("ProgressPlugin", 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("ProgressPlugin", 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("ProgressPlugin", item => - factorizeAdd(compilation.factorizeQueue, item) - ); - compilation.factorizeQueue.hooks.result.tap( - "ProgressPlugin", - factorizeDone - ); - - compilation.addModuleQueue.hooks.added.tap("ProgressPlugin", item => - moduleAdd(compilation.addModuleQueue, item) - ); - compilation.processDependenciesQueue.hooks.result.tap( - "ProgressPlugin", - moduleDone - ); - - if (showActiveModules) { - compilation.hooks.buildModule.tap("ProgressPlugin", moduleBuild); - } - - compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd); - compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone); - compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone); - - // avoid dynamic require if bundled with webpack - // @ts-expect-error - if (typeof __webpack_require__ !== "function") { - const requiredLoaders = new Set(); - NormalModule.getCompilationHooks(compilation).beforeLoaders.tap( - "ProgressPlugin", - 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); - } - } - 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: "ProgressPlugin", - 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.make.intercept({ - name: "ProgressPlugin", - call() { - handler(0.1, "building"); - }, - done() { - handler(0.65, "building"); - } - }); - /** - * @param {TODO} 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: "ProgressPlugin", - call() { - handler(progress, category, name); - }, - done() { - progressReporters.set(compiler, undefined); - handler(progress, category, name); - }, - result() { - handler(progress, category, name); - }, - error() { - handler(progress, category, name); - }, - /** - * @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.cache.hooks.endIdle.intercept({ - name: "ProgressPlugin", - call() { - handler(0, ""); - } - }); - interceptHook(compiler.cache.hooks.endIdle, 0.01, "cache", "end idle"); - compiler.hooks.beforeRun.intercept({ - name: "ProgressPlugin", - 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: "ProgressPlugin", - 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: "ProgressPlugin", - done() { - handler(1, ""); - } - }); - compiler.cache.hooks.shutdown.intercept({ - name: "ProgressPlugin", - done() { - handler(1, ""); - } - }); - } -} - -ProgressPlugin.defaultOptions = { - profile: false, - modulesCount: 5000, - dependenciesCount: 10000, - modules: true, - dependencies: true, - activeModules: false, - entries: true -}; - -ProgressPlugin.createDefaultHandler = createDefaultHandler; - -module.exports = ProgressPlugin; diff --git a/webpack-lib/lib/ProvidePlugin.js b/webpack-lib/lib/ProvidePlugin.js deleted file mode 100644 index 28c3ce5d590..00000000000 --- a/webpack-lib/lib/ProvidePlugin.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - 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 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 PLUGIN_NAME = "ProvidePlugin"; - -class ProvidePlugin { - /** - * @param {Record} definitions the provided identifiers - */ - constructor(definitions) { - this.definitions = definitions; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const definitions = this.definitions; - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - compilation.dependencyFactories.set( - ProvidedDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - ProvidedDependency, - new ProvidedDependency.Template() - ); - /** - * @param {JavascriptParser} parser the parser - * @param {JavascriptParserOptions} parserOptions options - * @returns {void} - */ - const handler = (parser, parserOptions) => { - for (const name of Object.keys(definitions)) { - const request = - /** @type {string[]} */ - ([]).concat(definitions[name]); - const splittedName = name.split("."); - if (splittedName.length > 0) { - for (const [i, _] of splittedName.slice(1).entries()) { - const name = splittedName.slice(0, i + 1).join("."); - 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_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 = ProvidePlugin; diff --git a/webpack-lib/lib/RawModule.js b/webpack-lib/lib/RawModule.js deleted file mode 100644 index bd02863c672..00000000000 --- a/webpack-lib/lib/RawModule.js +++ /dev/null @@ -1,164 +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 { JS_TYPES } = require("./ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("./ModuleTypeConstants"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("./Generator").SourceTypes} SourceTypes */ -/** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("./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 */ - -class RawModule extends Module { - /** - * @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.runtimeRequirements = runtimeRequirements || null; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this.identifierStr; - } - - /** - * @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); - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return /** @type {string} */ ( - requestShortener.shorten(this.readableIdentifierStr) - ); - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - cacheable: true - }; - callback(); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - const sources = new Map(); - if (this.useSourceMap || this.useSimpleSourceMap) { - sources.set( - "javascript", - new OriginalSource(this.sourceStr, this.identifier()) - ); - } else { - sources.set("javascript", new RawSource(this.sourceStr)); - } - return { sources, runtimeRequirements: this.runtimeRequirements }; - } - - /** - * @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, context); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.sourceStr); - write(this.identifierStr); - write(this.readableIdentifierStr); - write(this.runtimeRequirements); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.sourceStr = read(); - this.identifierStr = read(); - this.readableIdentifierStr = read(); - this.runtimeRequirements = read(); - - super.deserialize(context); - } -} - -makeSerializable(RawModule, "webpack/lib/RawModule"); - -module.exports = RawModule; diff --git a/webpack-lib/lib/RecordIdsPlugin.js b/webpack-lib/lib/RecordIdsPlugin.js deleted file mode 100644 index aaace61c89a..00000000000 --- a/webpack-lib/lib/RecordIdsPlugin.js +++ /dev/null @@ -1,238 +0,0 @@ -/* - 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("./Chunk")} Chunk */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Module")} Module */ - -/** - * @typedef {object} RecordsChunks - * @property {Record=} byName - * @property {Record=} bySource - * @property {number[]=} usedIds - */ - -/** - * @typedef {object} RecordsModules - * @property {Record=} byIdentifier - * @property {Record=} bySource - * @property {number[]=} usedIds - */ - -/** - * @typedef {object} Records - * @property {RecordsChunks=} chunks - * @property {RecordsModules=} modules - */ - -class RecordIdsPlugin { - /** - * @param {object} options Options object - * @param {boolean=} options.portableIds true, when ids need to be portable - */ - constructor(options) { - this.options = options || {}; - } - - /** - * @param {Compiler} compiler the Compiler - * @returns {void} - */ - apply(compiler) { - const portableIds = this.options.portableIds; - - const makePathsRelative = - identifierUtils.makePathsRelative.bindContextCache( - compiler.context, - compiler.root - ); - - /** - * @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("RecordIdsPlugin", compilation => { - compilation.hooks.recordModules.tap( - "RecordIdsPlugin", - /** - * @param {Iterable} modules the modules array - * @param {Records} records the records object - * @returns {void} - */ - (modules, records) => { - const chunkGraph = compilation.chunkGraph; - if (!records.modules) records.modules = {}; - if (!records.modules.byIdentifier) records.modules.byIdentifier = {}; - /** @type {Set} */ - 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 = Array.from(usedIds).sort(compareNumbers); - } - ); - compilation.hooks.reviveModules.tap( - "RecordIdsPlugin", - /** - * @param {Iterable} modules the modules array - * @param {Records} records the records object - * @returns {void} - */ - (modules, records) => { - if (!records.modules) return; - if (records.modules.byIdentifier) { - const chunkGraph = compilation.chunkGraph; - /** @type {Set} */ - const usedIds = new Set(); - for (const module of modules) { - 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); - } - } - if (Array.isArray(records.modules.usedIds)) { - compilation.usedModuleIds = new Set(records.modules.usedIds); - } - } - ); - - /** - * @param {Chunk} chunk the chunk - * @returns {string[]} sources of the chunk - */ - const getChunkSources = chunk => { - /** @type {string[]} */ - const sources = []; - for (const chunkGroup of chunk.groupsIterable) { - const index = chunkGroup.chunks.indexOf(chunk); - 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)}` - ); - } - } - } - } - } - return sources; - }; - - compilation.hooks.recordChunks.tap( - "RecordIdsPlugin", - /** - * @param {Iterable} 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(); - 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 = Array.from(usedIds).sort(compareNumbers); - } - ); - compilation.hooks.reviveChunks.tap( - "RecordIdsPlugin", - /** - * @param {Iterable} 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 (id === undefined) continue; - if (usedIds.has(id)) continue; - usedIds.add(id); - chunk.id = id; - chunk.ids = [id]; - } - } - 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 (Array.isArray(records.chunks.usedIds)) { - compilation.usedChunkIds = new Set(records.chunks.usedIds); - } - } - ); - }); - } -} -module.exports = RecordIdsPlugin; diff --git a/webpack-lib/lib/RequestShortener.js b/webpack-lib/lib/RequestShortener.js deleted file mode 100644 index 9ef80190fed..00000000000 --- a/webpack-lib/lib/RequestShortener.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { contextify } = require("./util/identifier"); - -class RequestShortener { - /** - * @param {string} dir the directory - * @param {object=} associatedObjectForCache an object to which the cache will be attached - */ - constructor(dir, associatedObjectForCache) { - this.contextify = contextify.bindContextCache( - dir, - associatedObjectForCache - ); - } - - /** - * @param {string | undefined | null} request the request to shorten - * @returns {string | undefined | null} the shortened request - */ - shorten(request) { - if (!request) { - return request; - } - return this.contextify(request); - } -} - -module.exports = RequestShortener; diff --git a/webpack-lib/lib/RequireJsStuffPlugin.js b/webpack-lib/lib/RequireJsStuffPlugin.js deleted file mode 100644 index c9acc6643dd..00000000000 --- a/webpack-lib/lib/RequireJsStuffPlugin.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - 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 -} = require("./ModuleTypeConstants"); -const RuntimeGlobals = require("./RuntimeGlobals"); -const ConstDependency = require("./dependencies/ConstDependency"); -const { - toConstantDependency -} = require("./javascript/JavascriptParserHelpers"); - -/** @typedef {import("../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ - -const PLUGIN_NAME = "RequireJsStuffPlugin"; - -module.exports = class RequireJsStuffPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - ConstDependency, - new ConstDependency.Template() - ); - /** - * @param {JavascriptParser} parser the parser - * @param {JavascriptParserOptions} parserOptions options - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if ( - parserOptions.requireJs === undefined || - !parserOptions.requireJs - ) { - return; - } - - parser.hooks.call - .for("require.config") - .tap(PLUGIN_NAME, toConstantDependency(parser, "undefined")); - parser.hooks.call - .for("requirejs.config") - .tap(PLUGIN_NAME, toConstantDependency(parser, "undefined")); - - parser.hooks.expression - .for("require.version") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("0.0.0")) - ); - parser.hooks.expression - .for("requirejs.onError") - .tap( - PLUGIN_NAME, - toConstantDependency( - parser, - RuntimeGlobals.uncaughtErrorHandler, - [RuntimeGlobals.uncaughtErrorHandler] - ) - ); - }; - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - } - ); - } -}; diff --git a/webpack-lib/lib/ResolverFactory.js b/webpack-lib/lib/ResolverFactory.js deleted file mode 100644 index 9651c6a73e8..00000000000 --- a/webpack-lib/lib/ResolverFactory.js +++ /dev/null @@ -1,155 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Factory = require("enhanced-resolve").ResolverFactory; -const { HookMap, SyncHook, SyncWaterfallHook } = require("tapable"); -const { - cachedCleverMerge, - removeOperations, - resolveByProperty -} = require("./util/cleverMerge"); - -/** @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("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */ -/** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */ - -/** @typedef {WebpackResolveOptions & {dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */ -/** - * @typedef {object} WithOptions - * @property {function(Partial): ResolverWithOptions} withOptions create a resolver with additional/different options - */ - -/** @typedef {Resolver & WithOptions} ResolverWithOptions */ - -// need to be hoisted on module level for caching identity -const EMPTY_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 removeOperations( - resolveByProperty(options, "byDependency", dependencyType), - // Keep the `unsafeCache` because it can be a `Proxy` - ["unsafeCache"] - ); -}; - -/** - * @typedef {object} ResolverCache - * @property {WeakMap} direct - * @property {Map} stringified - */ - -module.exports = class ResolverFactory { - constructor() { - this.hooks = Object.freeze({ - /** @type {HookMap>} */ - resolveOptions: new HookMap( - () => new SyncWaterfallHook(["resolveOptions"]) - ), - /** @type {HookMap>} */ - resolver: new HookMap( - () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"]) - ) - }); - /** @type {Map} */ - this.cache = new Map(); - } - - /** - * @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); - typedCaches.direct.set(resolveOptions, newResolver); - typedCaches.stringified.set(ident, newResolver); - return newResolver; - } - - /** - * @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"); - } - /** @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/webpack-lib/lib/RuntimeGlobals.js b/webpack-lib/lib/RuntimeGlobals.js deleted file mode 100644 index 7d201f6267a..00000000000 --- a/webpack-lib/lib/RuntimeGlobals.js +++ /dev/null @@ -1,387 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * the internal require function - */ -module.exports.require = "__webpack_require__"; - -/** - * access to properties of the internal require function/object - */ -module.exports.requireScope = "__webpack_require__.*"; - -/** - * the internal exports object - */ -module.exports.exports = "__webpack_exports__"; - -/** - * top-level this need to be the exports object - */ -module.exports.thisAsExports = "top-level-this-exports"; - -/** - * runtime need to return the exports of the last entry module - */ -module.exports.returnExportsFromRuntime = "return-exports-from-runtime"; - -/** - * the internal module object - */ -module.exports.module = "module"; - -/** - * the internal module object - */ -module.exports.moduleId = "module.id"; - -/** - * the internal module object - */ -module.exports.moduleLoaded = "module.loaded"; - -/** - * the bundle public path - */ -module.exports.publicPath = "__webpack_require__.p"; - -/** - * the module id of the entry point - */ -module.exports.entryModuleId = "__webpack_require__.s"; - -/** - * 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 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 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 exported property define getters function - */ -module.exports.definePropertyGetters = "__webpack_require__.d"; - -/** - * define compatibility on export - */ -module.exports.makeNamespaceObject = "__webpack_require__.r"; - -/** - * create a fake namespace object - */ -module.exports.createFakeNamespaceObject = "__webpack_require__.t"; - -/** - * compatibility get default export - */ -module.exports.compatGetDefaultExport = "__webpack_require__.n"; - -/** - * harmony module decorator - */ -module.exports.harmonyModuleDecorator = "__webpack_require__.hmd"; - -/** - * node.js module decorator - */ -module.exports.nodeModuleDecorator = "__webpack_require__.nmd"; - -/** - * the webpack hash - */ -module.exports.getFullHash = "__webpack_require__.h"; - -/** - * an object containing all installed WebAssembly.Instance export objects keyed by module id - */ -module.exports.wasmInstances = "__webpack_require__.w"; - -/** - * instantiate a wasm instance from module exports object, id, hash and importsObject - */ -module.exports.instantiateWasm = "__webpack_require__.v"; - -/** - * the uncaught error handler for the webpack runtime - */ -module.exports.uncaughtErrorHandler = "__webpack_require__.oe"; - -/** - * the script nonce - */ -module.exports.scriptNonce = "__webpack_require__.nc"; - -/** - * 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"; - -/** - * 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"; - -/** - * function to return webpack's Trusted Types policy - * Arguments: () => TrustedTypePolicy - */ -module.exports.getTrustedTypesPolicy = "__webpack_require__.tt"; - -/** - * a flag when a chunk has a fetch priority - */ -module.exports.hasFetchPriority = "has fetch priority"; - -/** - * the chunk name of the chunk with the runtime - */ -module.exports.chunkName = "__webpack_require__.cn"; - -/** - * the runtime id of the current runtime - */ -module.exports.runtimeId = "__webpack_require__.j"; - -/** - * the filename of the script part of the chunk - */ -module.exports.getChunkScriptFilename = "__webpack_require__.u"; - -/** - * the filename of the css part of the chunk - */ -module.exports.getChunkCssFilename = "__webpack_require__.k"; - -/** - * a flag when a module/chunk/tree has css modules - */ -module.exports.hasCssModules = "has css modules"; - -/** - * the filename of the script part of the hot update chunk - */ -module.exports.getChunkUpdateScriptFilename = "__webpack_require__.hu"; - -/** - * the filename of the css part of the hot update chunk - */ -module.exports.getChunkUpdateCssFilename = "__webpack_require__.hk"; - -/** - * startup signal from runtime - * This will be called when the runtime chunk has been loaded. - */ -module.exports.startup = "__webpack_require__.x"; - -/** - * @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)"; - -/** - * global callback functions for installing chunks - */ -module.exports.chunkCallback = "webpackChunk"; - -/** - * 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"; - -/** - * 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"; - -/** - * method to install a chunk that was loaded somehow - * Signature: ({ id, ids, modules, runtime }) => void - */ -module.exports.externalInstallChunk = "__webpack_require__.C"; - -/** - * interceptor for module executions - */ -module.exports.interceptModuleExecution = "__webpack_require__.i"; - -/** - * the global object - */ -module.exports.global = "__webpack_require__.g"; - -/** - * an object with all share scopes - */ -module.exports.shareScopeMap = "__webpack_require__.S"; - -/** - * 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"; - -/** - * The current scope when getting a module from a remote - */ -module.exports.currentRemoteGetScope = "__webpack_require__.R"; - -/** - * the filename of the HMR manifest - */ -module.exports.getUpdateManifestFilename = "__webpack_require__.hmrF"; - -/** - * 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"; - -/** - * object with all hmr module data for all modules - */ -module.exports.hmrModuleData = "__webpack_require__.hmrD"; - -/** - * array with handler functions when a module should be invalidated - */ -module.exports.hmrInvalidateModuleHandlers = "__webpack_require__.hmrI"; - -/** - * the prefix for storing state of runtime modules when hmr is enabled - */ -module.exports.hmrRuntimeStatePrefix = "__webpack_require__.hmrS"; - -/** - * the AMD define function - */ -module.exports.amdDefine = "__webpack_require__.amdD"; - -/** - * the AMD options - */ -module.exports.amdOptions = "__webpack_require__.amdO"; - -/** - * the System polyfill object - */ -module.exports.system = "__webpack_require__.System"; - -/** - * the shorthand for Object.prototype.hasOwnProperty - * using of it decreases the compiled bundle size - */ -module.exports.hasOwnProperty = "__webpack_require__.o"; - -/** - * the System.register context object - */ -module.exports.systemContext = "__webpack_require__.y"; - -/** - * the baseURI of current document - */ -module.exports.baseURI = "__webpack_require__.b"; - -/** - * a RelativeURL class when relative URLs are used - */ -module.exports.relativeUrl = "__webpack_require__.U"; - -/** - * 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"; diff --git a/webpack-lib/lib/RuntimeModule.js b/webpack-lib/lib/RuntimeModule.js deleted file mode 100644 index f4fff959ca4..00000000000 --- a/webpack-lib/lib/RuntimeModule.js +++ /dev/null @@ -1,214 +0,0 @@ -/* - 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 { RUNTIME_TYPES } = require("./ModuleSourceTypesConstants"); -const { WEBPACK_MODULE_TYPE_RUNTIME } = require("./ModuleTypeConstants"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} 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").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("./RequestShortener")} RequestShortener */ -/** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./util/Hash")} Hash */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ - -class RuntimeModule extends Module { - /** - * @param {string} name a readable name - * @param {number=} stage an optional stage - */ - constructor(name, stage = 0) { - super(WEBPACK_MODULE_TYPE_RUNTIME); - this.name = name; - this.stage = stage; - this.buildMeta = {}; - this.buildInfo = {}; - /** @type {Compilation | undefined} */ - this.compilation = undefined; - /** @type {Chunk | undefined} */ - this.chunk = undefined; - /** @type {ChunkGraph | undefined} */ - this.chunkGraph = undefined; - this.fullHash = false; - this.dependentHash = false; - /** @type {string | undefined | null} */ - this._cachedGeneratedCode = undefined; - } - - /** - * @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 {string} a unique identifier of the module - */ - identifier() { - return `webpack/runtime/${this.name}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `webpack/runtime/${this.name}`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, false); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} 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(); - } - - /** - * @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 { - if (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. - hash.update(/** @type {string} */ (this.generate())); - } else { - hash.update(/** @type {string} */ (this.getGeneratedCode())); - } - } catch (err) { - hash.update(/** @type {Error} */ (err).message); - } - super.updateHash(hash, context); - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return RUNTIME_TYPES; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - 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 - }; - } - - /** - * @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 */ - /** - * @abstract - * @returns {string | null} runtime code - */ - generate() { - const AbstractMethodError = require("./AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @returns {string | null} runtime code - */ - getGeneratedCode() { - if (this._cachedGeneratedCode) { - return this._cachedGeneratedCode; - } - return (this._cachedGeneratedCode = this.generate()); - } - - /** - * @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/webpack-lib/lib/RuntimePlugin.js b/webpack-lib/lib/RuntimePlugin.js deleted file mode 100644 index cabdffeaa60..00000000000 --- a/webpack-lib/lib/RuntimePlugin.js +++ /dev/null @@ -1,496 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("./RuntimeGlobals"); -const { getChunkFilenameTemplate } = require("./css/CssModulesPlugin"); -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 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 SystemContextRuntimeModule = require("./runtime/SystemContextRuntimeModule"); -const ShareRuntimeModule = require("./sharing/ShareRuntimeModule"); -const StringXor = require("./util/StringXor"); -const memoize = require("./util/memoize"); - -/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ - -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.systemContext, - RuntimeGlobals.onChunksLoaded -]; - -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.initializeSharing]: [RuntimeGlobals.shareScopeMap], - [RuntimeGlobals.shareScopeMap]: [RuntimeGlobals.hasOwnProperty] -}; - -class RuntimePlugin { - /** - * @param {Compiler} compiler the Compiler - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("RuntimePlugin", compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @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("RuntimePlugin", (module, set) => { - set.add(RuntimeGlobals.requireScope); - }); - compilation.hooks.runtimeRequirementInTree - .for(req) - .tap("RuntimePlugin", (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("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { - for (const dep of deps) set.add(dep); - }); - } - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.definePropertyGetters) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule( - chunk, - new DefinePropertyGettersRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.makeNamespaceObject) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule( - chunk, - new MakeNamespaceObjectRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.createFakeNamespaceObject) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule( - chunk, - new CreateFakeNamespaceObjectRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hasOwnProperty) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule( - chunk, - new HasOwnPropertyRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.compatGetDefaultExport) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule( - chunk, - new CompatGetDefaultExportRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.runtimeId) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule(chunk, new RuntimeIdRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.publicPath) - .tap("RuntimePlugin", (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") set.add(RuntimeGlobals.global); - compilation.addRuntimeModule(chunk, module); - } else { - const module = new PublicPathRuntimeModule(publicPath); - - if ( - typeof publicPath !== "string" || - /\[(full)?hash\]/.test(publicPath) - ) { - module.fullHash = true; - } - - compilation.addRuntimeModule(chunk, module); - } - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.global) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule(chunk, new GlobalRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.asyncModule) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule(chunk, new AsyncModuleRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.systemContext) - .tap("RuntimePlugin", 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("RuntimePlugin", (chunk, set, { chunkGraph }) => { - if ( - typeof compilation.outputOptions.chunkFilename === "string" && - /\[(full)?hash(:\d+)?\]/.test( - compilation.outputOptions.chunkFilename - ) - ) { - set.add(RuntimeGlobals.getFullHash); - } - compilation.addRuntimeModule( - chunk, - new GetChunkFilenameRuntimeModule( - "javascript", - "javascript", - RuntimeGlobals.getChunkScriptFilename, - chunk => - getJavascriptModulesPlugin().chunkHasJs(chunk, chunkGraph) && - /** @type {TemplatePath} */ ( - chunk.filenameTemplate || - (chunk.canBeInitial() - ? compilation.outputOptions.filename - : compilation.outputOptions.chunkFilename) - ), - false - ) - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.getChunkCssFilename) - .tap("RuntimePlugin", (chunk, set, { chunkGraph }) => { - if ( - typeof compilation.outputOptions.cssChunkFilename === "string" && - /\[(full)?hash(:\d+)?\]/.test( - compilation.outputOptions.cssChunkFilename - ) - ) { - set.add(RuntimeGlobals.getFullHash); - } - compilation.addRuntimeModule( - chunk, - new GetChunkFilenameRuntimeModule( - "css", - "css", - RuntimeGlobals.getChunkCssFilename, - chunk => - getCssModulesPlugin().chunkHasCss(chunk, chunkGraph) && - getChunkFilenameTemplate(chunk, compilation.outputOptions), - set.has(RuntimeGlobals.hmrDownloadUpdateHandlers) - ) - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.getChunkUpdateScriptFilename) - .tap("RuntimePlugin", (chunk, set) => { - if ( - /\[(full)?hash(:\d+)?\]/.test( - /** @type {NonNullable} */ - (compilation.outputOptions.hotUpdateChunkFilename) - ) - ) - set.add(RuntimeGlobals.getFullHash); - compilation.addRuntimeModule( - chunk, - new GetChunkFilenameRuntimeModule( - "javascript", - "javascript update", - RuntimeGlobals.getChunkUpdateScriptFilename, - c => - /** @type {NonNullable} */ - (compilation.outputOptions.hotUpdateChunkFilename), - true - ) - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.getUpdateManifestFilename) - .tap("RuntimePlugin", (chunk, set) => { - if ( - /\[(full)?hash(:\d+)?\]/.test( - /** @type {NonNullable} */ - (compilation.outputOptions.hotUpdateMainFilename) - ) - ) { - set.add(RuntimeGlobals.getFullHash); - } - compilation.addRuntimeModule( - chunk, - new GetMainFilenameRuntimeModule( - "update manifest", - RuntimeGlobals.getUpdateManifestFilename, - /** @type {NonNullable} */ - (compilation.outputOptions.hotUpdateMainFilename) - ) - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunk) - .tap("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { - set.add(RuntimeGlobals.ensureChunkHandlers); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.shareScopeMap) - .tap("RuntimePlugin", (chunk, set) => { - compilation.addRuntimeModule(chunk, new ShareRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.loadScript) - .tap("RuntimePlugin", (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("RuntimePlugin", (chunk, set) => { - if (compilation.outputOptions.trustedTypes) { - set.add(RuntimeGlobals.getTrustedTypesPolicy); - } - compilation.addRuntimeModule(chunk, new CreateScriptRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.createScriptUrl) - .tap("RuntimePlugin", (chunk, set) => { - if (compilation.outputOptions.trustedTypes) { - set.add(RuntimeGlobals.getTrustedTypesPolicy); - } - compilation.addRuntimeModule( - chunk, - new CreateScriptUrlRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.getTrustedTypesPolicy) - .tap("RuntimePlugin", (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new GetTrustedTypesPolicyRuntimeModule(set) - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.relativeUrl) - .tap("RuntimePlugin", (chunk, set) => { - compilation.addRuntimeModule(chunk, new RelativeUrlRuntimeModule()); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.onChunksLoaded) - .tap("RuntimePlugin", (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new OnChunksLoadedRuntimeModule() - ); - return true; - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.baseURI) - .tap("RuntimePlugin", chunk => { - if (isChunkLoadingDisabledForChunk(chunk)) { - compilation.addRuntimeModule(chunk, new BaseUriRuntimeModule()); - return true; - } - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.scriptNonce) - .tap("RuntimePlugin", chunk => { - compilation.addRuntimeModule(chunk, new NonceRuntimeModule()); - return true; - }); - // TODO webpack 6: remove CompatRuntimeModule - compilation.hooks.additionalTreeRuntimeRequirements.tap( - "RuntimePlugin", - (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( - "RuntimePlugin", - (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/webpack-lib/lib/RuntimeTemplate.js b/webpack-lib/lib/RuntimeTemplate.js deleted file mode 100644 index 4f79d7137a6..00000000000 --- a/webpack-lib/lib/RuntimeTemplate.js +++ /dev/null @@ -1,1109 +0,0 @@ -/* - 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 { equals } = require("./util/ArrayHelpers"); -const compileBooleanMatcher = require("./util/compileBooleanMatcher"); -const propertyAccess = require("./util/propertyAccess"); -const { forEachRuntime, subtractRuntime } = require("./util/runtime"); - -/** @typedef {import("../declarations/WebpackOptions").Environment} Environment */ -/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("./CodeGenerationResults").CodeGenerationResult} CodeGenerationResult */ -/** @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 */ - -/** - * @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 && - Array.from(connection.explanations).join(", ")) || - "" - }` -).join("")}`; - -/** - * @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 { - /** - * @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( - /** @type {NonNullable} */ - (outputOptions.hashDigestLength) - ); - } - - isIIFE() { - return this.outputOptions.iife; - } - - isModule() { - return this.outputOptions.module; - } - - isNeutralPlatform() { - return ( - !this.outputOptions.environment.document && - !this.compilation.compiler.platform.node - ); - } - - supportsConst() { - return this.outputOptions.environment.const; - } - - supportsArrowFunction() { - return this.outputOptions.environment.arrowFunction; - } - - supportsAsyncFunction() { - return this.outputOptions.environment.asyncFunction; - } - - supportsOptionalChaining() { - return this.outputOptions.environment.optionalChaining; - } - - 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; - } - - /** - * @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}; }`; - } - - /** - * @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}`; - } - - /** - * @param {Array} 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("")}\``; - } - - /** - * @param {Array} 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; - } - - /** - * @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 {string} empty function code - */ - emptyFunction() { - return this.supportsArrowFunction() ? "x => {}" : "function() {}"; - } - - /** - * @param {string[]} items items - * @param {string} value value - * @returns {string} destructure array code - */ - destructureArray(items, value) { - return this.supportsDestructuring() - ? `var [${items.join(", ")}] = ${value};` - : Template.asString( - items.map((item, i) => `var ${item} = ${value}[${i}];`) - ); - } - - /** - * @param {string[]} items items - * @param {string} value value - * @returns {string} destructure object code - */ - destructureObject(items, value) { - return this.supportsDestructuring() - ? `var {${items.join(", ")}} = ${value};` - : Template.asString( - items.map(item => `var ${item} = ${value}${propertyAccess([item])};`) - ); - } - - /** - * @param {string} args arguments - * @param {string} body body - * @returns {string} IIFE code - */ - iife(args, body) { - return `(${this.basicFunction(args, body)})()`; - } - - /** - * @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});`; - } - - /** - * Add a comment - * @param {object} options Information content of the comment - * @param {string=} options.request request string used originally - * @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 }) { - let content; - if (this.outputOptions.pathinfo) { - content = [message, request, chunkName, chunkReason] - .filter(Boolean) - .map(item => this.requestShortener.shorten(item)) - .join(" | "); - } else { - content = [message, chunkName, chunkReason] - .filter(Boolean) - .map(item => this.requestShortener.shorten(item)) - .join(" | "); - } - if (!content) return ""; - if (this.outputOptions.pathinfo) { - return `${Template.toComment(content)} `; - } - return `${Template.toNormalComment(content)} `; - } - - /** - * @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 `var e = new Error(${JSON.stringify( - err - )}); e.code = 'MODULE_NOT_FOUND'; throw e;`; - } - - /** - * @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 } - )} }`; - } - - /** - * @param {object} options generation options - * @param {string=} options.request request string used originally - * @returns {string} generated error IIFE - */ - missingModule({ request }) { - return `Object(${this.throwMissingModuleErrorFunction({ request })}())`; - } - - /** - * @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`; - } - - /** - * @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 - })})`; - } - - /** - * @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 = `var 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); - } - } - - /** - * @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 - }); - } - const moduleId = chunkGraph.getModuleId(module); - if (moduleId === null) { - if (weak) { - return "null /* weak dependency, without id */"; - } - throw new Error( - `RuntimeTemplate.moduleId(): ${noModuleIdErrorMessage( - module, - chunkGraph - )}` - ); - } - return `${this.comment({ request })}${JSON.stringify(moduleId)}`; - } - - /** - * @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 - }); - } - 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 - })})`; - } - - /** - * @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, - chunkGraph, - request, - weak, - runtimeRequirements - }); - } - - /** - * @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, - chunkGraph, - request, - weak - }); - 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)`; - } - } - - /** - * @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 {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements - * @returns {string} the promise expression - */ - moduleNamespacePromise({ - chunkGraph, - block, - module, - request, - message, - strict, - weak, - runtimeRequirements - }) { - if (!module) { - return this.missingModulePromise({ - 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: "promise" - }); - } - throw new Error( - `RuntimeTemplate.moduleNamespacePromise(): ${noModuleIdErrorMessage( - module, - chunkGraph - )}` - ); - } - const promise = this.blockPromise({ - chunkGraph, - block, - message, - runtimeRequirements - }); - - let appending; - let idExpr = JSON.stringify(chunkGraph.getModuleId(module)); - const comment = this.comment({ - request - }); - let header = ""; - if (weak) { - if (idExpr.length > 8) { - // 'var x="nnnnnn";x,"+x+",x' vs '"nnnnnn",nnnnnn,"nnnnnn"' - header += `var id = ${idExpr}; `; - idExpr = "id"; - } - runtimeRequirements.add(RuntimeGlobals.moduleFactories); - header += `if(!${ - RuntimeGlobals.moduleFactories - }[${idExpr}]) { ${this.weakError({ - module, - chunkGraph, - request, - idExpr, - type: "statements" - })} } `; - } - const moduleIdExpr = this.moduleId({ - module, - chunkGraph, - request, - weak - }); - const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict); - 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 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()"}${appending}`; - } - - /** - * @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( - Array.from(positiveRuntimeIds), - Array.from(negativeRuntimeIds) - )(RuntimeGlobals.runtimeId); - } - - /** - * @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 {ChunkGraph} options.chunkGraph the chunk graph - * @param {string} options.request the request that should be printed as comment - * @param {string} options.importVar name of the import variable - * @param {Module} options.originModule module in which the statement is emitted - * @param {boolean=} options.weak true, if this is a weak dependency - * @param {RuntimeRequirements} options.runtimeRequirements if set, will be filled with runtime requirements - * @returns {[string, string]} the import statement and the compat statement - */ - importStatement({ - update, - module, - chunkGraph, - request, - importVar, - originModule, - weak, - runtimeRequirements - }) { - if (!module) { - 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, - chunkGraph, - request, - weak - }); - const optDeclaration = update ? "" : "var "; - - const exportsType = module.getExportsType( - chunkGraph.moduleGraph, - /** @type {BuildMeta} */ - (originModule.buildMeta).strictHarmonyModule - ); - runtimeRequirements.add(RuntimeGlobals.require); - const 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 [importContent, ""]; - } - - /** - * @param {object} options options - * @param {ModuleGraph} options.moduleGraph the module 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} 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 - * @returns {string} expression - */ - exportFromImport({ - moduleGraph, - module, - request, - exportName, - originModule, - asiSafe, - isCall, - callContext, - defaultInterop, - importVar, - initFragments, - runtime, - runtimeRequirements - }) { - if (!module) { - return this.missingModule({ - request - }); - } - if (!Array.isArray(exportName)) { - exportName = exportName ? [exportName] : []; - } - const exportsType = module.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ - (originModule.buildMeta).strictHarmonyModule - ); - - if (defaultInterop) { - if (exportName.length > 0 && exportName[0] === "default") { - 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) { - if (exportsType === "default-only") { - return `/* non-default import from non-esm module */undefined${propertyAccess( - exportName, - 1 - )}`; - } else if ( - exportsType !== "namespace" && - exportName[0] === "__esModule" - ) { - return "/* __esModule */true"; - } - } else if ( - exportsType === "default-only" || - exportsType === "default-with-named" - ) { - runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); - initFragments.push( - new InitFragment( - `var ${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 (exportName.length > 0) { - const exportsInfo = moduleGraph.getExportsInfo(module); - const used = exportsInfo.getUsedName(exportName, runtime); - if (!used) { - const comment = Template.toNormalComment( - `unused export ${propertyAccess(exportName)}` - ); - return `${comment} undefined`; - } - const comment = equals(used, exportName) - ? "" - : `${Template.toNormalComment(propertyAccess(exportName))} `; - const access = `${importVar}${comment}${propertyAccess(used)}`; - if (isCall && callContext === false) { - return asiSafe - ? `(0,${access})` - : asiSafe === false - ? `;(0,${access})` - : `/*#__PURE__*/Object(${access})`; - } - return access; - } - return importVar; - } - - /** - * @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 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 - }); - if (chunks.length === 1) { - const chunkId = JSON.stringify(chunks[0].id); - 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) { - runtimeRequirements.add(RuntimeGlobals.ensureChunk); - - const fetchPriority = chunkGroup.options.fetchPriority; - - if (fetchPriority) { - runtimeRequirements.add(RuntimeGlobals.hasFetchPriority); - } - - /** - * @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(", ")}])`; - } - return `Promise.resolve(${comment.trim()})`; - } - - /** - * @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)})` - ); - } - - /** - * @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); - } - - /** - * @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/webpack-lib/lib/SelfModuleFactory.js b/webpack-lib/lib/SelfModuleFactory.js deleted file mode 100644 index 3a10333e20c..00000000000 --- a/webpack-lib/lib/SelfModuleFactory.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./ModuleGraph")} ModuleGraph */ - -class SelfModuleFactory { - /** - * @param {ModuleGraph} moduleGraph module graph - */ - constructor(moduleGraph) { - this.moduleGraph = moduleGraph; - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const module = this.moduleGraph.getParentModule(data.dependencies[0]); - callback(null, { - module - }); - } -} - -module.exports = SelfModuleFactory; diff --git a/webpack-lib/lib/SingleEntryPlugin.js b/webpack-lib/lib/SingleEntryPlugin.js deleted file mode 100644 index 65791735c79..00000000000 --- a/webpack-lib/lib/SingleEntryPlugin.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -module.exports = require("./EntryPlugin"); diff --git a/webpack-lib/lib/SizeFormatHelpers.js b/webpack-lib/lib/SizeFormatHelpers.js deleted file mode 100644 index 4c6a748f0eb..00000000000 --- a/webpack-lib/lib/SizeFormatHelpers.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -/** - * @param {number} size the size in bytes - * @returns {string} the formatted size - */ -module.exports.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 `${Number((size / 1024 ** index).toPrecision(3))} ${abbreviations[index]}`; -}; diff --git a/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js b/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js deleted file mode 100644 index e7d722e12a8..00000000000 --- a/webpack-lib/lib/SourceMapDevToolModuleOptionsPlugin.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); - -/** @typedef {import("../declarations/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ -/** @typedef {import("./Compilation")} Compilation */ - -class SourceMapDevToolModuleOptionsPlugin { - /** - * @param {SourceMapDevToolPluginOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * @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; - } - ); - compilation.hooks.runtimeModule.tap( - "SourceMapDevToolModuleOptionsPlugin", - module => { - module.useSourceMap = true; - } - ); - } else { - compilation.hooks.buildModule.tap( - "SourceMapDevToolModuleOptionsPlugin", - module => { - module.useSimpleSourceMap = true; - } - ); - compilation.hooks.runtimeModule.tap( - "SourceMapDevToolModuleOptionsPlugin", - module => { - module.useSimpleSourceMap = true; - } - ); - } - JavascriptModulesPlugin.getCompilationHooks(compilation).useSourceMap.tap( - "SourceMapDevToolModuleOptionsPlugin", - () => true - ); - } -} - -module.exports = SourceMapDevToolModuleOptionsPlugin; diff --git a/webpack-lib/lib/SourceMapDevToolPlugin.js b/webpack-lib/lib/SourceMapDevToolPlugin.js deleted file mode 100644 index ca16afd0f8b..00000000000 --- a/webpack-lib/lib/SourceMapDevToolPlugin.js +++ /dev/null @@ -1,618 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -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 createSchemaValidation = require("./util/create-schema-validation"); -const createHash = require("./util/createHash"); -const { relative, dirname } = 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/plugins/SourceMapDevToolPlugin").SourceMapDevToolPluginOptions} SourceMapDevToolPluginOptions */ -/** @typedef {import("./Cache").Etag} Etag */ -/** @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").SourceMap} SourceMap */ -/** @typedef {import("./TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("./util/Hash")} Hash */ -/** @typedef {import("./util/createHash").Algorithm} Algorithm */ -/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */ - -const validate = createSchemaValidation( - require("../schemas/plugins/SourceMapDevToolPlugin.check.js"), - () => require("../schemas/plugins/SourceMapDevToolPlugin.json"), - { - name: "SourceMap DevTool Plugin", - baseDataPath: "options" - } -); -/** - * @typedef {object} SourceMapTask - * @property {Source} asset - * @property {AssetInfo} assetInfo - * @property {(string | Module)[]} modules - * @property {string} source - * @property {string} file - * @property {SourceMap} sourceMap - * @property {ItemCacheFacade} cacheItem cache item - */ - -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; -}; - -/** - * Escapes regular expression metacharacters - * @param {string} str String to quote - * @returns {string} Escaped string - */ -const quoteMeta = str => str.replace(METACHARACTERS_REGEXP, "\\$&"); - -/** - * 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 - * @returns {SourceMapTask | undefined} created task instance or `undefined` - */ -const getTaskForFile = ( - file, - asset, - assetInfo, - options, - compilation, - cacheItem -) => { - let source; - /** @type {SourceMap} */ - let sourceMap; - /** - * Check if asset can build source map - */ - if (asset.sourceAndMap) { - const sourceAndMap = asset.sourceAndMap(options); - sourceMap = /** @type {SourceMap} */ (sourceAndMap.map); - source = sourceAndMap.source; - } else { - sourceMap = /** @type {SourceMap} */ (asset.map(options)); - source = asset.source(); - } - if (!sourceMap || typeof source !== "string") return; - const context = /** @type {string} */ (compilation.options.context); - const root = compilation.compiler.root; - const cachedAbsolutify = makePathsAbsolute.bindContextCache(context, root); - const modules = sourceMap.sources.map(source => { - if (!source.startsWith("webpack://")) return source; - source = cachedAbsolutify(source.slice(10)); - const module = compilation.findModule(source); - return module || source; - }); - - return { - file, - asset, - source, - assetInfo, - sourceMap, - modules, - cacheItem - }; -}; - -class SourceMapDevToolPlugin { - /** - * @param {SourceMapDevToolPluginOptions} [options] options object - * @throws {Error} throws error, if got more than 1 arguments - */ - constructor(options = {}) { - validate(options); - - this.sourceMapFilename = /** @type {string | false} */ (options.filename); - /** @type {false | TemplatePath}} */ - this.sourceMappingURLComment = - options.append === false - ? false - : // eslint-disable-next-line no-useless-concat - options.append || "\n//# source" + "MappingURL=[url]"; - /** @type {string | Function} */ - this.moduleFilenameTemplate = - options.moduleFilenameTemplate || "webpack://[namespace]/[resourcePath]"; - /** @type {string | Function} */ - this.fallbackModuleFilenameTemplate = - options.fallbackModuleFilenameTemplate || - "webpack://[namespace]/[resourcePath]?[hash]"; - /** @type {string} */ - this.namespace = options.namespace || ""; - /** @type {SourceMapDevToolPluginOptions} */ - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler compiler instance - * @returns {void} - */ - apply(compiler) { - const outputFs = /** @type {OutputFileSystem} */ ( - compiler.outputFileSystem - ); - const sourceMapFilename = this.sourceMapFilename; - const sourceMappingURLComment = this.sourceMappingURLComment; - const moduleFilenameTemplate = this.moduleFilenameTemplate; - const namespace = this.namespace; - const fallbackModuleFilenameTemplate = this.fallbackModuleFilenameTemplate; - const requestShortener = compiler.requestShortener; - const options = this.options; - options.test = options.test || CSS_AND_JS_MODULE_EXTENSIONS_REGEXP; - - const matchObject = ModuleFilenameHelpers.matchObject.bind( - undefined, - options - ); - - compiler.hooks.compilation.tap("SourceMapDevToolPlugin", compilation => { - new SourceMapDevToolModuleOptionsPlugin(options).apply(compilation); - - compilation.hooks.processAssets.tapAsync( - { - name: "SourceMapDevToolPlugin", - stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, - additionalAssets: true - }, - (assets, callback) => { - const chunkGraph = compilation.chunkGraph; - const cache = compilation.getCache("SourceMapDevToolPlugin"); - /** @type {Map} */ - const moduleToSourceNameMapping = new Map(); - /** - * @type {Function} - * @returns {void} - */ - const reportProgress = - ProgressPlugin.getReporter(compilation.compiler) || (() => {}); - - /** @type {Map} */ - const fileToChunk = new Map(); - for (const chunk of compilation.chunks) { - for (const file of chunk.files) { - 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); - /** @type {SourceMapTask[]} */ - const tasks = []; - let fileIndex = 0; - - asyncLib.each( - files, - (file, callback) => { - const asset = - /** @type {Readonly} */ - (compilation.getAsset(file)); - if (asset.info.related && asset.info.related.sourceMap) { - fileIndex++; - return callback(); - } - - const chunk = fileToChunk.get(file); - const sourceMapNamespace = compilation.getPath(this.namespace, { - chunk - }); - - const cacheItem = cache.getItemCache( - file, - 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) { - 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(); - } - - reportProgress( - (0.5 * fileIndex) / files.length, - file, - "generate SourceMap" - ); - - /** @type {SourceMapTask | undefined} */ - const task = getTaskForFile( - file, - asset.source, - asset.info, - { - module: options.module, - columns: options.columns - }, - compilation, - cacheItem - ); - - if (task) { - const modules = task.modules; - - 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(); - }); - }, - 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 {Array} - */ - 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; - }); - - // 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); - } - - let taskIndex = 0; - - asyncLib.each( - tasks, - (task, callback) => { - const assets = Object.create(null); - 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 = modules.map(m => - moduleToSourceNameMapping.get(m) - ); - sourceMap.sources = /** @type {string[]} */ (moduleFilenames); - if (options.noSources) { - sourceMap.sourcesContent = undefined; - } - sourceMap.sourceRoot = options.sourceRoot || ""; - sourceMap.file = file; - const usesContentHash = - sourceMapFilename && - CONTENT_HASH_DETECT_REGEXP.test(sourceMapFilename); - - resetRegexpState(CONTENT_HASH_DETECT_REGEXP); - - // 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); - sourceMap.file = sourceMap.file.replace( - new RegExp(pattern, "g"), - m => "x".repeat(m.length) - ); - } - - /** @type {false | TemplatePath} */ - 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*/" - ); - } - - if (options.debugIds) { - const debugId = generateDebugId(source, sourceMap.file); - sourceMap.debugId = debugId; - currentSourceMappingURLComment = `\n//# debugId=${debugId}${currentSourceMappingURLComment}`; - } - - const sourceMapString = JSON.stringify(sourceMap); - if (sourceMapFilename) { - const filename = file; - const sourceMapContentHash = - /** @type {string} */ - ( - usesContentHash && - createHash( - /** @type {Algorithm} */ - (compilation.outputOptions.hashFunction) - ) - .update(sourceMapString) - .digest("hex") - ); - 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 - }) - ); - } - const assetInfo = { - related: { sourceMap: sourceMapFile } - }; - 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(sourceMapString); - 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( - "SourceMapDevToolPlugin: append can't be false when no filename is provided" - ); - } - if (typeof currentSourceMappingURLComment === "function") { - throw new Error( - "SourceMapDevToolPlugin: append can't be a function when no filename is provided" - ); - } - /** - * 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,${Buffer.from( - sourceMapString, - "utf-8" - ).toString("base64")}` - ) - ); - 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); - } - ); - } - ); - } - ); - }); - } -} - -module.exports = SourceMapDevToolPlugin; diff --git a/webpack-lib/lib/Stats.js b/webpack-lib/lib/Stats.js deleted file mode 100644 index 22a36632a97..00000000000 --- a/webpack-lib/lib/Stats.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ -/** @typedef {import("./Compilation")} Compilation */ -/** @typedef {import("./Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ - -class Stats { - /** - * @param {Compilation} compilation webpack compilation - */ - constructor(compilation) { - this.compilation = compilation; - } - - get hash() { - return this.compilation.hash; - } - - get startTime() { - return this.compilation.startTime; - } - - get endTime() { - return this.compilation.endTime; - } - - /** - * @returns {boolean} true if the compilation had a warning - */ - hasWarnings() { - return ( - this.compilation.getWarnings().length > 0 || - this.compilation.children.some(child => child.getStats().hasWarnings()) - ); - } - - /** - * @returns {boolean} true if the compilation encountered an error - */ - hasErrors() { - return ( - this.compilation.errors.length > 0 || - this.compilation.children.some(child => child.getStats().hasErrors()) - ); - } - - /** - * @param {(string | boolean | StatsOptions)=} options stats options - * @returns {StatsCompilation} json output - */ - toJson(options) { - const normalizedOptions = this.compilation.createStatsOptions(options, { - forToString: false - }); - - const statsFactory = this.compilation.createStatsFactory(normalizedOptions); - - return statsFactory.create("compilation", this.compilation, { - compilation: this.compilation - }); - } - - /** - * @param {(string | boolean | StatsOptions)=} options stats options - * @returns {string} string output - */ - toString(options) { - const normalizedOptions = this.compilation.createStatsOptions(options, { - forToString: true - }); - - const statsFactory = this.compilation.createStatsFactory(normalizedOptions); - const statsPrinter = this.compilation.createStatsPrinter(normalizedOptions); - - const data = statsFactory.create("compilation", this.compilation, { - compilation: this.compilation - }); - const result = statsPrinter.print("compilation", data); - return result === undefined ? "" : result; - } -} - -module.exports = Stats; diff --git a/webpack-lib/lib/Template.js b/webpack-lib/lib/Template.js deleted file mode 100644 index 3b95cfc35b5..00000000000 --- a/webpack-lib/lib/Template.js +++ /dev/null @@ -1,415 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"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("../declarations/WebpackOptions").Output} 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("./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; -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 COMMENT_END_REGEX = /\*\//g; -const PATH_NAME_NORMALIZE_REPLACE_REGEX = /[^a-zA-Z0-9_!§$()=\-^°]+/g; -const MATCH_PADDED_HYPHENS_REPLACE_REGEX = /^-|-$/g; - -/** - * @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 */ - -/** - * @typedef {object} RenderManifestEntryTemplated - * @property {function(): Source} render - * @property {TemplatePath} filenameTemplate - * @property {PathData=} pathOptions - * @property {AssetInfo=} info - * @property {string} identifier - * @property {string=} hash - * @property {boolean=} auxiliary - */ - -/** - * @typedef {object} RenderManifestEntryStatic - * @property {function(): Source} render - * @property {string} filename - * @property {AssetInfo} info - * @property {string} identifier - * @property {string=} hash - * @property {boolean=} auxiliary - */ - -/** - * @typedef {object} HasId - * @property {number | string} id - */ - -/** - * @typedef {function(Module, number): boolean} ModuleFilterPredicate - */ - -class Template { - /** - * @param {Function} fn a runtime function (.runtime.js) "template" - * @returns {string} the updated and normalized function string - */ - static getFunctionContent(fn) { - return fn - .toString() - .replace(FUNCTION_CONTENT_REGEX, "") - .replace(INDENT_MULTILINE_REGEX, "") - .replace(LINE_SEPARATOR_REGEX, "\n"); - } - - /** - * @param {string} str the string converted to identifier - * @returns {string} created identifier - */ - static toIdentifier(str) { - if (typeof str !== "string") return ""; - return str - .replace(IDENTIFIER_NAME_REPLACE_REGEX, "_$1") - .replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_"); - } - - /** - * @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, "* /")} */`; - } - - /** - * @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, "* /")} */`; - } - - /** - * @param {string} str string path to be normalized - * @returns {string} normalized bundle-safe path - */ - static toPath(str) { - if (typeof str !== "string") return ""; - return str - .replace(PATH_NAME_NORMALIZE_REPLACE_REGEX, "-") - .replace(MATCH_PADDED_HYPHENS_REPLACE_REGEX, ""); - } - - // map number to a single character a-z, A-Z or multiple characters if number is too big - /** - * @param {number} n number to convert to ident - * @returns {string} returns single character ident - */ - 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) { - return String.fromCharCode(START_UPPERCASE_ALPHABET_CODE + n); - } - - if (n === DELTA_A_TO_Z) return "_"; - return "$"; - } - - /** - * @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) - ) - ); - } - - // 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[]} s string to convert to identity - * @returns {string} converted identity - */ - 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[]} s string to create prefix for - * @param {string} prefix prefix to compose - * @returns {string} returns new prefix string - */ - 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`); - } - - /** - * @param {string|string[]} str string or string collection - * @returns {string} returns a single string from array - */ - static asString(str) { - if (Array.isArray(str)) { - return str.join("\n"); - } - return str; - } - - /** - * @typedef {object} WithId - * @property {string|number} id - */ - - /** - * @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) { - let maxId = -Infinity; - let minId = Infinity; - for (const module of modules) { - const moduleId = module.id; - if (typeof moduleId !== "number") return false; - if (maxId < moduleId) maxId = moduleId; - if (minId > moduleId) minId = moduleId; - } - if (minId < 16 + String(minId).length) { - // add minId x ',' instead of 'Array(minId).concat(…)' - minId = 0; - } - // 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 {ChunkRenderContext} renderContext render context - * @param {Module[]} modules modules to render (should be ordered by identifier) - * @param {function(Module): Source | null} renderModule function to render a module - * @param {string=} prefix applying prefix strings - * @returns {Source | null} rendered chunk modules in a Source object or null if no modules - */ - static renderChunkModules(renderContext, modules, renderModule, prefix = "") { - const { chunkGraph } = renderContext; - const source = new ConcatSource(); - if (modules.length === 0) { - return null; - } - /** @type {{id: string|number, source: Source|string}[]} */ - const allModules = modules.map(module => ({ - id: /** @type {ModuleId} */ (chunkGraph.getModuleId(module)), - source: renderModule(module) || "false" - })); - const bounds = Template.getModulesArrayBounds(allModules); - if (bounds) { - // Render a spare array - 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 (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(")"); - } - } else { - // Render an object - source.add("{\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)}:\n`); - source.add(module.source); - } - source.add(`\n\n${prefix}}`); - } - return source; - } - - /** - * @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; - 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, - 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; - } - - /** - * @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_START_CHARS = - NUMBER_OF_IDENTIFIER_START_CHARS; -module.exports.NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS = - NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS; diff --git a/webpack-lib/lib/TemplatedPathPlugin.js b/webpack-lib/lib/TemplatedPathPlugin.js deleted file mode 100644 index e7cc5b9442a..00000000000 --- a/webpack-lib/lib/TemplatedPathPlugin.js +++ /dev/null @@ -1,395 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Jason Anderson @diurnalist -*/ - -"use strict"; - -const mime = require("mime-types"); -const { basename, extname } = require("path"); -const util = require("util"); -const Chunk = require("./Chunk"); -const Module = require("./Module"); -const { parseResource } = require("./util/identifier"); - -/** @typedef {import("./ChunkGraph")} ChunkGraph */ -/** @typedef {import("./ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("./Compilation").PathData} PathData */ -/** @typedef {import("./Compiler")} Compiler */ - -const REGEXP = /\[\\*([\w:]+)\\*\]/gi; - -/** - * @param {string | number} id id - * @returns {string | number} result - */ -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-zA-Z0-9_-])+/g, "_"); -}; - -/** - * @callback ReplacerFunction - * @param {string} match - * @param {string | undefined} arg - * @param {string} input - */ - -/** - * @param {ReplacerFunction} replacer replacer - * @param {((arg0: number) => string) | undefined} handler handler - * @param {AssetInfo | undefined} assetInfo asset info - * @param {string} hashName hash name - * @returns {ReplacerFunction} hash replacer function - */ -const hashLength = (replacer, handler, assetInfo, hashName) => { - /** @type {ReplacerFunction} */ - const fn = (match, arg, input) => { - 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; -}; - -/** @typedef {(match: string, arg?: string, input?: string) => string} 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 ""; - } - - return `${value}`; - }; - - return fn; -}; - -const deprecationCache = new Map(); -const deprecatedFunction = (() => () => {})(); -/** - * @param {Function} fn function - * @param {string} message message - * @param {string} code code - * @returns {function(...any[]): void} 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 (...args) => { - d(); - return fn(...args); - }; -}; - -/** @typedef {string | function(PathData, AssetInfo=): string} TemplatePath */ - -/** - * @param {TemplatePath} path the raw path - * @param {PathData} data context data - * @param {AssetInfo | undefined} assetInfo extra info about the asset (will be written to) - * @returns {string} the interpolated path - */ -const replacePathVariables = (path, data, assetInfo) => { - 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 (typeof data.filename === "string") { - // check that filename is data uri - const match = data.filename.match(/^data:([^;,]+)/); - if (match) { - const ext = mime.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; - - replacements.set("file", emptyReplacer); - replacements.set("query", emptyReplacer); - replacements.set("fragment", emptyReplacer); - replacements.set("path", emptyReplacer); - replacements.set("base", baseReplacer); - replacements.set("name", baseReplacer); - replacements.set("ext", replacer(ext ? `.${ext}` : "", true)); - // Legacy - 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); - - replacements.set("file", replacer(file)); - replacements.set("query", replacer(query, true)); - replacements.set("fragment", replacer(fragment, true)); - replacements.set("path", replacer(path, true)); - replacements.set("base", replacer(base)); - replacements.set("name", replacer(name)); - replacements.set("ext", replacer(ext, true)); - // Legacy - 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) { - const hashReplacer = hashLength( - replacer(data.hash), - data.hashWithLength, - assetInfo, - "fullhash" - ); - - replacements.set("fullhash", hashReplacer); - - // Legacy - 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" - ) - ); - } - - // 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; - - const contentHashType = data.contentHashType; - - const idReplacer = replacer(chunk.id); - const nameReplacer = replacer(chunk.name || chunk.id); - const chunkhashReplacer = hashLength( - replacer(chunk instanceof Chunk ? chunk.renderedHash : chunk.hash), - "hashWithLength" in chunk ? chunk.hashWithLength : undefined, - assetInfo, - "chunkhash" - ); - const contenthashReplacer = 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" - ); - - replacements.set("id", idReplacer); - replacements.set("name", nameReplacer); - replacements.set("chunkhash", chunkhashReplacer); - replacements.set("contenthash", contenthashReplacer); - } - - // 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 idReplacer = replacer(() => - prepareId( - module instanceof Module - ? /** @type {ModuleId} */ - (/** @type {ChunkGraph} */ (chunkGraph).getModuleId(module)) - : module.id - ) - ); - const moduleHashReplacer = hashLength( - replacer(() => - module instanceof Module - ? /** @type {ChunkGraph} */ - (chunkGraph).getRenderedModuleHash(module, data.runtime) - : module.hash - ), - "hashWithLength" in module ? module.hashWithLength : undefined, - assetInfo, - "modulehash" - ); - const contentHashReplacer = hashLength( - replacer(/** @type {string} */ (data.contentHash)), - undefined, - assetInfo, - "contenthash" - ); - - replacements.set("id", idReplacer); - replacements.set("modulehash", moduleHashReplacer); - replacements.set("contenthash", contentHashReplacer); - replacements.set( - "hash", - data.contentHash ? contentHashReplacer : moduleHashReplacer - ); - // Legacy - replacements.set( - "moduleid", - deprecated( - idReplacer, - "[moduleid] is now [id]", - "DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_MODULE_ID" - ) - ); - } - - // Other things - if (data.url) { - replacements.set("url", replacer(data.url)); - } - if (typeof data.runtime === "string") { - replacements.set( - "runtime", - replacer(() => prepareId(/** @type {string} */ (data.runtime))) - ); - } else { - replacements.set("runtime", replacer("_")); - } - - if (typeof path === "function") { - path = path(data, assetInfo); - } - - 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, path); - } - } else if (match.startsWith("[\\") && match.endsWith("\\]")) { - return `[${match.slice(2, -2)}]`; - } - return match; - }); - - return path; -}; - -const plugin = "TemplatedPathPlugin"; - -class TemplatedPathPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap(plugin, compilation => { - compilation.hooks.assetPath.tap(plugin, replacePathVariables); - }); - } -} - -module.exports = TemplatedPathPlugin; diff --git a/webpack-lib/lib/UnhandledSchemeError.js b/webpack-lib/lib/UnhandledSchemeError.js deleted file mode 100644 index 80fa07af188..00000000000 --- a/webpack-lib/lib/UnhandledSchemeError.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); -const makeSerializable = require("./util/makeSerializable"); - -class UnhandledSchemeError extends WebpackError { - /** - * @param {string} scheme scheme - * @param {string} resource resource - */ - constructor(scheme, resource) { - super( - `Reading from "${resource}" is not handled by plugins (Unhandled scheme).` + - '\nWebpack supports "data:" and "file:" URIs by default.' + - `\nYou may need an additional plugin to handle "${scheme}:" URIs.` - ); - this.file = resource; - this.name = "UnhandledSchemeError"; - } -} - -makeSerializable( - UnhandledSchemeError, - "webpack/lib/UnhandledSchemeError", - "UnhandledSchemeError" -); - -module.exports = UnhandledSchemeError; diff --git a/webpack-lib/lib/UnsupportedFeatureWarning.js b/webpack-lib/lib/UnsupportedFeatureWarning.js deleted file mode 100644 index 2c59f4a80a8..00000000000 --- a/webpack-lib/lib/UnsupportedFeatureWarning.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"); -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ - -class UnsupportedFeatureWarning extends WebpackError { - /** - * @param {string} message description of warning - * @param {DependencyLocation} loc location start and end positions of the module - */ - constructor(message, loc) { - super(message); - - this.name = "UnsupportedFeatureWarning"; - this.loc = loc; - this.hideStack = true; - } -} - -makeSerializable( - UnsupportedFeatureWarning, - "webpack/lib/UnsupportedFeatureWarning" -); - -module.exports = UnsupportedFeatureWarning; diff --git a/webpack-lib/lib/UseStrictPlugin.js b/webpack-lib/lib/UseStrictPlugin.js deleted file mode 100644 index 3db3daa8f62..00000000000 --- a/webpack-lib/lib/UseStrictPlugin.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - 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("../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 { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - /** - * @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 && - firstNode.type === "ExpressionStatement" && - firstNode.expression.type === "Literal" && - firstNode.expression.value === "use strict" - ) { - // 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( - "", - /** @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_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 = UseStrictPlugin; diff --git a/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js b/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js deleted file mode 100644 index 11af42a590f..00000000000 --- a/webpack-lib/lib/WarnCaseSensitiveModulesPlugin.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - 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("./NormalModule")} NormalModule */ - -class WarnCaseSensitiveModulesPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "WarnCaseSensitiveModulesPlugin", - compilation => { - compilation.hooks.seal.tap("WarnCaseSensitiveModulesPlugin", () => { - /** @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; - } - - 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 - ) - ); - } - } - }); - } - ); - } -} - -module.exports = WarnCaseSensitiveModulesPlugin; diff --git a/webpack-lib/lib/WarnDeprecatedOptionPlugin.js b/webpack-lib/lib/WarnDeprecatedOptionPlugin.js deleted file mode 100644 index 8cad0869908..00000000000 --- a/webpack-lib/lib/WarnDeprecatedOptionPlugin.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const WebpackError = require("./WebpackError"); - -/** @typedef {import("./Compiler")} Compiler */ - -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; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "WarnDeprecatedOptionPlugin", - 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(); - - this.name = "DeprecatedOptionWarning"; - this.message = - "configuration\n" + - `The value '${value}' for option '${option}' is deprecated. ` + - `Use '${suggestion}' instead.`; - } -} - -module.exports = WarnDeprecatedOptionPlugin; diff --git a/webpack-lib/lib/WarnNoModeSetPlugin.js b/webpack-lib/lib/WarnNoModeSetPlugin.js deleted file mode 100644 index b8685f03990..00000000000 --- a/webpack-lib/lib/WarnNoModeSetPlugin.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NoModeWarning = require("./NoModeWarning"); - -/** @typedef {import("./Compiler")} Compiler */ - -class WarnNoModeSetPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap("WarnNoModeSetPlugin", compilation => { - compilation.warnings.push(new NoModeWarning()); - }); - } -} - -module.exports = WarnNoModeSetPlugin; diff --git a/webpack-lib/lib/WatchIgnorePlugin.js b/webpack-lib/lib/WatchIgnorePlugin.js deleted file mode 100644 index a7471e9c347..00000000000 --- a/webpack-lib/lib/WatchIgnorePlugin.js +++ /dev/null @@ -1,153 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { groupBy } = require("./util/ArrayHelpers"); -const createSchemaValidation = require("./util/create-schema-validation"); - -/** @typedef {import("../declarations/plugins/WatchIgnorePlugin").WatchIgnorePluginOptions} WatchIgnorePluginOptions */ -/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */ -/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */ -/** @typedef {import("./util/fs").WatchMethod} WatchMethod */ -/** @typedef {import("./util/fs").Watcher} Watcher */ -const validate = createSchemaValidation( - require("../schemas/plugins/WatchIgnorePlugin.check.js"), - () => require("../schemas/plugins/WatchIgnorePlugin.json"), - { - name: "Watch Ignore Plugin", - baseDataPath: "options" - } -); - -const IGNORE_TIME_ENTRY = "ignore"; - -class 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) { - files = Array.from(files); - dirs = Array.from(dirs); - /** - * @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 [ignoredFiles, notIgnoredFiles] = groupBy( - /** @type {Array} */ - (files), - ignored - ); - const [ignoredDirs, notIgnoredDirs] = groupBy( - /** @type {Array} */ - (dirs), - ignored - ); - - const watcher = this.wfs.watch( - notIgnoredFiles, - notIgnoredDirs, - missing, - startTime, - options, - (err, fileTimestamps, dirTimestamps, changedFiles, removedFiles) => { - if (err) return callback(err); - for (const path of ignoredFiles) { - /** @type {TimeInfoEntries} */ - (fileTimestamps).set(path, IGNORE_TIME_ENTRY); - } - - for (const path of ignoredDirs) { - /** @type {TimeInfoEntries} */ - (dirTimestamps).set(path, IGNORE_TIME_ENTRY); - } - - callback( - null, - fileTimestamps, - dirTimestamps, - changedFiles, - removedFiles - ); - }, - callbackUndelayed - ); - - return { - close: () => watcher.close(), - pause: () => watcher.pause(), - getContextTimeInfoEntries: () => { - const dirTimestamps = watcher.getContextTimeInfoEntries(); - for (const path of ignoredDirs) { - dirTimestamps.set(path, IGNORE_TIME_ENTRY); - } - return dirTimestamps; - }, - getFileTimeInfoEntries: () => { - const fileTimestamps = watcher.getFileTimeInfoEntries(); - for (const path of ignoredFiles) { - 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; - }) - }; - } -} - -class WatchIgnorePlugin { - /** - * @param {WatchIgnorePluginOptions} options options - */ - constructor(options) { - validate(options); - this.paths = options.paths; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.afterEnvironment.tap("WatchIgnorePlugin", () => { - compiler.watchFileSystem = new IgnoringWatchFileSystem( - /** @type {WatchFileSystem} */ - (compiler.watchFileSystem), - this.paths - ); - }); - } -} - -module.exports = WatchIgnorePlugin; diff --git a/webpack-lib/lib/Watching.js b/webpack-lib/lib/Watching.js deleted file mode 100644 index a047f257b20..00000000000 --- a/webpack-lib/lib/Watching.js +++ /dev/null @@ -1,528 +0,0 @@ -/* - 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("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ -/** @typedef {import("./WebpackError")} WebpackError */ -/** @typedef {import("./logging/Logger").Logger} Logger */ -/** @typedef {import("./util/fs").TimeInfoEntries} TimeInfoEntries */ -/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */ -/** @typedef {import("./util/fs").Watcher} Watcher */ - -/** - * @template T - * @callback Callback - * @param {Error | null} err - * @param {T=} result - */ - -class Watching { - /** - * @param {Compiler} compiler the compiler - * @param {WatchOptions} watchOptions options - * @param {Callback} handler completion handler - */ - constructor(compiler, watchOptions, handler) { - this.startTime = null; - this.invalid = false; - this.handler = handler; - /** @type {Callback[]} */ - this.callbacks = []; - /** @type {Callback[] | 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") { - /** @type {WatchOptions} */ - this.watchOptions = { ...watchOptions }; - } else { - /** @type {WatchOptions} */ - this.watchOptions = {}; - } - if (typeof this.watchOptions.aggregateTimeout !== "number") { - this.watchOptions.aggregateTimeout = 20; - } - this.compiler = compiler; - this.running = false; - this._initial = true; - this._invalidReported = true; - this._needRecords = true; - this.watcher = undefined; - this.pausedWatcher = undefined; - /** @type {Set | undefined} */ - this._collectedChangedFiles = undefined; - /** @type {Set | undefined} */ - this._collectedRemovedFiles = undefined; - this._done = this._done.bind(this); - process.nextTick(() => { - if (this._initial) this._invalidate(); - }); - } - - /** - * @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 {Set} */ - (this._collectedRemovedFiles).delete(file); - } - for (const file of /** @type {ReadonlySet} */ (removedFiles)) { - this._collectedChangedFiles.delete(file); - /** @type {Set} */ - (this._collectedRemovedFiles).add(file); - } - } - } - - /** - * @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; - 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); - /** - * @param {Error | null} err error - * @param {Compilation=} _compilation compilation - * @returns {void} - */ - const onCompiled = (err, _compilation) => { - if (err) return this._done(err, _compilation); - - const compilation = /** @type {Compilation} */ (_compilation); - - if (this.invalid) return this._done(null, compilation); - - 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); - - 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; - - 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, compilation); - this.compiler.compile(onCompiled); - }); - }); - return; - } - return this._done(null, compilation); - }); - }); - }); - }; - this.compiler.compile(onCompiled); - }); - }; - - run(); - } - - /** - * @param {Compilation} compilation the compilation - * @returns {Stats} the compilation stats - */ - _getStats(compilation) { - const stats = new Stats(compilation); - return stats; - } - - /** - * @param {(Error | null)=} err an optional error - * @param {Compilation=} compilation the compilation - * @returns {void} - */ - _done(err, compilation) { - this.running = false; - - const logger = - /** @type {Logger} */ - (compilation && compilation.getLogger("webpack.Watching")); - - /** @type {Stats | undefined} */ - let stats; - - /** - * @param {Error} err error - * @param {Callback[]=} cbs callbacks - */ - const handleError = (err, cbs) => { - this.compiler.hooks.failed.call(err); - 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; - } - - 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); - 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)); - } - ); - }); - } - - /** - * @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 = - /** @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(); - } - ); - } - - /** - * @param {Callback=} callback signals when the build has completed again - * @returns {void} - */ - invalidate(callback) { - if (callback) { - this.callbacks.push(callback); - } - if (!this._invalidReported) { - this._invalidReported = true; - this.compiler.hooks.invalid.call(null, Date.now()); - } - this._onChange(); - this._invalidate(); - } - - /** - * @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; - } else { - this._go( - fileTimeInfoEntries, - contextTimeInfoEntries, - changedFiles, - removedFiles - ); - } - } - - suspend() { - this.suspended = true; - } - - resume() { - if (this.suspended) { - this.suspended = false; - this._invalidate(); - } - } - - /** - * @param {Callback} callback signals when the watcher is closed - * @returns {void} - */ - close(callback) { - if (this._closeCallbacks) { - if (callback) { - this._closeCallbacks.push(callback); - } - return; - } - /** - * @param {WebpackError | null} err error if any - * @param {Compilation=} compilation compilation if any - */ - const finalCallback = (err, compilation) => { - this.running = false; - this.compiler.running = false; - 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; - /** - * @param {WebpackError | null} err error if any - */ - const shutdown = err => { - this.compiler.hooks.watchClose.call(); - const closeCallbacks = - /** @type {Callback[]} */ - (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; - if (this.watcher) { - this.watcher.close(); - this.watcher = null; - } - if (this.pausedWatcher) { - 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(null); - } - } -} - -module.exports = Watching; diff --git a/webpack-lib/lib/WebpackError.js b/webpack-lib/lib/WebpackError.js deleted file mode 100644 index d20d816b246..00000000000 --- a/webpack-lib/lib/WebpackError.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Jarid Margolin @jaridmargolin -*/ - -"use strict"; - -const inspect = require("util").inspect.custom; -const makeSerializable = require("./util/makeSerializable"); - -/** @typedef {import("./Chunk")} Chunk */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class WebpackError extends Error { - /** - * Creates an instance of WebpackError. - * @param {string=} message error message - */ - constructor(message) { - super(message); - - /** @type {string=} */ - this.details = undefined; - /** @type {(Module | null)=} */ - this.module = undefined; - /** @type {DependencyLocation=} */ - this.loc = undefined; - /** @type {boolean=} */ - this.hideStack = undefined; - /** @type {Chunk=} */ - this.chunk = undefined; - /** @type {string=} */ - this.file = undefined; - } - - [inspect]() { - return this.stack + (this.details ? `\n${this.details}` : ""); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize({ write }) { - write(this.name); - write(this.message); - write(this.stack); - write(this.details); - write(this.loc); - write(this.hideStack); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize({ read }) { - this.name = read(); - this.message = read(); - this.stack = read(); - this.details = read(); - this.loc = read(); - this.hideStack = read(); - } -} - -makeSerializable(WebpackError, "webpack/lib/WebpackError"); - -module.exports = WebpackError; diff --git a/webpack-lib/lib/WebpackIsIncludedPlugin.js b/webpack-lib/lib/WebpackIsIncludedPlugin.js deleted file mode 100644 index 981cf8f6dff..00000000000 --- a/webpack-lib/lib/WebpackIsIncludedPlugin.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const IgnoreErrorModuleFactory = require("./IgnoreErrorModuleFactory"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("./ModuleTypeConstants"); -const WebpackIsIncludedDependency = require("./dependencies/WebpackIsIncludedDependency"); -const { - toConstantDependency -} = require("./javascript/JavascriptParserHelpers"); - -/** @typedef {import("enhanced-resolve").Resolver} Resolver */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Module")} Module */ -/** @typedef {import("./javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./javascript/JavascriptParser").Range} Range */ - -const PLUGIN_NAME = "WebpackIsIncludedPlugin"; - -class WebpackIsIncludedPlugin { - /** - * @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() - ); - - /** - * @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/webpack-lib/lib/WebpackOptionsApply.js b/webpack-lib/lib/WebpackOptionsApply.js deleted file mode 100644 index 3928c043832..00000000000 --- a/webpack-lib/lib/WebpackOptionsApply.js +++ /dev/null @@ -1,783 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const OptionsApply = require("./OptionsApply"); - -const AssetModulesPlugin = require("./asset/AssetModulesPlugin"); -const JavascriptModulesPlugin = require("./javascript/JavascriptModulesPlugin"); -const JsonModulesPlugin = require("./json/JsonModulesPlugin"); - -const ChunkPrefetchPreloadPlugin = require("./prefetch/ChunkPrefetchPreloadPlugin"); - -const EntryOptionPlugin = require("./EntryOptionPlugin"); -const RecordIdsPlugin = require("./RecordIdsPlugin"); - -const RuntimePlugin = require("./RuntimePlugin"); - -const APIPlugin = require("./APIPlugin"); -const CompatibilityPlugin = require("./CompatibilityPlugin"); -const ConstPlugin = require("./ConstPlugin"); -const ExportsInfoApiPlugin = require("./ExportsInfoApiPlugin"); -const WebpackIsIncludedPlugin = require("./WebpackIsIncludedPlugin"); - -const TemplatedPathPlugin = require("./TemplatedPathPlugin"); -const UseStrictPlugin = require("./UseStrictPlugin"); -const WarnCaseSensitiveModulesPlugin = require("./WarnCaseSensitiveModulesPlugin"); - -const DataUriPlugin = require("./schemes/DataUriPlugin"); -const FileUriPlugin = require("./schemes/FileUriPlugin"); - -const ResolverCachePlugin = require("./cache/ResolverCachePlugin"); - -const CommonJsPlugin = require("./dependencies/CommonJsPlugin"); -const HarmonyModulesPlugin = require("./dependencies/HarmonyModulesPlugin"); -const ImportMetaContextPlugin = require("./dependencies/ImportMetaContextPlugin"); -const ImportMetaPlugin = require("./dependencies/ImportMetaPlugin"); -const ImportPlugin = require("./dependencies/ImportPlugin"); -const LoaderPlugin = require("./dependencies/LoaderPlugin"); -const RequireContextPlugin = require("./dependencies/RequireContextPlugin"); -const RequireEnsurePlugin = require("./dependencies/RequireEnsurePlugin"); -const RequireIncludePlugin = require("./dependencies/RequireIncludePlugin"); -const SystemPlugin = require("./dependencies/SystemPlugin"); -const URLPlugin = require("./dependencies/URLPlugin"); -const WorkerPlugin = require("./dependencies/WorkerPlugin"); - -const InferAsyncModulesPlugin = require("./async-modules/InferAsyncModulesPlugin"); - -const JavascriptMetaInfoPlugin = require("./JavascriptMetaInfoPlugin"); -const DefaultStatsFactoryPlugin = require("./stats/DefaultStatsFactoryPlugin"); -const DefaultStatsPresetPlugin = require("./stats/DefaultStatsPresetPlugin"); -const DefaultStatsPrinterPlugin = require("./stats/DefaultStatsPrinterPlugin"); - -const { cleverMerge } = require("./util/cleverMerge"); - -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ -/** @typedef {import("./Compiler")} Compiler */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */ - -class WebpackOptionsApply extends OptionsApply { - constructor() { - super(); - } - - /** - * @param {WebpackOptions} options options object - * @param {Compiler} compiler compiler object - * @returns {WebpackOptions} options object - */ - process(options, compiler) { - compiler.outputPath = /** @type {string} */ (options.output.path); - compiler.recordsInputPath = options.recordsInputPath || null; - compiler.recordsOutputPath = options.recordsOutputPath || null; - compiler.name = options.name; - - 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"); - new NodeTargetPlugin().apply(compiler); - } - if (options.externalsPresets.electronMain) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); - new ElectronTargetPlugin("main").apply(compiler); - } - if (options.externalsPresets.electronPreload) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); - new ElectronTargetPlugin("preload").apply(compiler); - } - if (options.externalsPresets.electronRenderer) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); - new ElectronTargetPlugin("renderer").apply(compiler); - } - if ( - options.externalsPresets.electron && - !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().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); - } - if (options.externalsPresets.webAsync) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ExternalsPlugin = require("./ExternalsPlugin"); - new ExternalsPlugin("import", ({ request, dependencyType }, callback) => { - if (dependencyType === "url") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `asset ${request}`); - } else if (options.experiments.css && dependencyType === "css-import") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `css-import ${request}`); - } else if ( - options.experiments.css && - /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) - ) { - if (/^\.css(\?|$)/.test(/** @type {string} */ (request))) - return callback(null, `css-import ${request}`); - return callback(null, `import ${request}`); - } - callback(); - }).apply(compiler); - } else if (options.externalsPresets.web) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ExternalsPlugin = require("./ExternalsPlugin"); - new ExternalsPlugin("module", ({ request, dependencyType }, callback) => { - if (dependencyType === "url") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `asset ${request}`); - } else if (options.experiments.css && dependencyType === "css-import") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `css-import ${request}`); - } else if ( - /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) - ) { - if ( - options.experiments.css && - /^\.css((\?)|$)/.test(/** @type {string} */ (request)) - ) - return callback(null, `css-import ${request}`); - return callback(null, `module ${request}`); - } - callback(); - }).apply(compiler); - } else if (options.externalsPresets.node && options.experiments.css) { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const ExternalsPlugin = require("./ExternalsPlugin"); - new ExternalsPlugin("module", ({ request, dependencyType }, callback) => { - if (dependencyType === "url") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `asset ${request}`); - } else if (dependencyType === "css-import") { - if (/^(\/\/|https?:\/\/|#)/.test(/** @type {string} */ (request))) - return callback(null, `css-import ${request}`); - } else if ( - /^(\/\/|https?:\/\/|std:)/.test(/** @type {string} */ (request)) - ) { - if (/^\.css(\?|$)/.test(/** @type {string} */ (request))) - return callback(null, `css-import ${request}`); - return callback(null, `module ${request}`); - } - callback(); - }).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 "commonjs": { - const CommonJsChunkFormatPlugin = require("./javascript/CommonJsChunkFormatPlugin"); - new CommonJsChunkFormatPlugin().apply(compiler); - break; - } - case "module": { - const ModuleChunkFormatPlugin = require("./esm/ModuleChunkFormatPlugin"); - new ModuleChunkFormatPlugin().apply(compiler); - break; - } - default: - 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) { - for (const type of enabledLibraryTypes) { - const EnableLibraryPlugin = require("./library/EnableLibraryPlugin"); - new EnableLibraryPlugin(type).apply(compiler); - } - } - - 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.devtool) { - if (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"); - const noSources = options.devtool.includes("nosources"); - const debugIds = options.devtool.includes("debugids"); - const Plugin = evalWrapped - ? require("./EvalSourceMapDevToolPlugin") - : require("./SourceMapDevToolPlugin"); - new Plugin({ - 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 (options.devtool.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 AssetModulesPlugin().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.lazyCompilation) { - const LazyCompilationPlugin = require("./hmr/LazyCompilationPlugin"); - const lazyOptions = - typeof options.experiments.lazyCompilation === "object" - ? options.experiments.lazyCompilation - : {}; - new LazyCompilationPlugin({ - backend: - typeof lazyOptions.backend === "function" - ? lazyOptions.backend - : require("./hmr/lazyCompilationBackend")({ - ...lazyOptions.backend, - client: - (lazyOptions.backend && lazyOptions.backend.client) || - require.resolve( - `../hot/lazy-compilation-${ - options.externalsPresets.node ? "node" : "web" - }.js` - ) - }), - entries: !lazyOptions || lazyOptions.entries !== false, - imports: !lazyOptions || lazyOptions.imports !== false, - test: (lazyOptions && lazyOptions.test) || undefined - }).apply(compiler); - } - - if (options.experiments.buildHttp) { - const HttpUriPlugin = require("./schemes/HttpUriPlugin"); - const httpOptions = options.experiments.buildHttp; - new HttpUriPlugin(httpOptions).apply(compiler); - } - - new EntryOptionPlugin().apply(compiler); - compiler.hooks.entryOption.call( - /** @type {string} */ - (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({ - topLevelAwait: options.experiments.topLevelAwait - }).apply(compiler); - if (options.amd !== false) { - const AMDPlugin = require("./dependencies/AMDPlugin"); - const RequireJsStuffPlugin = require("./RequireJsStuffPlugin"); - new AMDPlugin(options.amd || {}).apply(compiler); - new RequireJsStuffPlugin().apply(compiler); - } - new CommonJsPlugin().apply(compiler); - new LoaderPlugin({}).apply(compiler); - if (options.node !== false) { - const NodeStuffPlugin = require("./NodeStuffPlugin"); - new NodeStuffPlugin(options.node).apply(compiler); - } - new APIPlugin({ - module: options.output.module - }).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().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 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.sideEffects) { - const SideEffectsFlagPlugin = require("./optimize/SideEffectsFlagPlugin"); - new SideEffectsFlagPlugin( - options.optimization.sideEffects === true - ).apply(compiler); - } - if (options.optimization.providedExports) { - const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin"); - new FlagDependencyExportsPlugin().apply(compiler); - } - if (options.optimization.usedExports) { - 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.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( - /** @type {{ name?: (entrypoint: { name: string }) => string }} */ - (options.optimization.runtimeChunk) - ).apply(compiler); - } - 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); - } - 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` - ); - } - } - 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"); - new DefinePlugin({ - "process.env.NODE_ENV": JSON.stringify(options.optimization.nodeEnv) - }).apply(compiler); - } - if (options.optimization.minimize) { - for (const minimizer of /** @type {(WebpackPluginInstance | WebpackPluginFunction | "...")[]} */ ( - options.optimization.minimizer - )) { - 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); - } - - new TemplatedPathPlugin().apply(compiler); - - new RecordIdsPlugin({ - portableIds: options.optimization.portableRecords - }).apply(compiler); - - new WarnCaseSensitiveModulesPlugin().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"); - // eslint-disable-next-line guard-for-in - 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: /** @type {string} */ (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); - if (!compiler.inputFileSystem) { - throw new Error("No input filesystem provided"); - } - compiler.resolverFactory.hooks.resolveOptions - .for("normal") - .tap("WebpackOptionsApply", resolveOptions => { - resolveOptions = cleverMerge(options.resolve, resolveOptions); - resolveOptions.fileSystem = - /** @type {InputFileSystem} */ - (compiler.inputFileSystem); - return resolveOptions; - }); - compiler.resolverFactory.hooks.resolveOptions - .for("context") - .tap("WebpackOptionsApply", 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 => { - resolveOptions = cleverMerge(options.resolveLoader, resolveOptions); - resolveOptions.fileSystem = - /** @type {InputFileSystem} */ - (compiler.inputFileSystem); - return resolveOptions; - }); - compiler.hooks.afterResolvers.call(compiler); - return options; - } -} - -module.exports = WebpackOptionsApply; diff --git a/webpack-lib/lib/WebpackOptionsDefaulter.js b/webpack-lib/lib/WebpackOptionsDefaulter.js deleted file mode 100644 index 12fbe698d93..00000000000 --- a/webpack-lib/lib/WebpackOptionsDefaulter.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { applyWebpackOptionsDefaults } = require("./config/defaults"); -const { getNormalizedWebpackOptions } = require("./config/normalization"); - -/** @typedef {import("./config/normalization").WebpackOptions} WebpackOptions */ -/** @typedef {import("./config/normalization").WebpackOptionsNormalized} WebpackOptionsNormalized */ - -class WebpackOptionsDefaulter { - /** - * @param {WebpackOptions} options webpack options - * @returns {WebpackOptionsNormalized} normalized webpack options - */ - process(options) { - const normalizedOptions = getNormalizedWebpackOptions(options); - applyWebpackOptionsDefaults(normalizedOptions); - return normalizedOptions; - } -} - -module.exports = WebpackOptionsDefaulter; diff --git a/webpack-lib/lib/asset/AssetGenerator.js b/webpack-lib/lib/asset/AssetGenerator.js deleted file mode 100644 index 4661d6cafdc..00000000000 --- a/webpack-lib/lib/asset/AssetGenerator.js +++ /dev/null @@ -1,739 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const mimeTypes = require("mime-types"); -const path = require("path"); -const { RawSource } = require("webpack-sources"); -const ConcatenationScope = require("../ConcatenationScope"); -const Generator = require("../Generator"); -const { - NO_TYPES, - ASSET_TYPES, - ASSET_AND_JS_TYPES, - ASSET_AND_JS_AND_CSS_URL_TYPES, - ASSET_AND_CSS_URL_TYPES, - JS_TYPES, - JS_AND_CSS_URL_TYPES, - CSS_URL_TYPES -} = require("../ModuleSourceTypesConstants"); -const { ASSET_MODULE_TYPE } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const CssUrlDependency = require("../dependencies/CssUrlDependency"); -const createHash = require("../util/createHash"); -const { makePathsRelative } = require("../util/identifier"); -const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); - -/** @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").RawPublicPath} RawPublicPath */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("../Compilation").InterpolatedPathAndAssetInfo} InterpolatedPathAndAssetInfo */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/createHash").Algorithm} Algorithm */ - -/** - * @template T - * @template U - * @param {Array | Set} a a - * @param {Array | Set} b b - * @returns {Array & Array} array - */ -const mergeMaybeArrays = (a, b) => { - 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 Array.from(set); -}; - -/** - * @template {object} T - * @template {object} U - * @param {TODO} a a - * @param {TODO} b b - * @returns {T & U} object - */ -const mergeAssetInfo = (a, b) => { - 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(a[key], b[key]); - break; - default: - throw new Error(`Can't handle conflicting asset info for ${key}`); - } - } - } - return result; -}; - -/** - * @template {object} T - * @template {object} U - * @param {TODO} a a - * @param {TODO} b b - * @returns {T & U} 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; -}; - -/** - * @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("utf-8"); - } - - 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; -}; - -/** - * @param {string} 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 { - /** - * @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(); - this.dataUrlOptions = dataUrlOptions; - this.filename = filename; - this.publicPath = publicPath; - this.outputPath = outputPath; - this.emit = emit; - this._moduleGraph = moduleGraph; - } - - /** - * @param {NormalModule} module module - * @param {RuntimeTemplate} runtimeTemplate runtime template - * @returns {string} source file name - */ - getSourceFileName(module, runtimeTemplate) { - return makePathsRelative( - runtimeTemplate.compilation.compiler.context, - module.matchResource || module.resource, - runtimeTemplate.compilation.compiler.root - ).replace(/^\.\//, ""); - } - - /** - * @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; - } - - /** - * @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 | boolean | undefined} */ - let mimeType = - /** @type {AssetGeneratorDataUrlOptions} */ - (this.dataUrlOptions).mimetype; - if (mimeType === undefined) { - const ext = path.extname( - /** @type {string} */ - (module.nameForCondition()) - ); - if ( - module.resourceResolveData && - module.resourceResolveData.mimetype !== undefined - ) { - mimeType = - module.resourceResolveData.mimetype + - module.resourceResolveData.parameters; - } else if (ext) { - mimeType = mimeTypes.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); - } - - /** - * @param {NormalModule} module module for which the code should be generated - * @returns {string} DataURI - */ - generateDataUri(module) { - const source = /** @type {Source} */ (module.originalSource()); - - let encodedSource; - - if (typeof this.dataUrlOptions === "function") { - encodedSource = this.dataUrlOptions.call(null, source.source(), { - filename: module.matchResource || module.resource, - module - }); - } else { - /** @type {"base64" | false | undefined} */ - 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); - - let encodedContent; - - if ( - module.resourceResolveData && - module.resourceResolveData.encoding === encoding && - decodeDataUriContent( - module.resourceResolveData.encoding, - module.resourceResolveData.encodedContent - ).equals(source.buffer()) - ) { - encodedContent = module.resourceResolveData.encodedContent; - } else { - encodedContent = encodeDataUri(encoding, source); - } - - encodedSource = `data:${mimeType}${ - encoding ? `;${encoding}` : "" - },${encodedContent}`; - } - - return encodedSource; - } - - /** - * @private - * @param {NormalModule} module module for which the code should be generated - * @param {GenerateContext} generateContext context for generate - * @param {string} contentHash the content hash - * @returns {{ filename: string, originalFilename: string, assetInfo: AssetInfo }} info - */ - _getFilenameWithInfo( - module, - { runtime, runtimeTemplate, chunkGraph }, - contentHash - ) { - const assetModuleFilename = - this.filename || - /** @type {AssetModuleFilename} */ - (runtimeTemplate.outputOptions.assetModuleFilename); - - const sourceFilename = this.getSourceFileName(module, runtimeTemplate); - let { path: filename, info: assetInfo } = - runtimeTemplate.compilation.getAssetPathWithInfo(assetModuleFilename, { - module, - runtime, - filename: sourceFilename, - chunkGraph, - contentHash - }); - - const originalFilename = filename; - - if (this.outputPath) { - const { path: outputPath, info } = - runtimeTemplate.compilation.getAssetPathWithInfo(this.outputPath, { - module, - runtime, - filename: sourceFilename, - chunkGraph, - contentHash - }); - filename = path.posix.join(outputPath, filename); - assetInfo = mergeAssetInfo(assetInfo, info); - } - - return { originalFilename, filename, assetInfo }; - } - - /** - * @private - * @param {NormalModule} module module for which the code should be generated - * @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 - */ - _getAssetPathWithInfo( - module, - { runtimeTemplate, runtime, chunkGraph, type, runtimeRequirements }, - filename, - assetInfo, - contentHash - ) { - const sourceFilename = this.getSourceFileName(module, runtimeTemplate); - - let assetPath; - - if (this.publicPath !== undefined && type === "javascript") { - const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo( - this.publicPath, - { - module, - runtime, - filename: sourceFilename, - chunkGraph, - contentHash - } - ); - assetInfo = mergeAssetInfo(assetInfo, info); - assetPath = JSON.stringify(path + filename); - } else if (this.publicPath !== undefined && type === "css-url") { - const { path, info } = runtimeTemplate.compilation.getAssetPathWithInfo( - this.publicPath, - { - module, - runtime, - filename: sourceFilename, - chunkGraph, - contentHash - } - ); - assetInfo = mergeAssetInfo(assetInfo, info); - assetPath = path + filename; - } else if (type === "javascript") { - // add __webpack_require__.p - runtimeRequirements.add(RuntimeGlobals.publicPath); - assetPath = runtimeTemplate.concatenation( - { expr: RuntimeGlobals.publicPath }, - filename - ); - } else if (type === "css-url") { - const compilation = runtimeTemplate.compilation; - const path = - compilation.outputOptions.publicPath === "auto" - ? CssUrlDependency.PUBLIC_PATH_AUTO - : compilation.getAssetPath( - /** @type {TemplatePath} */ - (compilation.outputOptions.publicPath), - { - hash: compilation.hash - } - ); - - assetPath = path + filename; - } - - return { - // eslint-disable-next-line object-shorthand - assetPath: /** @type {string} */ (assetPath), - assetInfo: { sourceFilename, ...assetInfo } - }; - } - - /** - * @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; - - let content; - - const needContent = type === "javascript" || type === "css-url"; - - const data = getData ? getData() : undefined; - - if ( - /** @type {BuildInfo} */ - (module.buildInfo).dataUrl && - needContent - ) { - const encodedSource = this.generateDataUri(module); - content = - type === "javascript" ? JSON.stringify(encodedSource) : encodedSource; - - if (data) { - data.set("url", { [type]: content, ...data.get("url") }); - } - } else { - const hash = createHash( - /** @type {Algorithm} */ - (runtimeTemplate.outputOptions.hashFunction) - ); - - if (runtimeTemplate.outputOptions.hashSalt) { - hash.update(runtimeTemplate.outputOptions.hashSalt); - } - - hash.update(/** @type {Source} */ (module.originalSource()).buffer()); - - const fullHash = - /** @type {string} */ - (hash.digest(runtimeTemplate.outputOptions.hashDigest)); - - if (data) { - data.set("fullContentHash", fullHash); - } - - /** @type {BuildInfo} */ - (module.buildInfo).fullContentHash = fullHash; - - /** @type {string} */ - const contentHash = nonNumericOnlyHash( - fullHash, - /** @type {number} */ - (generateContext.runtimeTemplate.outputOptions.hashDigestLength) - ); - - if (data) { - data.set("contentHash", contentHash); - } - - const { originalFilename, filename, assetInfo } = - this._getFilenameWithInfo(module, generateContext, contentHash); - - if (data) { - data.set("filename", filename); - } - - let { assetPath, assetInfo: newAssetInfo } = this._getAssetPathWithInfo( - module, - generateContext, - originalFilename, - assetInfo, - contentHash - ); - - if (data && (type === "javascript" || type === "css-url")) { - data.set("url", { [type]: assetPath, ...data.get("url") }); - } - - if (data && data.get("assetInfo")) { - newAssetInfo = mergeAssetInfo(data.get("assetInfo"), 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 {BuildInfo} */ - (module.buildInfo).filename = filename; - - /** @type {BuildInfo} */ - (module.buildInfo).assetInfo = newAssetInfo; - - content = assetPath; - } - - if (type === "javascript") { - if (concatenationScope) { - concatenationScope.registerNamespaceExport( - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - ); - - return new RawSource( - `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - } = ${content};` - ); - } - - runtimeRequirements.add(RuntimeGlobals.module); - - return new RawSource(`${RuntimeGlobals.module}.exports = ${content};`); - } else if (type === "css-url") { - return null; - } - - return /** @type {Source} */ (module.originalSource()); - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - 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 && module.buildInfo.dataUrl) || this.emit === false) { - if (sourceTypes) { - if (sourceTypes.has("javascript") && sourceTypes.has("css")) { - return JS_AND_CSS_URL_TYPES; - } else if (sourceTypes.has("javascript")) { - return JS_TYPES; - } else if (sourceTypes.has("css")) { - return CSS_URL_TYPES; - } - } - - return NO_TYPES; - } - - if (sourceTypes) { - if (sourceTypes.has("javascript") && sourceTypes.has("css")) { - return ASSET_AND_JS_AND_CSS_URL_TYPES; - } else if (sourceTypes.has("javascript")) { - return ASSET_AND_JS_TYPES; - } else if (sourceTypes.has("css")) { - return ASSET_AND_CSS_URL_TYPES; - } - } - - return ASSET_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} 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 && 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; - } - } - - /** - * @param {Hash} hash hash that will be modified - * @param {UpdateHashContext} updateHashContext context for updating hash - */ - updateHash(hash, updateHashContext) { - const { module } = updateHashContext; - if ( - /** @type {BuildInfo} */ - (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: this.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 || - /** @type {AssetModuleFilename} */ - (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/webpack-lib/lib/asset/AssetModulesPlugin.js b/webpack-lib/lib/asset/AssetModulesPlugin.js deleted file mode 100644 index 93817f3d064..00000000000 --- a/webpack-lib/lib/asset/AssetModulesPlugin.js +++ /dev/null @@ -1,250 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Yuta Hiroto @hiroppy -*/ - -"use strict"; - -const { - ASSET_MODULE_TYPE_RESOURCE, - ASSET_MODULE_TYPE_INLINE, - ASSET_MODULE_TYPE, - ASSET_MODULE_TYPE_SOURCE -} = require("../ModuleTypeConstants"); -const { cleverMerge } = require("../util/cleverMerge"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const memoize = require("../util/memoize"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").AssetParserOptions} AssetParserOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ - -/** - * @param {string} name name of definitions - * @returns {TODO} definition - */ -const getSchema = name => { - const { definitions } = require("../../schemas/WebpackOptions.json"); - return { - definitions, - oneOf: [{ $ref: `#/definitions/${name}` }] - }; -}; - -const generatorValidationOptions = { - name: "Asset Modules Plugin", - baseDataPath: "generator" -}; -const validateGeneratorOptions = { - asset: createSchemaValidation( - require("../../schemas/plugins/asset/AssetGeneratorOptions.check.js"), - () => getSchema("AssetGeneratorOptions"), - generatorValidationOptions - ), - "asset/resource": createSchemaValidation( - require("../../schemas/plugins/asset/AssetResourceGeneratorOptions.check.js"), - () => getSchema("AssetResourceGeneratorOptions"), - generatorValidationOptions - ), - "asset/inline": createSchemaValidation( - require("../../schemas/plugins/asset/AssetInlineGeneratorOptions.check.js"), - () => getSchema("AssetInlineGeneratorOptions"), - generatorValidationOptions - ) -}; - -const validateParserOptions = createSchemaValidation( - require("../../schemas/plugins/asset/AssetParserOptions.check.js"), - () => getSchema("AssetParserOptions"), - { - name: "Asset Modules Plugin", - baseDataPath: "parser" - } -); - -const getAssetGenerator = memoize(() => require("./AssetGenerator")); -const getAssetParser = memoize(() => require("./AssetParser")); -const getAssetSourceParser = memoize(() => require("./AssetSourceParser")); -const getAssetSourceGenerator = memoize(() => - require("./AssetSourceGenerator") -); - -const type = ASSET_MODULE_TYPE; -const plugin = "AssetModulesPlugin"; - -class AssetModulesPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - plugin, - (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.createParser - .for(ASSET_MODULE_TYPE) - .tap(plugin, parserOptions => { - validateParserOptions(parserOptions); - parserOptions = cleverMerge( - /** @type {AssetParserOptions} */ - (compiler.options.module.parser.asset), - parserOptions - ); - - 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, _parserOptions => { - const AssetParser = getAssetParser(); - - return new AssetParser(true); - }); - normalModuleFactory.hooks.createParser - .for(ASSET_MODULE_TYPE_RESOURCE) - .tap(plugin, _parserOptions => { - const AssetParser = getAssetParser(); - - return new AssetParser(false); - }); - normalModuleFactory.hooks.createParser - .for(ASSET_MODULE_TYPE_SOURCE) - .tap(plugin, _parserOptions => { - const AssetSourceParser = getAssetSourceParser(); - - return new AssetSourceParser(); - }); - - for (const type of [ - ASSET_MODULE_TYPE, - ASSET_MODULE_TYPE_INLINE, - ASSET_MODULE_TYPE_RESOURCE - ]) { - normalModuleFactory.hooks.createGenerator - .for(type) - .tap(plugin, generatorOptions => { - validateGeneratorOptions[type](generatorOptions); - - let dataUrl; - if (type !== ASSET_MODULE_TYPE_RESOURCE) { - dataUrl = generatorOptions.dataUrl; - if (!dataUrl || typeof dataUrl === "object") { - dataUrl = { - encoding: undefined, - mimetype: undefined, - ...dataUrl - }; - } - } - - let filename; - let publicPath; - 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, () => { - const AssetSourceGenerator = getAssetSourceGenerator(); - - return new AssetSourceGenerator(compilation.moduleGraph); - }); - - compilation.hooks.renderManifest.tap(plugin, (result, options) => { - const { chunkGraph } = compilation; - const { chunk, codeGenerationResults } = options; - - const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - ASSET_MODULE_TYPE, - compareModulesByIdentifier - ); - if (modules) { - for (const module of modules) { - try { - const codeGenResult = codeGenerationResults.get( - module, - chunk.runtime - ); - const buildInfo = /** @type {BuildInfo} */ (module.buildInfo); - const data = - /** @type {NonNullable} */ - (codeGenResult.data); - const errored = module.getNumberOfErrors() > 0; - result.push({ - render: () => - /** @type {Source} */ (codeGenResult.sources.get(type)), - filename: errored - ? module.nameForCondition() - : buildInfo.filename || data.get("filename"), - info: buildInfo.assetInfo || data.get("assetInfo"), - auxiliary: true, - identifier: `assetModule${chunkGraph.getModuleId(module)}`, - hash: errored - ? chunkGraph.getModuleHash(module, chunk.runtime) - : buildInfo.fullContentHash || data.get("fullContentHash") - }); - } catch (err) { - /** @type {Error} */ (err).message += - `\nduring rendering of asset ${module.identifier()}`; - throw err; - } - } - } - - return result; - }); - - compilation.hooks.prepareModuleExecution.tap( - "AssetModulesPlugin", - (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(data.get("filename"), { - source, - info: data.get("assetInfo") - }); - } - ); - } - ); - } -} - -module.exports = AssetModulesPlugin; diff --git a/webpack-lib/lib/asset/AssetParser.js b/webpack-lib/lib/asset/AssetParser.js deleted file mode 100644 index b4f1d534948..00000000000 --- a/webpack-lib/lib/asset/AssetParser.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - 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").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ - -class AssetParser extends Parser { - /** - * @param {AssetParserOptions["dataUrlCondition"] | boolean} dataUrlCondition condition for inlining as DataUrl - */ - constructor(dataUrlCondition) { - super(); - this.dataUrlCondition = dataUrlCondition; - } - - /** - * @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 {BuildInfo} */ (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: state.module.matchResource || state.module.resource, - 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/webpack-lib/lib/asset/AssetSourceGenerator.js b/webpack-lib/lib/asset/AssetSourceGenerator.js deleted file mode 100644 index c6f2633d0b9..00000000000 --- a/webpack-lib/lib/asset/AssetSourceGenerator.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - 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 { - NO_TYPES, - CSS_URL_TYPES, - JS_TYPES, - JS_AND_CSS_URL_TYPES -} = require("../ModuleSourceTypesConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../NormalModule")} NormalModule */ - -class AssetSourceGenerator extends Generator { - /** - * @param {ModuleGraph} moduleGraph the module graph - */ - constructor(moduleGraph) { - super(); - - this._moduleGraph = moduleGraph; - } - - /** - * @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": { - if (!originalSource) { - return new RawSource(""); - } - - const content = originalSource.source(); - const encodedSource = - typeof content === "string" ? content : content.toString("utf-8"); - - let sourceContent; - if (concatenationScope) { - concatenationScope.registerNamespaceExport( - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - ); - sourceContent = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - } = ${JSON.stringify(encodedSource)};`; - } else { - runtimeRequirements.add(RuntimeGlobals.module); - sourceContent = `${RuntimeGlobals.module}.exports = ${JSON.stringify( - encodedSource - )};`; - } - return new RawSource(sourceContent); - } - case "css-url": { - if (!originalSource) { - return null; - } - - const content = originalSource.source(); - const encodedSource = - typeof content === "string" ? content : content.toString("utf-8"); - - if (data) { - data.set("url", { [type]: encodedSource }); - } - return null; - } - default: - return null; - } - } - - /** - * @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; - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - 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.has("javascript") && sourceTypes.has("css")) { - return JS_AND_CSS_URL_TYPES; - } else if (sourceTypes.has("javascript")) { - return JS_TYPES; - } else if (sourceTypes.has("css")) { - return CSS_URL_TYPES; - } - - return NO_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} 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/webpack-lib/lib/asset/AssetSourceParser.js b/webpack-lib/lib/asset/AssetSourceParser.js deleted file mode 100644 index c122f0ea4e4..00000000000 --- a/webpack-lib/lib/asset/AssetSourceParser.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - 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 { - /** - * @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/webpack-lib/lib/asset/RawDataUrlModule.js b/webpack-lib/lib/asset/RawDataUrlModule.js deleted file mode 100644 index 509efa51604..00000000000 --- a/webpack-lib/lib/asset/RawDataUrlModule.js +++ /dev/null @@ -1,163 +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"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const { ASSET_MODULE_TYPE_RAW_DATA_URL } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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 */ - -class RawDataUrlModule extends Module { - /** - * @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); - this.url = url; - this.urlBuffer = url ? Buffer.from(url) : undefined; - this.identifierStr = identifier || this.url; - this.readableIdentifierStr = readableIdentifier || this.identifierStr; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this.identifierStr; - } - - /** - * @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); - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return /** @type {string} */ ( - requestShortener.shorten(this.readableIdentifierStr) - ); - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - cacheable: true - }; - callback(); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration(context) { - if (this.url === undefined) - this.url = /** @type {Buffer} */ (this.urlBuffer).toString(); - const sources = new Map(); - sources.set( - "javascript", - new RawSource(`module.exports = ${JSON.stringify(this.url)};`) - ); - const data = new Map(); - data.set("url", { - javascript: this.url - }); - const runtimeRequirements = new Set(); - runtimeRequirements.add(RuntimeGlobals.module); - return { sources, runtimeRequirements, data }; - } - - /** - * @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); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.urlBuffer); - write(this.identifierStr); - write(this.readableIdentifierStr); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.urlBuffer = read(); - this.identifierStr = read(); - this.readableIdentifierStr = read(); - - super.deserialize(context); - } -} - -makeSerializable(RawDataUrlModule, "webpack/lib/asset/RawDataUrlModule"); - -module.exports = RawDataUrlModule; diff --git a/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js b/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js deleted file mode 100644 index 84abf28107d..00000000000 --- a/webpack-lib/lib/async-modules/AwaitDependenciesInitFragment.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - 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 */ - -/** - * @extends {InitFragment} - */ -class AwaitDependenciesInitFragment extends InitFragment { - /** - * @param {Set} promises the promises that should be awaited - */ - constructor(promises) { - super( - undefined, - InitFragment.STAGE_ASYNC_DEPENDENCIES, - 0, - "await-dependencies" - ); - this.promises = promises; - } - - /** - * @param {AwaitDependenciesInitFragment} other other AwaitDependenciesInitFragment - * @returns {AwaitDependenciesInitFragment} AwaitDependenciesInitFragment - */ - merge(other) { - const promises = new Set(other.promises); - for (const p of this.promises) { - promises.add(p); - } - return new AwaitDependenciesInitFragment(promises); - } - - /** - * @param {GenerateContext} context context - * @returns {string | Source | undefined} the source code that will be included as initialization code - */ - getContent({ runtimeRequirements }) { - runtimeRequirements.add(RuntimeGlobals.module); - const promises = this.promises; - if (promises.size === 0) { - return ""; - } - if (promises.size === 1) { - const [p] = promises; - return Template.asString([ - `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${p}]);`, - `${p} = (__webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__)[0];`, - "" - ]); - } - const sepPromises = Array.from(promises).join(", "); - // TODO check if destructuring is supported - return Template.asString([ - `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${sepPromises}]);`, - `([${sepPromises}] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__);`, - "" - ]); - } -} - -module.exports = AwaitDependenciesInitFragment; diff --git a/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js b/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js deleted file mode 100644 index d395e30c474..00000000000 --- a/webpack-lib/lib/async-modules/InferAsyncModulesPlugin.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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 */ - -class InferAsyncModulesPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("InferAsyncModulesPlugin", compilation => { - const { moduleGraph } = compilation; - compilation.hooks.finishModules.tap( - "InferAsyncModulesPlugin", - 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/webpack-lib/lib/buildChunkGraph.js b/webpack-lib/lib/buildChunkGraph.js deleted file mode 100644 index ce2dafebb05..00000000000 --- a/webpack-lib/lib/buildChunkGraph.js +++ /dev/null @@ -1,1344 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError"); -const { connectChunkGroupParentAndChild } = require("./GraphHelpers"); -const ModuleGraphConnection = require("./ModuleGraphConnection"); -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")} Dependency */ -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Entrypoint")} Entrypoint */ -/** @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 */ - -/** - * @typedef {object} QueueItem - * @property {number} action - * @property {DependenciesBlock} block - * @property {Module} module - * @property {Chunk} chunk - * @property {ChunkGroup} chunkGroup - * @property {ChunkGroupInfo} chunkGroupInfo - */ - -/** - * @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 - */ - -/** - * @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); - -/** - * @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; - -/** - * @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; -}; - -/** - * @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; - 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; - } -}; - -/** - * @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; - - const blockModulesRuntimeMap = new Map(); - - /** @type {BlockModulesMap | undefined} */ - let blockModulesMap; - - /** @type {Map} */ - const ordinalByModule = new Map(); - - /** - * @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); - } - - /** - * @param {DependenciesBlock} block block - * @param {RuntimeSpec} runtime runtime - * @returns {BlockModulesInFlattenTuples} block modules in flatten tuples - */ - const getBlockModules = (block, runtime) => { - blockModulesMap = blockModulesRuntimeMap.get(runtime); - if (blockModulesMap === undefined) { - 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) { - const map = memCache.provide( - "bundleChunkGraph.blockModules", - runtime, - () => { - logger.time("visitModules: prepare"); - const map = new Map(); - extractBlockModules(module, moduleGraph, runtime, map); - logger.timeAggregate("visitModules: prepare"); - return map; - } - ); - for (const [block, blockModules] of map) - blockModulesMap.set(block, blockModules); - return map.get(block); - } - logger.time("visitModules: prepare"); - extractBlockModules(module, moduleGraph, runtime, 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(); - - /** @type {Map} */ - const namedChunkGroups = new Map(); - - /** @type {Map} */ - const namedAsyncEntrypoints = 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 = []; - - /** @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 = { - 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 - /** - * @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; - 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) { - entrypoint = compilation.addAsyncEntrypoint( - entryOptions, - module, - /** @type {DependencyLocation} */ (b.loc), - /** @type {string} */ (b.request) - ); - maskByChunk.set(entrypoint.chunks[0], ZERO_BIGINT); - entrypoint.index = nextChunkGroupIndex++; - cgi = { - chunkGroup: entrypoint, - initialized: false, - runtime: entrypoint.options.runtime || 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, cgi); - - chunkGraph.connectBlockAndChunkGroup(b, entrypoint); - if (chunkName) { - namedAsyncEntrypoints.set(chunkName, cgi); - } - } else { - entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup); - // TODO merge entryOptions - 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: 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 = { - 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) { - 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.chunkGroup.addAsyncEntrypoint(entrypoint); - } - }; - - /** - * @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 refOrdinal = /** @type {number} */ getModuleOrdinal(refModule); - 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 (isOrdinalSetInMask(minAvailableModules, refOrdinal)) { - // 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); - } - }; - - /** - * @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; - } - } - } - }; - - /** - * @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) { - 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; - /** - * @param {DependenciesBlock} current current - * @param {BlocksWithNestedBlocks} visited visited dependencies blocks - */ - const process = (current, visited) => { - const blockModules = 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)` - ); -}; - -/** - * @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 ( - // TODO is this needed? - !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 - connectChunkGroupParentAndChild( - originChunkGroupInfo.chunkGroup, - 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/webpack-lib/lib/cli.js b/webpack-lib/lib/cli.js deleted file mode 100644 index c7ff52bc311..00000000000 --- a/webpack-lib/lib/cli.js +++ /dev/null @@ -1,699 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const path = require("path"); -const webpackSchema = require("../schemas/WebpackOptions.json"); - -/** @typedef {TODO & { absolutePath: boolean, instanceof: string, cli: { helper?: boolean, exclude?: boolean } }} Schema */ - -// TODO add originPath to PathItem for better errors -/** - * @typedef {object} PathItem - * @property {any} schema the part of the schema - * @property {string} path the path in the config - */ - -/** @typedef {"unknown-argument" | "unexpected-non-array-in-path" | "unexpected-non-object-in-path" | "multiple-values-unexpected" | "invalid-value"} ProblemType */ - -/** - * @typedef {object} Problem - * @property {ProblemType} type - * @property {string} path - * @property {string} argument - * @property {any=} value - * @property {number=} index - * @property {string=} expected - */ - -/** - * @typedef {object} LocalProblem - * @property {ProblemType} type - * @property {string} path - * @property {string=} expected - */ - -/** - * @typedef {object} ArgumentConfig - * @property {string | undefined} description - * @property {string} [negatedDescription] - * @property {string} path - * @property {boolean} multiple - * @property {"enum"|"string"|"path"|"number"|"boolean"|"RegExp"|"reset"} type - * @property {any[]=} values - */ - -/** @typedef {"string" | "number" | "boolean"} SimpleType */ - -/** - * @typedef {object} Argument - * @property {string | undefined} description - * @property {SimpleType} simpleType - * @property {boolean} multiple - * @property {ArgumentConfig[]} configs - */ - -/** @typedef {string | number | boolean | RegExp | (string | number | boolean | RegExp)} Value */ - -/** @typedef {Record} Flags */ - -/** - * @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 = {}; - - /** - * @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(); - - /** - * @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[newPath[i]]; - - if (!inner) { - break; - } - - schemaPart = inner; - } - - return schemaPart; - }; - - /** - * @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; - } - }; - - /** - * @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; - } - } - }; - - /** - * @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; - } - } - }; - - /** - * @param {Schema} schemaPart schema - * @returns {Pick | undefined} partial argument config - */ - const schemaToArgumentConfig = schemaPart => { - if (schemaPart.enum) { - return { - type: "enum", - values: schemaPart.enum - }; - } - 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; - }; - - /** - * @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)) - }; - }; - - /** - * @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} with ${argConfig.type} type (array type must be before single item type)` - ); - } - return 0; - } - - flags[name].configs.push(argConfig); - - return 1; - }; - - // TODO support `not` and `if/then/else` - // TODO support `const`, but we don't use it on our schema - /** - * @param {Schema} schemaPart the current schema - * @param {string} schemaPath the current path in the schema - * @param {{schema: object, path: string}[]} path all previous visited schemaParts - * @param {string | null} inArray if inside of an array, the path to the array - * @returns {number} added arguments - */ - const traverse = (schemaPart, schemaPath = "", path = [], inArray = null) => { - while (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; - - const fullPath = [{ schema: schemaPart, path: schemaPath }, ...path]; - - let addedArguments = 0; - - addedArguments += addFlag(fullPath, Boolean(inArray)); - - 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 - ); - } - } - - 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 - ); - } - - return addedArguments; - } - - addedArguments += traverse( - /** @type {Schema} */ - (schemaPart.items), - `${schemaPath}[]`, - fullPath, - schemaPath - ); - - if (addedArguments > 0) { - addResetFlag(fullPath); - addedArguments++; - } - - return addedArguments; - } - - const maybeOf = schemaPart.oneOf || schemaPart.anyOf || schemaPart.allOf; - - if (maybeOf) { - const items = maybeOf; - - for (let i = 0; i < items.length; i++) { - addedArguments += traverse( - /** @type {Schema} */ - (items[i]), - schemaPath, - fullPath, - inArray - ); - } - - 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; -}; - -const cliAddedItems = new WeakMap(); - -/** @typedef {string | number} Property */ - -/** - * @param {Configuration} 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?: any, property?: Property, value?: 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; - 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++; - } - 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 }; -}; - -/** - * @param {Configuration} config configuration - * @param {string} schemaPath path in the config - * @param {any} 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; - object[/** @type {Property} */ (property)] = value; - return null; -}; - -/** - * @param {ArgumentConfig} argConfig processing instructions - * @param {Configuration} 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; -}; - -/** - * @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; - } -}; - -/** - * @param {ArgumentConfig} argConfig processing instructions - * @param {Value} value the value - * @returns {any | 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" && /^[+-]?\d*(\.\d*)[eE]\d+$/) { - 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 {NonNullable} */ - (argConfig.values); - if (values.includes(value)) return value; - for (const item of values) { - if (`${item}` === value) return item; - } - break; - } - case "reset": - if (value === true) return []; - break; - } -}; - -/** @typedef {any} Configuration */ - -/** - * @param {Flags} args object of arguments - * @param {Configuration} config configuration - * @param {Record} 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; - } - /** - * @param {Value} value value - * @param {number | undefined} i index - */ - const processValue = (value, i) => { - 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; -}; - -module.exports.getArguments = getArguments; -module.exports.processArguments = processArguments; diff --git a/webpack-lib/lib/config/browserslistTargetHandler.js b/webpack-lib/lib/config/browserslistTargetHandler.js deleted file mode 100644 index 51b0896ab0c..00000000000 --- a/webpack-lib/lib/config/browserslistTargetHandler.js +++ /dev/null @@ -1,363 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const browserslist = require("browserslist"); -const path = require("path"); - -/** @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; - -/** - * @typedef {object} BrowserslistHandlerConfig - * @property {string=} configPath - * @property {string=} env - * @property {string=} query - */ - -/** - * @param {string | null | undefined} input input string - * @param {string} context the context directory - * @returns {BrowserslistHandlerConfig} config - */ -const parse = (input, context) => { - if (!input) { - return {}; - } - - if (path.isAbsolute(input)) { - const [, configPath, env] = inputRx.exec(input) || []; - return { configPath, env }; - } - - const config = browserslist.findConfig(context); - - if (config && Object.keys(config).includes(input)) { - return { env: input }; - } - - return { query: input }; -}; - -/** - * @param {string | null | undefined} input input string - * @param {string} context the context directory - * @returns {string[] | undefined} selected browsers - */ -const load = (input, context) => { - const { configPath, env, query } = parse(input, context); - - // if a query is specified, then use it, else - // if a path to a config is specified then load it, else - // find a nearest config - const config = - query || - (configPath - ? browserslist.loadConfig({ - config: configPath, - env - }) - : browserslist.loadConfig({ path: context, env })); - - if (!config) return; - return browserslist(config); -}; - -/** - * @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; - // Internet Explorer Mobile, Blackberry browser and Opera Mini are very old browsers, they do not support new features - const es6DynamicImport = rawChecker({ - /* eslint-disable camelcase */ - 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] - /* eslint-enable camelcase */ - }); - - 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] - }), - 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: Unknown support - // baidu: Unknown support - // and_uc: Unknown support - 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: Unknown support - // baidu: Unknown support - // and_uc: Unknown support - 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: es6DynamicImport, - dynamicImportInWorker: es6DynamicImport && !anyNode, - // 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: Unknown support - // baidu: Unknown support - // and_uc: Unknown support - 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: Not supported - // baidu: Not supported - // and_uc: Not supported - kaios: [3, 0], - node: 14 - }), - 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: [13, 1], - baidu: [13, 18], - and_uc: [15, 5], - 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: true, - nodeBuiltins: nodeProperty, - nodePrefixForCoreModules: - nodeProperty && - !browsers.some(b => b.startsWith("node 15")) && - rawChecker({ - node: [14, 18] - }), - require: nodeProperty - }; -}; - -module.exports = { - resolve, - load -}; diff --git a/webpack-lib/lib/config/defaults.js b/webpack-lib/lib/config/defaults.js deleted file mode 100644 index f264730144a..00000000000 --- a/webpack-lib/lib/config/defaults.js +++ /dev/null @@ -1,1673 +0,0 @@ -/* - 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 { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JSON_MODULE_TYPE, - WEBASSEMBLY_MODULE_TYPE_ASYNC, - WEBASSEMBLY_MODULE_TYPE_SYNC, - ASSET_MODULE_TYPE, - ASSET_MODULE_TYPE_INLINE, - ASSET_MODULE_TYPE_RESOURCE, - CSS_MODULE_TYPE_AUTO, - CSS_MODULE_TYPE, - CSS_MODULE_TYPE_MODULE, - CSS_MODULE_TYPE_GLOBAL -} = require("../ModuleTypeConstants"); -const Template = require("../Template"); -const { cleverMerge } = require("../util/cleverMerge"); -const { - getTargetsProperties, - getTargetProperties, - getDefaultTarget -} = require("./target"); - -/** @typedef {import("../../declarations/WebpackOptions").CacheOptions} CacheOptions */ -/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptionsNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").Context} Context */ -/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorOptions} CssGeneratorOptions */ -/** @typedef {import("../../declarations/WebpackOptions").CssParserOptions} CssParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").EntryDescription} EntryDescription */ -/** @typedef {import("../../declarations/WebpackOptions").EntryNormalized} Entry */ -/** @typedef {import("../../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */ -/** @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").Library} Library */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../../declarations/WebpackOptions").Loader} Loader */ -/** @typedef {import("../../declarations/WebpackOptions").Mode} Mode */ -/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ -/** @typedef {import("../../declarations/WebpackOptions").Node} WebpackNode */ -/** @typedef {import("../../declarations/WebpackOptions").Optimization} 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").Target} Target */ -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */ -/** @typedef {import("./target").TargetProperties} TargetProperties */ - -/** - * @typedef {object} ResolvedOptions - * @property {PlatformTargetProperties | false} platform - platform target properties - */ - -const NODE_MODULES_REGEXP = /[\\/]node_modules[\\/]/i; -const DEFAULT_CACHE_NAME = "default"; - -/** - * Sets a constant default value when undefined - * @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; - } -}; - -/** - * Sets a dynamic default value when undefined, by calling the factory function - * @template T - * @template {keyof T} P - * @param {T} obj an object - * @param {P} prop a property of this object - * @param {function(): 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 {function(): 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); - } - } - } -}; - -/** - * @param {WebpackOptionsNormalized} options options to be modified - * @returns {void} - */ -const applyWebpackOptionsBaseDefaults = options => { - F(options, "context", () => process.cwd()); - applyInfrastructureLoggingDefaults(options.infrastructureLogging); -}; - -/** - * @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", () => (development ? "eval" : 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, "cache", () => - development ? { type: /** @type {"memory"} */ ("memory") } : false - ); - applyCacheDefaults(options.cache, { - name: name || DEFAULT_CACHE_NAME, - mode: mode || "production", - development, - cacheUnaffected: options.experiments.cacheUnaffected, - 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, - syncWebAssembly: - /** @type {NonNullable} */ - (options.experiments.syncWebAssembly), - asyncWebAssembly: - /** @type {NonNullable} */ - (options.experiments.asyncWebAssembly), - css: - /** @type {NonNullable} */ - (options.experiments.css), - futureDefaults, - isNode: targetProperties && targetProperties.node === true, - uniqueName: options.output.uniqueName, - targetProperties, - mode: options.mode - }); - - applyExternalsPresetsDefaults(options.externalsPresets, { - targetProperties, - buildHttp: Boolean(options.experiments.buildHttp) - }); - - 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) - }), - 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, - nwjs: targetProperties.nwjs, - electron: targetProperties.electron - } - }; -}; - -/** - * @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); - D(experiments, "syncWebAssembly", false); - D(experiments, "asyncWebAssembly", experiments.futureDefaults); - D(experiments, "outputModule", false); - D(experiments, "layers", false); - D(experiments, "lazyCompilation", undefined); - D(experiments, "buildHttp", undefined); - D(experiments, "cacheUnaffected", experiments.futureDefaults); - F(experiments, "css", () => (experiments.futureDefaults ? true : undefined)); - - // TODO webpack 6: remove this. topLevelAwait should be enabled by default - let shouldEnableTopLevelAwait = true; - if (typeof experiments.topLevelAwait === "boolean") { - shouldEnableTopLevelAwait = experiments.topLevelAwait; - } - D(experiments, "topLevelAwait", shouldEnableTopLevelAwait); - - if (typeof experiments.buildHttp === "object") { - D(experiments.buildHttp, "frozen", production); - D(experiments.buildHttp, "upgrade", false); - } -}; - -/** - * @param {CacheOptionsNormalized} cache options - * @param {object} options options - * @param {string} options.name name - * @param {Mode} options.mode mode - * @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 } -) => { - 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", "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; - } -}; - -/** - * @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, "resolve", () => - production ? { timestamp: true, hash: true } : { timestamp: true } - ); -}; - -/** - * @param {JavascriptParserOptions} parserOptions parser options - * @param {object} options options - * @param {boolean} options.futureDefaults is future defaults enabled - * @param {boolean} options.isNode is node target platform - * @returns {void} - */ -const applyJavascriptParserOptionsDefaults = ( - parserOptions, - { futureDefaults, isNode } -) => { - 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", true); - D(parserOptions, "dynamicImportMode", "lazy"); - D(parserOptions, "dynamicImportPrefetch", false); - D(parserOptions, "dynamicImportPreload", false); - D(parserOptions, "dynamicImportFetchPriority", false); - D(parserOptions, "createRequire", isNode); - if (futureDefaults) D(parserOptions, "exportsPresence", "error"); -}; - -/** - * @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); -}; - -/** - * @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.css is css 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 {TargetProperties | false} options.targetProperties target properties - * @param {Mode} options.mode mode - * @returns {void} - */ -const applyModuleDefaults = ( - module, - { - cache, - syncWebAssembly, - asyncWebAssembly, - css, - futureDefaults, - isNode, - uniqueName, - targetProperties, - mode - } -) => { - if (cache) { - D( - module, - "unsafeCache", - /** - * @param {Module} module module - * @returns {boolean | null | string} true, if we want to cache the module - */ - module => { - const name = module.nameForCondition(); - return name && 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( - module.parser[JSON_MODULE_TYPE], - "exportsDepth", - mode === "development" ? 1 : Infinity - ); - - applyJavascriptParserOptionsDefaults( - /** @type {NonNullable} */ - (module.parser.javascript), - { - futureDefaults, - isNode - } - ); - - if (css) { - F(module.parser, CSS_MODULE_TYPE, () => ({})); - - D(module.parser[CSS_MODULE_TYPE], "import", true); - D(module.parser[CSS_MODULE_TYPE], "url", true); - D(module.parser[CSS_MODULE_TYPE], "namedExports", true); - - F(module.generator, CSS_MODULE_TYPE, () => ({})); - - applyCssGeneratorOptionsDefaults( - /** @type {NonNullable} */ - (module.generator[CSS_MODULE_TYPE]), - { targetProperties } - ); - - const localIdentName = - uniqueName.length > 0 ? "[uniqueName]-[id]-[local]" : "[id]-[local]"; - - F(module.generator, CSS_MODULE_TYPE_AUTO, () => ({})); - D(module.generator[CSS_MODULE_TYPE_AUTO], "localIdentName", localIdentName); - D(module.generator[CSS_MODULE_TYPE_AUTO], "exportsConvention", "as-is"); - - F(module.generator, CSS_MODULE_TYPE_MODULE, () => ({})); - D( - module.generator[CSS_MODULE_TYPE_MODULE], - "localIdentName", - localIdentName - ); - D(module.generator[CSS_MODULE_TYPE_MODULE], "exportsConvention", "as-is"); - - F(module.generator, CSS_MODULE_TYPE_GLOBAL, () => ({})); - D( - module.generator[CSS_MODULE_TYPE_GLOBAL], - "localIdentName", - localIdentName - ); - D(module.generator[CSS_MODULE_TYPE_GLOBAL], "exportsConvention", "as-is"); - } - - A(module, "defaultRules", () => { - const esm = { - type: JAVASCRIPT_MODULE_TYPE_ESM, - resolve: { - byDependency: { - esm: { - fullySpecified: true - } - } - } - }; - const commonjs = { - type: JAVASCRIPT_MODULE_TYPE_DYNAMIC - }; - /** @type {RuleSetRules} */ - const rules = [ - { - mimetype: "application/node", - type: JAVASCRIPT_MODULE_TYPE_AUTO - }, - { - test: /\.json$/i, - type: JSON_MODULE_TYPE - }, - { - mimetype: "application/json", - type: JSON_MODULE_TYPE - }, - { - test: /\.mjs$/i, - ...esm - }, - { - test: /\.js$/i, - descriptionData: { - type: "module" - }, - ...esm - }, - { - test: /\.cjs$/i, - ...commonjs - }, - { - test: /\.js$/i, - descriptionData: { - type: "commonjs" - }, - ...commonjs - }, - { - mimetype: { - or: ["text/javascript", "application/javascript"] - }, - ...esm - } - ]; - if (asyncWebAssembly) { - const wasm = { - type: WEBASSEMBLY_MODULE_TYPE_ASYNC, - rules: [ - { - descriptionData: { - type: "module" - }, - resolve: { - fullySpecified: true - } - } - ] - }; - rules.push({ - test: /\.wasm$/i, - ...wasm - }); - rules.push({ - mimetype: "application/wasm", - ...wasm - }); - } else if (syncWebAssembly) { - const wasm = { - type: WEBASSEMBLY_MODULE_TYPE_SYNC, - rules: [ - { - descriptionData: { - type: "module" - }, - resolve: { - fullySpecified: true - } - } - ] - }; - rules.push({ - test: /\.wasm$/i, - ...wasm - }); - rules.push({ - mimetype: "application/wasm", - ...wasm - }); - } - if (css) { - const resolve = { - fullySpecified: true, - preferRelative: true - }; - rules.push({ - test: /\.css$/i, - type: CSS_MODULE_TYPE_AUTO, - resolve - }); - rules.push({ - mimetype: "text/css+module", - type: CSS_MODULE_TYPE_MODULE, - resolve - }); - rules.push({ - mimetype: "text/css", - type: CSS_MODULE_TYPE, - resolve - }); - } - rules.push( - { - dependency: "url", - oneOf: [ - { - scheme: /^data$/, - type: ASSET_MODULE_TYPE_INLINE - }, - { - type: ASSET_MODULE_TYPE_RESOURCE - } - ] - }, - { - assert: { type: JSON_MODULE_TYPE }, - type: JSON_MODULE_TYPE - }, - { - with: { type: JSON_MODULE_TYPE }, - type: JSON_MODULE_TYPE - } - ); - return rules; - }); -}; - -/** - * @param {Output} output options - * @param {object} options options - * @param {string} options.context context - * @param {TargetProperties | false} options.targetProperties target properties - * @param {boolean} options.isAffectedByBrowserslist is affected by browserslist - * @param {boolean} options.outputModule is outputModule experiment enabled - * @param {boolean} options.development is development mode - * @param {Entry} options.entry entry option - * @param {boolean} options.futureDefaults is future defaults enabled - * @param {boolean} options.asyncWebAssembly is asyncWebAssembly enabled - * @returns {void} - */ -const applyOutputDefaults = ( - output, - { - context, - targetProperties: tp, - isAffectedByBrowserslist, - outputModule, - development, - entry, - futureDefaults, - asyncWebAssembly - } -) => { - /** - * @param {Library=} library the library option - * @returns {string} a readable library name - */ - const getLibraryName = library => { - const libraryName = - typeof library === "object" && - library && - !Array.isArray(library) && - "type" in library - ? library.name - : /** @type {LibraryName} */ (library); - if (Array.isArray(libraryName)) { - return libraryName.join("."); - } else if (typeof libraryName === "object") { - return getLibraryName(libraryName.root); - } else if (typeof libraryName === "string") { - return libraryName; - } - return ""; - }; - - F(output, "uniqueName", () => { - const libraryName = getLibraryName(output.library).replace( - /^\[(\\*[\w:]+\\*)\](\.)|(\.)\[(\\*[\w:]+\\*)\](?=\.|$)|\[(\\*[\w:]+\\*)\]/g, - (m, a, d1, d2, b, c) => { - const content = a || b || c; - return content.startsWith("\\") && content.endsWith("\\") - ? `${d2 || ""}[${content.slice(1, -1)}]${d1 || ""}` - : ""; - } - ); - if (libraryName) return libraryName; - const pkgPath = path.resolve(context, "package.json"); - try { - const packageInfo = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); - return packageInfo.name || ""; - } catch (err) { - if (/** @type {Error & { code: string }} */ (err).code !== "ENOENT") { - /** @type {Error & { code: string }} */ - (err).message += - `\nwhile determining default 'output.uniqueName' from 'name' in ${pkgPath}`; - throw err; - } - return ""; - } - }); - - F(output, "module", () => Boolean(outputModule)); - - const environment = /** @type {Environment} */ (output.environment); - /** - * @param {boolean | undefined} v value - * @returns {boolean} true, when v is truthy or undefined - */ - const optimistic = v => v || v === undefined; - /** - * @param {boolean | undefined} v value - * @param {boolean | undefined} c condition - * @returns {boolean | undefined} true, when v is truthy or undefined, or c is truthy - */ - const conditionallyOptimistic = (v, c) => (v === undefined && c) || v; - - F( - environment, - "globalThis", - () => /** @type {boolean | undefined} */ (tp && tp.globalThis) - ); - F( - environment, - "bigIntLiteral", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.bigIntLiteral)) - ); - F( - environment, - "const", - () => tp && optimistic(/** @type {boolean | undefined} */ (tp.const)) - ); - F( - environment, - "arrowFunction", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.arrowFunction)) - ); - F( - environment, - "asyncFunction", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.asyncFunction)) - ); - F( - environment, - "forOf", - () => tp && optimistic(/** @type {boolean | undefined} */ (tp.forOf)) - ); - F( - environment, - "destructuring", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.destructuring)) - ); - F( - environment, - "optionalChaining", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.optionalChaining)) - ); - F( - environment, - "nodePrefixForCoreModules", - () => - tp && - optimistic( - /** @type {boolean | undefined} */ (tp.nodePrefixForCoreModules) - ) - ); - F( - environment, - "templateLiteral", - () => - tp && optimistic(/** @type {boolean | undefined} */ (tp.templateLiteral)) - ); - F(environment, "dynamicImport", () => - conditionallyOptimistic( - /** @type {boolean | undefined} */ (tp && tp.dynamicImport), - output.module - ) - ); - F(environment, "dynamicImportInWorker", () => - conditionallyOptimistic( - /** @type {boolean | undefined} */ (tp && tp.dynamicImportInWorker), - output.module - ) - ); - F(environment, "module", () => - conditionallyOptimistic( - /** @type {boolean | undefined} */ (tp && tp.module), - output.module - ) - ); - F( - environment, - "document", - () => tp && optimistic(/** @type {boolean | undefined} */ (tp.document)) - ); - - D(output, "filename", output.module ? "[name].mjs" : "[name].js"); - F(output, "iife", () => !output.module); - D(output, "importFunctionName", "import"); - D(output, "importMetaName", "import.meta"); - F(output, "chunkFilename", () => { - const filename = - /** @type {NonNullable} */ - (output.filename); - if (typeof filename !== "function") { - const hasName = filename.includes("[name]"); - const hasId = filename.includes("[id]"); - const hasChunkHash = filename.includes("[chunkhash]"); - const hasContentHash = filename.includes("[contenthash]"); - // Anything changing depending on chunk is fine - if (hasChunkHash || hasContentHash || hasName || hasId) return filename; - // Otherwise prefix "[id]." in front of the basename to make it changing - return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2"); - } - return output.module ? "[id].mjs" : "[id].js"; - }); - F(output, "cssFilename", () => { - const filename = - /** @type {NonNullable} */ - (output.filename); - if (typeof filename !== "function") { - return filename.replace(/\.[mc]?js(\?|$)/, ".css$1"); - } - return "[id].css"; - }); - F(output, "cssChunkFilename", () => { - const chunkFilename = - /** @type {NonNullable} */ - (output.chunkFilename); - if (typeof chunkFilename !== "function") { - return chunkFilename.replace(/\.[mc]?js(\?|$)/, ".css$1"); - } - return "[id].css"; - }); - D(output, "assetModuleFilename", "[hash][ext][query]"); - D(output, "webassemblyModuleFilename", "[hash].module.wasm"); - D(output, "compareBeforeEmit", true); - D(output, "charset", true); - const uniqueNameId = Template.toIdentifier( - /** @type {NonNullable} */ (output.uniqueName) - ); - F(output, "hotUpdateGlobal", () => `webpackHotUpdate${uniqueNameId}`); - F(output, "chunkLoadingGlobal", () => `webpackChunk${uniqueNameId}`); - F(output, "globalObject", () => { - if (tp) { - if (tp.global) return "global"; - if (tp.globalThis) return "globalThis"; - } - return "self"; - }); - F(output, "chunkFormat", () => { - if (tp) { - const helpMessage = isAffectedByBrowserslist - ? "Make sure that your 'browserslist' includes only platforms that support these features or select an appropriate 'target' to allow selecting a chunk format by default. Alternatively specify the 'output.chunkFormat' directly." - : "Select an appropriate 'target' to allow selecting one by default, or specify the 'output.chunkFormat' directly."; - if (output.module) { - if (environment.dynamicImport) return "module"; - if (tp.document) return "array-push"; - throw new Error( - "For the selected environment is no default ESM chunk format available:\n" + - "ESM exports can be chosen when 'import()' is available.\n" + - `JSONP Array push can be chosen when 'document' is available.\n${ - helpMessage - }` - ); - } else { - if (tp.document) return "array-push"; - if (tp.require) return "commonjs"; - if (tp.nodeBuiltins) return "commonjs"; - if (tp.importScripts) return "array-push"; - throw new Error( - "For the selected environment is no default script chunk format available:\n" + - "JSONP Array push can be chosen when 'document' or 'importScripts' is available.\n" + - `CommonJs exports can be chosen when 'require' or node builtins are available.\n${ - helpMessage - }` - ); - } - } - throw new Error( - "Chunk format can't be selected by default when no target is specified" - ); - }); - D(output, "asyncChunks", true); - F(output, "chunkLoading", () => { - if (tp) { - switch (output.chunkFormat) { - case "array-push": - if (tp.document) return "jsonp"; - if (tp.importScripts) return "import-scripts"; - break; - case "commonjs": - if (tp.require) return "require"; - if (tp.nodeBuiltins) return "async-node"; - break; - case "module": - if (environment.dynamicImport) return "import"; - break; - } - if ( - (tp.require === null || - tp.nodeBuiltins === null || - tp.document === null || - tp.importScripts === null) && - output.module && - environment.dynamicImport - ) { - return "universal"; - } - } - return false; - }); - F(output, "workerChunkLoading", () => { - if (tp) { - switch (output.chunkFormat) { - case "array-push": - if (tp.importScriptsInWorker) return "import-scripts"; - break; - case "commonjs": - if (tp.require) return "require"; - if (tp.nodeBuiltins) return "async-node"; - break; - case "module": - if (environment.dynamicImportInWorker) return "import"; - break; - } - if ( - (tp.require === null || - tp.nodeBuiltins === null || - tp.importScriptsInWorker === null) && - output.module && - environment.dynamicImport - ) { - return "universal"; - } - } - return false; - }); - F(output, "wasmLoading", () => { - if (tp) { - if (tp.fetchWasm) return "fetch"; - if (tp.nodeBuiltins) return "async-node"; - if ( - (tp.nodeBuiltins === null || tp.fetchWasm === null) && - asyncWebAssembly && - output.module && - environment.dynamicImport - ) { - return "universal"; - } - } - return false; - }); - F(output, "workerWasmLoading", () => output.wasmLoading); - F(output, "devtoolNamespace", () => output.uniqueName); - if (output.library) { - F(output.library, "type", () => (output.module ? "module" : "var")); - } - F(output, "path", () => path.join(process.cwd(), "dist")); - F(output, "pathinfo", () => development); - D(output, "sourceMapFilename", "[file].map[query]"); - D( - output, - "hotUpdateChunkFilename", - `[id].[fullhash].hot-update.${output.module ? "mjs" : "js"}` - ); - D(output, "hotUpdateMainFilename", "[runtime].[fullhash].hot-update.json"); - D(output, "crossOriginLoading", false); - F(output, "scriptType", () => (output.module ? "module" : false)); - D( - output, - "publicPath", - (tp && (tp.document || tp.importScripts)) || output.scriptType === "module" - ? "auto" - : "" - ); - D(output, "workerPublicPath", ""); - D(output, "chunkLoadTimeout", 120000); - D(output, "hashFunction", futureDefaults ? "xxhash64" : "md4"); - D(output, "hashDigest", "hex"); - D(output, "hashDigestLength", futureDefaults ? 16 : 20); - D(output, "strictModuleErrorHandling", false); - D(output, "strictModuleExceptionHandling", false); - - const { trustedTypes } = output; - if (trustedTypes) { - F( - trustedTypes, - "policyName", - () => - /** @type {NonNullable} */ - (output.uniqueName).replace(/[^a-zA-Z0-9\-#=_/@.%]+/g, "_") || "webpack" - ); - D(trustedTypes, "onPolicyCreationFailure", "stop"); - } - - /** - * @param {function(EntryDescription): void} fn iterator - * @returns {void} - */ - const forEachEntry = fn => { - for (const name of Object.keys(entry)) { - fn(/** @type {{[k: string] : EntryDescription}} */ (entry)[name]); - } - }; - A(output, "enabledLibraryTypes", () => { - /** @type {LibraryType[]} */ - const enabledLibraryTypes = []; - if (output.library) { - enabledLibraryTypes.push(output.library.type); - } - forEachEntry(desc => { - if (desc.library) { - enabledLibraryTypes.push(desc.library.type); - } - }); - return enabledLibraryTypes; - }); - - A(output, "enabledChunkLoadingTypes", () => { - const enabledChunkLoadingTypes = new Set(); - if (output.chunkLoading) { - enabledChunkLoadingTypes.add(output.chunkLoading); - } - if (output.workerChunkLoading) { - enabledChunkLoadingTypes.add(output.workerChunkLoading); - } - forEachEntry(desc => { - if (desc.chunkLoading) { - enabledChunkLoadingTypes.add(desc.chunkLoading); - } - }); - return Array.from(enabledChunkLoadingTypes); - }); - - A(output, "enabledWasmLoadingTypes", () => { - const enabledWasmLoadingTypes = new Set(); - if (output.wasmLoading) { - enabledWasmLoadingTypes.add(output.wasmLoading); - } - if (output.workerWasmLoading) { - enabledWasmLoadingTypes.add(output.workerWasmLoading); - } - forEachEntry(desc => { - if (desc.wasmLoading) { - enabledWasmLoadingTypes.add(desc.wasmLoading); - } - }); - return Array.from(enabledWasmLoadingTypes); - }); -}; - -/** - * @param {ExternalsPresets} externalsPresets options - * @param {object} options options - * @param {TargetProperties | false} options.targetProperties target properties - * @param {boolean} options.buildHttp buildHttp experiment enabled - * @returns {void} - */ -const applyExternalsPresetsDefaults = ( - externalsPresets, - { targetProperties, buildHttp } -) => { - D( - externalsPresets, - "web", - /** @type {boolean | undefined} */ - (!buildHttp && targetProperties && targetProperties.web) - ); - D( - externalsPresets, - "node", - /** @type {boolean | undefined} */ - (targetProperties && targetProperties.node) - ); - D( - externalsPresets, - "nwjs", - /** @type {boolean | undefined} */ - (targetProperties && targetProperties.nwjs) - ); - D( - externalsPresets, - "electron", - /** @type {boolean | undefined} */ - (targetProperties && targetProperties.electron) - ); - D( - externalsPresets, - "electronMain", - /** @type {boolean | undefined} */ - ( - targetProperties && - targetProperties.electron && - targetProperties.electronMain - ) - ); - D( - externalsPresets, - "electronPreload", - /** @type {boolean | undefined} */ - ( - targetProperties && - targetProperties.electron && - targetProperties.electronPreload - ) - ); - D( - externalsPresets, - "electronRenderer", - /** @type {boolean | undefined} */ - ( - targetProperties && - targetProperties.electron && - targetProperties.electronRenderer - ) - ); -}; - -/** - * @param {Loader} loader options - * @param {object} options options - * @param {TargetProperties | false} options.targetProperties target properties - * @param {Environment} options.environment environment - * @returns {void} - */ -const applyLoaderDefaults = (loader, { targetProperties, environment }) => { - F(loader, "target", () => { - if (targetProperties) { - if (targetProperties.electron) { - if (targetProperties.electronMain) return "electron-main"; - if (targetProperties.electronPreload) return "electron-preload"; - if (targetProperties.electronRenderer) return "electron-renderer"; - return "electron"; - } - if (targetProperties.nwjs) return "nwjs"; - if (targetProperties.node) return "node"; - if (targetProperties.web) return "web"; - } - }); - D(loader, "environment", environment); -}; - -/** - * @param {WebpackNode} node options - * @param {object} options options - * @param {TargetProperties | false} options.targetProperties target properties - * @param {boolean} options.futureDefaults is future defaults enabled - * @param {boolean} options.outputModule is output type is module - * @returns {void} - */ -const applyNodeDefaults = ( - node, - { futureDefaults, outputModule, targetProperties } -) => { - if (node === false) return; - - F(node, "global", () => { - if (targetProperties && targetProperties.global) return false; - // TODO webpack 6 should always default to false - return futureDefaults ? "warn" : true; - }); - - const handlerForNames = () => { - if (targetProperties && targetProperties.node) - return outputModule ? "node-module" : "eval-only"; - // TODO webpack 6 should always default to false - return futureDefaults ? "warn-mock" : "mock"; - }; - - F(node, "__filename", handlerForNames); - F(node, "__dirname", handlerForNames); -}; - -/** - * @param {Performance} performance options - * @param {object} options options - * @param {boolean} options.production is production - * @returns {void} - */ -const applyPerformanceDefaults = (performance, { production }) => { - if (performance === false) return; - D(performance, "maxAssetSize", 250000); - D(performance, "maxEntrypointSize", 250000); - F(performance, "hints", () => (production ? "warning" : false)); -}; - -/** - * @param {Optimization} optimization options - * @param {object} options options - * @param {boolean} options.production is production - * @param {boolean} options.development is development - * @param {boolean} options.css is css enabled - * @param {boolean} options.records using records - * @returns {void} - */ -const applyOptimizationDefaults = ( - optimization, - { production, development, css, records } -) => { - D(optimization, "removeAvailableModules", false); - D(optimization, "removeEmptyChunks", true); - D(optimization, "mergeDuplicateChunks", true); - D(optimization, "flagIncludedChunks", production); - F(optimization, "moduleIds", () => { - if (production) return "deterministic"; - if (development) return "named"; - return "natural"; - }); - F(optimization, "chunkIds", () => { - if (production) return "deterministic"; - if (development) return "named"; - return "natural"; - }); - F(optimization, "sideEffects", () => (production ? true : "flag")); - D(optimization, "providedExports", true); - D(optimization, "usedExports", production); - D(optimization, "innerGraph", production); - D(optimization, "mangleExports", production); - D(optimization, "concatenateModules", production); - D(optimization, "avoidEntryIife", production); - D(optimization, "runtimeChunk", false); - D(optimization, "emitOnErrors", !production); - D(optimization, "checkWasmTypes", production); - D(optimization, "mangleWasmImports", false); - D(optimization, "portableRecords", records); - D(optimization, "realContentHash", production); - D(optimization, "minimize", production); - A(optimization, "minimizer", () => [ - { - apply: compiler => { - // Lazy load the Terser plugin - const TerserPlugin = require("terser-webpack-plugin"); - new TerserPlugin({ - terserOptions: { - compress: { - passes: 2 - } - } - }).apply(compiler); - } - } - ]); - F(optimization, "nodeEnv", () => { - if (production) return "production"; - if (development) return "development"; - return false; - }); - const { splitChunks } = optimization; - if (splitChunks) { - A(splitChunks, "defaultSizeTypes", () => - css ? ["javascript", "css", "unknown"] : ["javascript", "unknown"] - ); - D(splitChunks, "hidePathInfo", production); - D(splitChunks, "chunks", "async"); - D(splitChunks, "usedExports", optimization.usedExports === true); - D(splitChunks, "minChunks", 1); - F(splitChunks, "minSize", () => (production ? 20000 : 10000)); - F(splitChunks, "minRemainingSize", () => (development ? 0 : undefined)); - F(splitChunks, "enforceSizeThreshold", () => (production ? 50000 : 30000)); - F(splitChunks, "maxAsyncRequests", () => (production ? 30 : Infinity)); - F(splitChunks, "maxInitialRequests", () => (production ? 30 : Infinity)); - D(splitChunks, "automaticNameDelimiter", "-"); - const cacheGroups = - /** @type {NonNullable} */ - (splitChunks.cacheGroups); - F(cacheGroups, "default", () => ({ - idHint: "", - reuseExistingChunk: true, - minChunks: 2, - priority: -20 - })); - F(cacheGroups, "defaultVendors", () => ({ - idHint: "vendors", - reuseExistingChunk: true, - test: NODE_MODULES_REGEXP, - priority: -10 - })); - } -}; - -/** - * @param {object} options options - * @param {boolean} options.cache is cache enable - * @param {string} options.context build context - * @param {TargetProperties | false} options.targetProperties target properties - * @param {Mode} options.mode mode - * @param {boolean} options.css is css enabled - * @returns {ResolveOptions} resolve options - */ -const getResolveDefaults = ({ - cache, - context, - targetProperties, - mode, - css -}) => { - /** @type {string[]} */ - const conditions = ["webpack"]; - - conditions.push(mode === "development" ? "development" : "production"); - - if (targetProperties) { - if (targetProperties.webworker) conditions.push("worker"); - if (targetProperties.node) conditions.push("node"); - if (targetProperties.web) conditions.push("browser"); - if (targetProperties.electron) conditions.push("electron"); - if (targetProperties.nwjs) conditions.push("nwjs"); - } - - const jsExtensions = [".js", ".json", ".wasm"]; - - const tp = targetProperties; - const browserField = - tp && tp.web && (!tp.node || (tp.electron && tp.electronRenderer)); - - /** @type {function(): ResolveOptions} */ - const cjsDeps = () => ({ - aliasFields: browserField ? ["browser"] : [], - mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."], - conditionNames: ["require", "module", "..."], - extensions: [...jsExtensions] - }); - /** @type {function(): ResolveOptions} */ - const esmDeps = () => ({ - aliasFields: browserField ? ["browser"] : [], - mainFields: browserField ? ["browser", "module", "..."] : ["module", "..."], - conditionNames: ["import", "module", "..."], - extensions: [...jsExtensions] - }); - - /** @type {ResolveOptions} */ - const resolveOptions = { - cache, - modules: ["node_modules"], - conditionNames: conditions, - mainFiles: ["index"], - extensions: [], - aliasFields: [], - exportsFields: ["exports"], - roots: [context], - mainFields: ["main"], - importsFields: ["imports"], - byDependency: { - wasm: esmDeps(), - esm: esmDeps(), - loaderImport: esmDeps(), - url: { - preferRelative: true - }, - worker: { - ...esmDeps(), - preferRelative: true - }, - commonjs: cjsDeps(), - amd: cjsDeps(), - // for backward-compat: loadModule - loader: cjsDeps(), - // for backward-compat: Custom Dependency - unknown: cjsDeps(), - // for backward-compat: getResolve without dependencyType - undefined: cjsDeps() - } - }; - - if (css) { - const styleConditions = []; - - styleConditions.push("webpack"); - styleConditions.push(mode === "development" ? "development" : "production"); - styleConditions.push("style"); - - resolveOptions.byDependency["css-import"] = { - // We avoid using any main files because we have to be consistent with CSS `@import` - // and CSS `@import` does not handle `main` files in directories, - // you should always specify the full URL for styles - mainFiles: [], - mainFields: ["style", "..."], - conditionNames: styleConditions, - extensions: [".css"], - preferRelative: true - }; - } - - return resolveOptions; -}; - -/** - * @param {object} options options - * @param {boolean} options.cache is cache enable - * @returns {ResolveOptions} resolve options - */ -const getResolveLoaderDefaults = ({ cache }) => { - /** @type {ResolveOptions} */ - const resolveOptions = { - cache, - conditionNames: ["loader", "require", "node"], - exportsFields: ["exports"], - mainFields: ["loader", "main"], - extensions: [".js"], - mainFiles: ["index"] - }; - - return resolveOptions; -}; - -/** - * @param {InfrastructureLogging} infrastructureLogging options - * @returns {void} - */ -const applyInfrastructureLoggingDefaults = infrastructureLogging => { - F(infrastructureLogging, "stream", () => process.stderr); - const tty = - /** @type {any} */ (infrastructureLogging.stream).isTTY && - process.env.TERM !== "dumb"; - D(infrastructureLogging, "level", "info"); - D(infrastructureLogging, "debug", false); - D(infrastructureLogging, "colors", tty); - D(infrastructureLogging, "appendOnly", !tty); -}; - -module.exports.applyWebpackOptionsBaseDefaults = - applyWebpackOptionsBaseDefaults; -module.exports.applyWebpackOptionsDefaults = applyWebpackOptionsDefaults; diff --git a/webpack-lib/lib/config/normalization.js b/webpack-lib/lib/config/normalization.js deleted file mode 100644 index 3ed0c81320b..00000000000 --- a/webpack-lib/lib/config/normalization.js +++ /dev/null @@ -1,558 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); - -/** @typedef {import("../../declarations/WebpackOptions").CacheOptionsNormalized} CacheOptions */ -/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").EntryStatic} EntryStatic */ -/** @typedef {import("../../declarations/WebpackOptions").EntryStaticNormalized} EntryStaticNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").Externals} Externals */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptionsNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunk} OptimizationRuntimeChunk */ -/** @typedef {import("../../declarations/WebpackOptions").OptimizationRuntimeChunkNormalized} OptimizationRuntimeChunkNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputNormalized */ -/** @typedef {import("../../declarations/WebpackOptions").Plugins} Plugins */ -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ -/** @typedef {import("../Entrypoint")} Entrypoint */ - -const handledDeprecatedNoEmitOnErrors = util.deprecate( - /** - * @param {boolean} noEmitOnErrors no emit on errors - * @param {boolean | undefined} emitOnErrors emit on errors - * @returns {boolean} emit on errors - */ - (noEmitOnErrors, emitOnErrors) => { - if (emitOnErrors !== undefined && !noEmitOnErrors === !emitOnErrors) { - throw new Error( - "Conflicting use of 'optimization.noEmitOnErrors' and 'optimization.emitOnErrors'. Remove deprecated 'optimization.noEmitOnErrors' from config." - ); - } - return !noEmitOnErrors; - }, - "optimization.noEmitOnErrors is deprecated in favor of optimization.emitOnErrors", - "DEP_WEBPACK_CONFIGURATION_OPTIMIZATION_NO_EMIT_ON_ERRORS" -); - -/** - * @template T - * @template R - * @param {T|undefined} value value or not - * @param {function(T): R} fn nested handler - * @returns {R} result value - */ -const nestedConfig = (value, fn) => - value === undefined ? fn(/** @type {T} */ ({})) : fn(value); - -/** - * @template T - * @param {T|undefined} value value or not - * @returns {T} result value - */ -const cloneObject = value => /** @type {T} */ ({ ...value }); -/** - * @template T - * @template R - * @param {T|undefined} value value or not - * @param {function(T): R} fn nested handler - * @returns {R|undefined} result value - */ -const optionalNestedConfig = (value, fn) => - value === undefined ? undefined : fn(value); - -/** - * @template T - * @template R - * @param {T[]|undefined} value array or not - * @param {function(T[]): R[]} fn nested handler - * @returns {R[]|undefined} cloned value - */ -const nestedArray = (value, fn) => (Array.isArray(value) ? fn(value) : fn([])); - -/** - * @template T - * @template R - * @param {T[]|undefined} value array or not - * @param {function(T[]): R[]} fn nested handler - * @returns {R[]|undefined} cloned value - */ -const optionalNestedArray = (value, fn) => - Array.isArray(value) ? fn(value) : undefined; - -/** - * @template T - * @template R - * @param {Record|undefined} value value or not - * @param {function(T): R} fn nested handler - * @param {Record=} customKeys custom nested handler for some keys - * @returns {Record} result value - */ -const keyedNestedConfig = (value, fn, customKeys) => { - /* eslint-disable no-sequences */ - const result = - value === undefined - ? {} - : Object.keys(value).reduce( - (obj, key) => ( - (obj[key] = ( - customKeys && key in customKeys ? customKeys[key] : fn - )(value[key])), - obj - ), - /** @type {Record} */ ({}) - ); - /* eslint-enable no-sequences */ - if (customKeys) { - for (const key of Object.keys(customKeys)) { - if (!(key in result)) { - result[key] = customKeys[key](/** @type {T} */ ({})); - } - } - } - return result; -}; - -/** - * @param {WebpackOptions} config input config - * @returns {WebpackOptionsNormalized} normalized options - */ -const getNormalizedWebpackOptions = config => ({ - amd: config.amd, - bail: config.bail, - cache: - /** @type {NonNullable} */ - ( - optionalNestedConfig(config.cache, cache => { - if (cache === false) return false; - if (cache === true) { - return { - type: "memory", - maxGenerations: undefined - }; - } - switch (cache.type) { - case "filesystem": - return { - type: "filesystem", - allowCollectingMemory: cache.allowCollectingMemory, - maxMemoryGenerations: cache.maxMemoryGenerations, - maxAge: cache.maxAge, - profile: cache.profile, - buildDependencies: cloneObject(cache.buildDependencies), - cacheDirectory: cache.cacheDirectory, - cacheLocation: cache.cacheLocation, - hashAlgorithm: cache.hashAlgorithm, - compression: cache.compression, - idleTimeout: cache.idleTimeout, - idleTimeoutForInitialStore: cache.idleTimeoutForInitialStore, - idleTimeoutAfterLargeChanges: cache.idleTimeoutAfterLargeChanges, - name: cache.name, - store: cache.store, - version: cache.version, - readonly: cache.readonly - }; - case undefined: - case "memory": - return { - type: "memory", - maxGenerations: cache.maxGenerations - }; - default: - // @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339) - throw new Error(`Not implemented cache.type ${cache.type}`); - } - }) - ), - context: config.context, - dependencies: config.dependencies, - devServer: optionalNestedConfig(config.devServer, devServer => { - if (devServer === false) return false; - return { ...devServer }; - }), - devtool: config.devtool, - entry: - config.entry === undefined - ? { main: {} } - : typeof config.entry === "function" - ? ( - fn => () => - Promise.resolve().then(fn).then(getNormalizedEntryStatic) - )(config.entry) - : getNormalizedEntryStatic(config.entry), - experiments: nestedConfig(config.experiments, experiments => ({ - ...experiments, - buildHttp: optionalNestedConfig(experiments.buildHttp, options => - Array.isArray(options) ? { allowedUris: options } : options - ), - lazyCompilation: optionalNestedConfig( - experiments.lazyCompilation, - options => (options === true ? {} : options) - ) - })), - externals: /** @type {NonNullable} */ (config.externals), - externalsPresets: cloneObject(config.externalsPresets), - externalsType: config.externalsType, - ignoreWarnings: config.ignoreWarnings - ? config.ignoreWarnings.map(ignore => { - if (typeof ignore === "function") return ignore; - const i = ignore instanceof RegExp ? { message: ignore } : ignore; - return (warning, { requestShortener }) => { - if (!i.message && !i.module && !i.file) return false; - if (i.message && !i.message.test(warning.message)) { - return false; - } - if ( - i.module && - (!warning.module || - !i.module.test( - warning.module.readableIdentifier(requestShortener) - )) - ) { - return false; - } - if (i.file && (!warning.file || !i.file.test(warning.file))) { - return false; - } - return true; - }; - }) - : undefined, - infrastructureLogging: cloneObject(config.infrastructureLogging), - loader: cloneObject(config.loader), - mode: config.mode, - module: - /** @type {ModuleOptionsNormalized} */ - ( - nestedConfig(config.module, module => ({ - noParse: module.noParse, - unsafeCache: module.unsafeCache, - parser: keyedNestedConfig(module.parser, cloneObject, { - javascript: parserOptions => ({ - unknownContextRequest: module.unknownContextRequest, - unknownContextRegExp: module.unknownContextRegExp, - unknownContextRecursive: module.unknownContextRecursive, - unknownContextCritical: module.unknownContextCritical, - exprContextRequest: module.exprContextRequest, - exprContextRegExp: module.exprContextRegExp, - exprContextRecursive: module.exprContextRecursive, - exprContextCritical: module.exprContextCritical, - wrappedContextRegExp: module.wrappedContextRegExp, - wrappedContextRecursive: module.wrappedContextRecursive, - wrappedContextCritical: module.wrappedContextCritical, - // TODO webpack 6 remove - strictExportPresence: module.strictExportPresence, - strictThisContextOnImports: module.strictThisContextOnImports, - ...parserOptions - }) - }), - generator: cloneObject(module.generator), - defaultRules: optionalNestedArray(module.defaultRules, r => [...r]), - rules: nestedArray(module.rules, r => [...r]) - })) - ), - name: config.name, - node: nestedConfig( - config.node, - node => - node && { - ...node - } - ), - optimization: nestedConfig(config.optimization, optimization => ({ - ...optimization, - runtimeChunk: getNormalizedOptimizationRuntimeChunk( - optimization.runtimeChunk - ), - splitChunks: nestedConfig( - optimization.splitChunks, - splitChunks => - splitChunks && { - ...splitChunks, - defaultSizeTypes: splitChunks.defaultSizeTypes - ? [...splitChunks.defaultSizeTypes] - : ["..."], - cacheGroups: cloneObject(splitChunks.cacheGroups) - } - ), - emitOnErrors: - optimization.noEmitOnErrors !== undefined - ? handledDeprecatedNoEmitOnErrors( - optimization.noEmitOnErrors, - optimization.emitOnErrors - ) - : optimization.emitOnErrors - })), - output: nestedConfig(config.output, output => { - const { library } = output; - const libraryAsName = /** @type {LibraryName} */ (library); - const libraryBase = - typeof library === "object" && - library && - !Array.isArray(library) && - "type" in library - ? library - : libraryAsName || output.libraryTarget - ? /** @type {LibraryOptions} */ ({ - name: libraryAsName - }) - : undefined; - /** @type {OutputNormalized} */ - const result = { - assetModuleFilename: output.assetModuleFilename, - asyncChunks: output.asyncChunks, - charset: output.charset, - chunkFilename: output.chunkFilename, - chunkFormat: output.chunkFormat, - chunkLoading: output.chunkLoading, - chunkLoadingGlobal: output.chunkLoadingGlobal, - chunkLoadTimeout: output.chunkLoadTimeout, - cssFilename: output.cssFilename, - cssChunkFilename: output.cssChunkFilename, - clean: output.clean, - compareBeforeEmit: output.compareBeforeEmit, - crossOriginLoading: output.crossOriginLoading, - devtoolFallbackModuleFilenameTemplate: - output.devtoolFallbackModuleFilenameTemplate, - devtoolModuleFilenameTemplate: output.devtoolModuleFilenameTemplate, - devtoolNamespace: output.devtoolNamespace, - environment: cloneObject(output.environment), - enabledChunkLoadingTypes: output.enabledChunkLoadingTypes - ? [...output.enabledChunkLoadingTypes] - : ["..."], - enabledLibraryTypes: output.enabledLibraryTypes - ? [...output.enabledLibraryTypes] - : ["..."], - enabledWasmLoadingTypes: output.enabledWasmLoadingTypes - ? [...output.enabledWasmLoadingTypes] - : ["..."], - filename: output.filename, - globalObject: output.globalObject, - hashDigest: output.hashDigest, - hashDigestLength: output.hashDigestLength, - hashFunction: output.hashFunction, - hashSalt: output.hashSalt, - hotUpdateChunkFilename: output.hotUpdateChunkFilename, - hotUpdateGlobal: output.hotUpdateGlobal, - hotUpdateMainFilename: output.hotUpdateMainFilename, - ignoreBrowserWarnings: output.ignoreBrowserWarnings, - iife: output.iife, - importFunctionName: output.importFunctionName, - importMetaName: output.importMetaName, - scriptType: output.scriptType, - library: libraryBase && { - type: - output.libraryTarget !== undefined - ? output.libraryTarget - : libraryBase.type, - auxiliaryComment: - output.auxiliaryComment !== undefined - ? output.auxiliaryComment - : libraryBase.auxiliaryComment, - amdContainer: - output.amdContainer !== undefined - ? output.amdContainer - : libraryBase.amdContainer, - export: - output.libraryExport !== undefined - ? output.libraryExport - : libraryBase.export, - name: libraryBase.name, - umdNamedDefine: - output.umdNamedDefine !== undefined - ? output.umdNamedDefine - : libraryBase.umdNamedDefine - }, - module: output.module, - path: output.path, - pathinfo: output.pathinfo, - publicPath: output.publicPath, - sourceMapFilename: output.sourceMapFilename, - sourcePrefix: output.sourcePrefix, - strictModuleErrorHandling: output.strictModuleErrorHandling, - strictModuleExceptionHandling: output.strictModuleExceptionHandling, - trustedTypes: optionalNestedConfig(output.trustedTypes, trustedTypes => { - if (trustedTypes === true) return {}; - if (typeof trustedTypes === "string") - return { policyName: trustedTypes }; - return { ...trustedTypes }; - }), - uniqueName: output.uniqueName, - wasmLoading: output.wasmLoading, - webassemblyModuleFilename: output.webassemblyModuleFilename, - workerPublicPath: output.workerPublicPath, - workerChunkLoading: output.workerChunkLoading, - workerWasmLoading: output.workerWasmLoading - }; - return result; - }), - parallelism: config.parallelism, - performance: optionalNestedConfig(config.performance, performance => { - if (performance === false) return false; - return { - ...performance - }; - }), - plugins: /** @type {Plugins} */ (nestedArray(config.plugins, p => [...p])), - profile: config.profile, - recordsInputPath: - config.recordsInputPath !== undefined - ? config.recordsInputPath - : config.recordsPath, - recordsOutputPath: - config.recordsOutputPath !== undefined - ? config.recordsOutputPath - : config.recordsPath, - resolve: nestedConfig(config.resolve, resolve => ({ - ...resolve, - byDependency: keyedNestedConfig(resolve.byDependency, cloneObject) - })), - resolveLoader: cloneObject(config.resolveLoader), - snapshot: nestedConfig(config.snapshot, snapshot => ({ - resolveBuildDependencies: optionalNestedConfig( - snapshot.resolveBuildDependencies, - resolveBuildDependencies => ({ - timestamp: resolveBuildDependencies.timestamp, - hash: resolveBuildDependencies.hash - }) - ), - buildDependencies: optionalNestedConfig( - snapshot.buildDependencies, - buildDependencies => ({ - timestamp: buildDependencies.timestamp, - hash: buildDependencies.hash - }) - ), - resolve: optionalNestedConfig(snapshot.resolve, resolve => ({ - timestamp: resolve.timestamp, - hash: resolve.hash - })), - module: optionalNestedConfig(snapshot.module, module => ({ - timestamp: module.timestamp, - hash: module.hash - })), - immutablePaths: optionalNestedArray(snapshot.immutablePaths, p => [...p]), - managedPaths: optionalNestedArray(snapshot.managedPaths, p => [...p]), - unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, p => [...p]) - })), - stats: nestedConfig(config.stats, stats => { - if (stats === false) { - return { - preset: "none" - }; - } - if (stats === true) { - return { - preset: "normal" - }; - } - if (typeof stats === "string") { - return { - preset: stats - }; - } - return { - ...stats - }; - }), - target: config.target, - watch: config.watch, - watchOptions: cloneObject(config.watchOptions) -}); - -/** - * @param {EntryStatic} entry static entry options - * @returns {EntryStaticNormalized} normalized static entry options - */ -const getNormalizedEntryStatic = entry => { - if (typeof entry === "string") { - return { - main: { - import: [entry] - } - }; - } - if (Array.isArray(entry)) { - return { - main: { - import: entry - } - }; - } - /** @type {EntryStaticNormalized} */ - const result = {}; - for (const key of Object.keys(entry)) { - const value = entry[key]; - if (typeof value === "string") { - result[key] = { - import: [value] - }; - } else if (Array.isArray(value)) { - result[key] = { - import: value - }; - } else { - result[key] = { - import: - /** @type {EntryDescriptionNormalized["import"]} */ - ( - value.import && - (Array.isArray(value.import) ? value.import : [value.import]) - ), - filename: value.filename, - layer: value.layer, - runtime: value.runtime, - baseUri: value.baseUri, - publicPath: value.publicPath, - chunkLoading: value.chunkLoading, - asyncChunks: value.asyncChunks, - wasmLoading: value.wasmLoading, - dependOn: - /** @type {EntryDescriptionNormalized["dependOn"]} */ - ( - value.dependOn && - (Array.isArray(value.dependOn) - ? value.dependOn - : [value.dependOn]) - ), - library: value.library - }; - } - } - return result; -}; - -/** - * @param {OptimizationRuntimeChunk=} runtimeChunk runtimeChunk option - * @returns {OptimizationRuntimeChunkNormalized=} normalized runtimeChunk option - */ -const getNormalizedOptimizationRuntimeChunk = runtimeChunk => { - if (runtimeChunk === undefined) return; - if (runtimeChunk === false) return false; - if (runtimeChunk === "single") { - return { - name: () => "runtime" - }; - } - if (runtimeChunk === true || runtimeChunk === "multiple") { - return { - /** - * @param {Entrypoint} entrypoint entrypoint - * @returns {string} runtime chunk name - */ - name: entrypoint => `runtime~${entrypoint.name}` - }; - } - const { name } = runtimeChunk; - return { - name: typeof name === "function" ? name : () => name - }; -}; - -module.exports.getNormalizedWebpackOptions = getNormalizedWebpackOptions; diff --git a/webpack-lib/lib/config/target.js b/webpack-lib/lib/config/target.js deleted file mode 100644 index 2a7ed046c78..00000000000 --- a/webpack-lib/lib/config/target.js +++ /dev/null @@ -1,377 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const memoize = require("../util/memoize"); - -const getBrowserslistTargetHandler = memoize(() => - require("./browserslistTargetHandler") -); - -/** - * @param {string} context the context directory - * @returns {string} default target - */ -const getDefaultTarget = context => { - const browsers = getBrowserslistTargetHandler().load(null, context); - return browsers ? "browserslist" : "web"; -}; - -/** - * @typedef {object} PlatformTargetProperties - * @property {boolean | null} web web platform, importing of http(s) and std: is available - * @property {boolean | null} browser browser platform, running in a normal web browser - * @property {boolean | null} webworker (Web)Worker platform, running in a web/shared/service worker - * @property {boolean | null} node node platform, require of node built-in modules is available - * @property {boolean | null} nwjs nwjs platform, require of legacy nw.gui is available - * @property {boolean | null} electron electron platform, require of some electron built-in modules is available - */ - -/** - * @typedef {object} ElectronContextTargetProperties - * @property {boolean | null} electronMain in main context - * @property {boolean | null} electronPreload in preload context - * @property {boolean | null} electronRenderer in renderer context with node integration - */ - -/** - * @typedef {object} ApiTargetProperties - * @property {boolean | null} require has require function available - * @property {boolean | null} nodeBuiltins has node.js built-in modules available - * @property {boolean | null} nodePrefixForCoreModules node.js allows to use `node:` prefix for core modules - * @property {boolean | null} document has document available (allows script tags) - * @property {boolean | null} importScripts has importScripts available - * @property {boolean | null} importScriptsInWorker has importScripts available when creating a worker - * @property {boolean | null} fetchWasm has fetch function available for WebAssembly - * @property {boolean | null} global has global variable available - */ - -/** - * @typedef {object} EcmaTargetProperties - * @property {boolean | null} globalThis has globalThis variable available - * @property {boolean | null} bigIntLiteral big int literal syntax is available - * @property {boolean | null} const const and let variable declarations are available - * @property {boolean | null} arrowFunction arrow functions are available - * @property {boolean | null} forOf for of iteration is available - * @property {boolean | null} destructuring destructuring is available - * @property {boolean | null} dynamicImport async import() is available - * @property {boolean | null} dynamicImportInWorker async import() is available when creating a worker - * @property {boolean | null} module ESM syntax is available (when in module) - * @property {boolean | null} optionalChaining optional chaining is available - * @property {boolean | null} templateLiteral template literal is available - * @property {boolean | null} asyncFunction async functions and await are available - */ - -/** - * @template T - * @typedef {{ [P in keyof T]?: never }} Never - */ - -/** - * @template A - * @template B - * @typedef {(A & Never) | (Never
& B) | (A & B)} Mix - */ - -/** @typedef {Mix, Mix>} TargetProperties */ - -/** - * @param {string} major major version - * @param {string | undefined} minor minor version - * @returns {(vMajor: number, vMinor?: number) => boolean | undefined} check if version is greater or equal - */ -const versionDependent = (major, minor) => { - if (!major) { - return () => /** @type {undefined} */ (undefined); - } - /** @type {number} */ - const nMajor = Number(major); - /** @type {number} */ - const nMinor = minor ? Number(minor) : 0; - return (vMajor, vMinor = 0) => - nMajor > vMajor || (nMajor === vMajor && nMinor >= vMinor); -}; - -/** @type {[string, string, RegExp, (...args: string[]) => Partial][]} */ -const TARGETS = [ - [ - "browserslist / browserslist:env / browserslist:query / browserslist:path-to-config / browserslist:path-to-config:env", - "Resolve features from browserslist. Will resolve browserslist config automatically. Only browser or node queries are supported (electron is not supported). Examples: 'browserslist:modern' to use 'modern' environment from browserslist config", - /^browserslist(?::(.+))?$/, - (rest, context) => { - const browserslistTargetHandler = getBrowserslistTargetHandler(); - const browsers = browserslistTargetHandler.load( - rest ? rest.trim() : null, - context - ); - if (!browsers) { - throw new Error(`No browserslist config found to handle the 'browserslist' target. -See https://github.com/browserslist/browserslist#queries for possible ways to provide a config. -The recommended way is to add a 'browserslist' key to your package.json and list supported browsers (resp. node.js versions). -You can also more options via the 'target' option: 'browserslist' / 'browserslist:env' / 'browserslist:query' / 'browserslist:path-to-config' / 'browserslist:path-to-config:env'`); - } - - return browserslistTargetHandler.resolve(browsers); - } - ], - [ - "web", - "Web browser.", - /^web$/, - () => ({ - node: false, - web: true, - webworker: null, - browser: true, - electron: false, - nwjs: false, - - document: true, - importScriptsInWorker: true, - fetchWasm: true, - nodeBuiltins: false, - importScripts: false, - require: false, - global: false - }) - ], - [ - "webworker", - "Web Worker, SharedWorker or Service Worker.", - /^webworker$/, - () => ({ - node: false, - web: true, - webworker: true, - browser: true, - electron: false, - nwjs: false, - - importScripts: true, - importScriptsInWorker: true, - fetchWasm: true, - nodeBuiltins: false, - require: false, - document: false, - global: false - }) - ], - [ - "[async-]node[X[.Y]]", - "Node.js in version X.Y. The 'async-' prefix will load chunks asynchronously via 'fs' and 'vm' instead of 'require()'. Examples: node14.5, async-node10.", - /^(async-)?node((\d+)(?:\.(\d+))?)?$/, - (asyncFlag, _, major, minor) => { - const v = versionDependent(major, minor); - // see https://node.green/ - return { - node: true, - web: false, - webworker: false, - browser: false, - electron: false, - nwjs: false, - - require: !asyncFlag, - nodeBuiltins: true, - // v16.0.0, v14.18.0 - nodePrefixForCoreModules: Number(major) < 15 ? v(14, 18) : v(16), - global: true, - document: false, - fetchWasm: false, - importScripts: false, - importScriptsInWorker: false, - - globalThis: v(12), - const: v(6), - templateLiteral: v(4), - optionalChaining: v(14), - arrowFunction: v(6), - asyncFunction: v(7, 6), - forOf: v(5), - destructuring: v(6), - bigIntLiteral: v(10, 4), - dynamicImport: v(12, 17), - dynamicImportInWorker: major ? false : undefined, - module: v(12, 17) - }; - } - ], - [ - "electron[X[.Y]]-main/preload/renderer", - "Electron in version X.Y. Script is running in main, preload resp. renderer context.", - /^electron((\d+)(?:\.(\d+))?)?-(main|preload|renderer)$/, - (_, major, minor, context) => { - const v = versionDependent(major, minor); - // see https://node.green/ + https://github.com/electron/releases - return { - node: true, - web: context !== "main", - webworker: false, - browser: false, - electron: true, - nwjs: false, - - electronMain: context === "main", - electronPreload: context === "preload", - electronRenderer: context === "renderer", - - global: true, - nodeBuiltins: true, - // 15.0.0 - Node.js v16.5 - // 14.0.0 - Mode.js v14.17, but prefixes only since v14.18 - nodePrefixForCoreModules: v(15), - - require: true, - document: context === "renderer", - fetchWasm: context === "renderer", - importScripts: false, - importScriptsInWorker: true, - - globalThis: v(5), - const: v(1, 1), - templateLiteral: v(1, 1), - optionalChaining: v(8), - arrowFunction: v(1, 1), - asyncFunction: v(1, 7), - forOf: v(0, 36), - destructuring: v(1, 1), - bigIntLiteral: v(4), - dynamicImport: v(11), - dynamicImportInWorker: major ? false : undefined, - module: v(11) - }; - } - ], - [ - "nwjs[X[.Y]] / node-webkit[X[.Y]]", - "NW.js in version X.Y.", - /^(?:nwjs|node-webkit)((\d+)(?:\.(\d+))?)?$/, - (_, major, minor) => { - const v = versionDependent(major, minor); - // see https://node.green/ + https://github.com/nwjs/nw.js/blob/nw48/CHANGELOG.md - return { - node: true, - web: true, - webworker: null, - browser: false, - electron: false, - nwjs: true, - - global: true, - nodeBuiltins: true, - document: false, - importScriptsInWorker: false, - fetchWasm: false, - importScripts: false, - require: false, - - globalThis: v(0, 43), - const: v(0, 15), - templateLiteral: v(0, 13), - optionalChaining: v(0, 44), - arrowFunction: v(0, 15), - asyncFunction: v(0, 21), - forOf: v(0, 13), - destructuring: v(0, 15), - bigIntLiteral: v(0, 32), - dynamicImport: v(0, 43), - dynamicImportInWorker: major ? false : undefined, - module: v(0, 43) - }; - } - ], - [ - "esX", - "EcmaScript in this version. Examples: es2020, es5.", - /^es(\d+)$/, - version => { - let v = Number(version); - if (v < 1000) v = v + 2009; - return { - const: v >= 2015, - templateLiteral: v >= 2015, - optionalChaining: v >= 2020, - arrowFunction: v >= 2015, - forOf: v >= 2015, - destructuring: v >= 2015, - module: v >= 2015, - asyncFunction: v >= 2017, - globalThis: v >= 2020, - bigIntLiteral: v >= 2020, - dynamicImport: v >= 2020, - dynamicImportInWorker: v >= 2020 - }; - } - ] -]; - -/** - * @param {string} target the target - * @param {string} context the context directory - * @returns {TargetProperties} target properties - */ -const getTargetProperties = (target, context) => { - for (const [, , regExp, handler] of TARGETS) { - const match = regExp.exec(target); - if (match) { - const [, ...args] = match; - const result = handler(...args, context); - if (result) return /** @type {TargetProperties} */ (result); - } - } - throw new Error( - `Unknown target '${target}'. The following targets are supported:\n${TARGETS.map( - ([name, description]) => `* ${name}: ${description}` - ).join("\n")}` - ); -}; - -/** - * @param {TargetProperties[]} targetProperties array of target properties - * @returns {TargetProperties} merged target properties - */ -const mergeTargetProperties = targetProperties => { - /** @type {Set} */ - const keys = new Set(); - for (const tp of targetProperties) { - for (const key of Object.keys(tp)) { - keys.add(/** @type {keyof TargetProperties} */ (key)); - } - } - /** @type {object} */ - const result = {}; - for (const key of keys) { - let hasTrue = false; - let hasFalse = false; - for (const tp of targetProperties) { - const value = tp[key]; - switch (value) { - case true: - hasTrue = true; - break; - case false: - hasFalse = true; - break; - } - } - if (hasTrue || hasFalse) - /** @type {TargetProperties} */ - (result)[key] = hasFalse && hasTrue ? null : Boolean(hasTrue); - } - return /** @type {TargetProperties} */ (result); -}; - -/** - * @param {string[]} targets the targets - * @param {string} context the context directory - * @returns {TargetProperties} target properties - */ -const getTargetsProperties = (targets, context) => - mergeTargetProperties(targets.map(t => getTargetProperties(t, context))); - -module.exports.getDefaultTarget = getDefaultTarget; -module.exports.getTargetProperties = getTargetProperties; -module.exports.getTargetsProperties = getTargetsProperties; diff --git a/webpack-lib/lib/container/ContainerEntryDependency.js b/webpack-lib/lib/container/ContainerEntryDependency.js deleted file mode 100644 index 787d99cffac..00000000000 --- a/webpack-lib/lib/container/ContainerEntryDependency.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("./ContainerEntryModule").ExposeOptions} ExposeOptions */ -/** @typedef {import("./ContainerEntryModule").ExposesList} ExposesList */ - -class ContainerEntryDependency extends Dependency { - /** - * @param {string} name entry name - * @param {ExposesList} exposes list of exposed modules - * @param {string} shareScope name of the share scope - */ - constructor(name, exposes, shareScope) { - super(); - this.name = name; - this.exposes = exposes; - this.shareScope = shareScope; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return `container-entry-${this.name}`; - } - - get type() { - return "container entry"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ContainerEntryDependency, - "webpack/lib/container/ContainerEntryDependency" -); - -module.exports = ContainerEntryDependency; diff --git a/webpack-lib/lib/container/ContainerEntryModule.js b/webpack-lib/lib/container/ContainerEntryModule.js deleted file mode 100644 index 3b22c712303..00000000000 --- a/webpack-lib/lib/container/ContainerEntryModule.js +++ /dev/null @@ -1,295 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const { OriginalSource, RawSource } = require("webpack-sources"); -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const Module = require("../Module"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_DYNAMIC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); -const makeSerializable = require("../util/makeSerializable"); -const ContainerExposedDependency = require("./ContainerExposedDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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("./ContainerEntryDependency")} ContainerEntryDependency */ - -/** - * @typedef {object} ExposeOptions - * @property {string[]} import requests to exposed modules (last one is exported) - * @property {string} name custom chunk name for the exposed module - */ - -/** @typedef {[string, ExposeOptions][]} ExposesList */ - -class ContainerEntryModule extends Module { - /** - * @param {string} name container entry name - * @param {ExposesList} exposes list of exposed modules - * @param {string} shareScope name of the share scope - */ - constructor(name, exposes, shareScope) { - super(JAVASCRIPT_MODULE_TYPE_DYNAMIC, null); - this._name = name; - this._exposes = exposes; - this._shareScope = shareScope; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `container entry (${this._shareScope}) ${JSON.stringify( - this._exposes - )}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return "container entry"; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return `${this.layer ? `(${this.layer})/` : ""}webpack/container/entry/${ - this._name - }`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - return callback(null, !this.buildMeta); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - strict: true, - topLevelDeclarations: new Set(["moduleMap", "get", "init"]) - }; - this.buildMeta.exportsType = "namespace"; - - this.clearDependenciesAndBlocks(); - - for (const [name, options] of this._exposes) { - const block = new AsyncDependenciesBlock( - { - name: options.name - }, - { name }, - options.import[options.import.length - 1] - ); - let idx = 0; - for (const request of options.import) { - const dep = new ContainerExposedDependency(name, request); - dep.loc = { - name, - index: idx++ - }; - - block.addDependency(dep); - } - this.addBlock(block); - } - this.addDependency(new StaticExportsDependency(["get", "init"], false)); - - callback(); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ moduleGraph, chunkGraph, runtimeTemplate }) { - const sources = new Map(); - const runtimeRequirements = new Set([ - RuntimeGlobals.definePropertyGetters, - RuntimeGlobals.hasOwnProperty, - RuntimeGlobals.exports - ]); - const getters = []; - - for (const block of this.blocks) { - const { dependencies } = block; - - const modules = dependencies.map(dependency => { - const dep = /** @type {ContainerExposedDependency} */ (dependency); - return { - name: dep.exposedName, - module: moduleGraph.getModule(dep), - request: dep.userRequest - }; - }); - - let str; - - if (modules.some(m => !m.module)) { - str = runtimeTemplate.throwMissingModuleErrorBlock({ - request: modules.map(m => m.request).join(", ") - }); - } else { - str = `return ${runtimeTemplate.blockPromise({ - block, - message: "", - chunkGraph, - runtimeRequirements - })}.then(${runtimeTemplate.returningFunction( - runtimeTemplate.returningFunction( - `(${modules - .map(({ module, request }) => - runtimeTemplate.moduleRaw({ - module, - chunkGraph, - request, - weak: false, - runtimeRequirements - }) - ) - .join(", ")})` - ) - )});`; - } - - getters.push( - `${JSON.stringify(modules[0].name)}: ${runtimeTemplate.basicFunction( - "", - str - )}` - ); - } - - const source = Template.asString([ - "var moduleMap = {", - Template.indent(getters.join(",\n")), - "};", - `var get = ${runtimeTemplate.basicFunction("module, getScope", [ - `${RuntimeGlobals.currentRemoteGetScope} = getScope;`, - // reusing the getScope variable to avoid creating a new var (and module is also used later) - "getScope = (", - Template.indent([ - `${RuntimeGlobals.hasOwnProperty}(moduleMap, module)`, - Template.indent([ - "? moduleMap[module]()", - `: Promise.resolve().then(${runtimeTemplate.basicFunction( - "", - "throw new Error('Module \"' + module + '\" does not exist in container.');" - )})` - ]) - ]), - ");", - `${RuntimeGlobals.currentRemoteGetScope} = undefined;`, - "return getScope;" - ])};`, - `var init = ${runtimeTemplate.basicFunction("shareScope, initScope", [ - `if (!${RuntimeGlobals.shareScopeMap}) return;`, - `var name = ${JSON.stringify(this._shareScope)}`, - `var oldScope = ${RuntimeGlobals.shareScopeMap}[name];`, - 'if(oldScope && oldScope !== shareScope) throw new Error("Container initialization failed as it has already been initialized with a different share scope");', - `${RuntimeGlobals.shareScopeMap}[name] = shareScope;`, - `return ${RuntimeGlobals.initializeSharing}(name, initScope);` - ])};`, - "", - "// This exports getters to disallow modifications", - `${RuntimeGlobals.definePropertyGetters}(exports, {`, - Template.indent([ - `get: ${runtimeTemplate.returningFunction("get")},`, - `init: ${runtimeTemplate.returningFunction("init")}` - ]), - "});" - ]); - - sources.set( - "javascript", - this.useSourceMap || this.useSimpleSourceMap - ? new OriginalSource(source, "webpack/container-entry") - : new RawSource(source) - ); - - return { - sources, - runtimeRequirements - }; - } - - /** - * @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; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this._name); - write(this._exposes); - write(this._shareScope); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ContainerEntryModule} deserialized container entry module - */ - static deserialize(context) { - const { read } = context; - const obj = new ContainerEntryModule(read(), read(), read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - ContainerEntryModule, - "webpack/lib/container/ContainerEntryModule" -); - -module.exports = ContainerEntryModule; diff --git a/webpack-lib/lib/container/ContainerEntryModuleFactory.js b/webpack-lib/lib/container/ContainerEntryModuleFactory.js deleted file mode 100644 index 4febfebe059..00000000000 --- a/webpack-lib/lib/container/ContainerEntryModuleFactory.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const ModuleFactory = require("../ModuleFactory"); -const ContainerEntryModule = require("./ContainerEntryModule"); - -/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./ContainerEntryDependency")} ContainerEntryDependency */ - -module.exports = class ContainerEntryModuleFactory extends ModuleFactory { - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create({ dependencies: [dependency] }, callback) { - const dep = /** @type {ContainerEntryDependency} */ (dependency); - callback(null, { - module: new ContainerEntryModule(dep.name, dep.exposes, dep.shareScope) - }); - } -}; diff --git a/webpack-lib/lib/container/ContainerExposedDependency.js b/webpack-lib/lib/container/ContainerExposedDependency.js deleted file mode 100644 index 2cbf04a694d..00000000000 --- a/webpack-lib/lib/container/ContainerExposedDependency.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const ModuleDependency = require("../dependencies/ModuleDependency"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ContainerExposedDependency extends ModuleDependency { - /** - * @param {string} exposedName public name - * @param {string} request request to module - */ - constructor(exposedName, request) { - super(request); - this.exposedName = exposedName; - } - - get type() { - return "container exposed"; - } - - get category() { - return "esm"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return `exposed dependency ${this.exposedName}=${this.request}`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - context.write(this.exposedName); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - this.exposedName = context.read(); - super.deserialize(context); - } -} - -makeSerializable( - ContainerExposedDependency, - "webpack/lib/container/ContainerExposedDependency" -); - -module.exports = ContainerExposedDependency; diff --git a/webpack-lib/lib/container/ContainerPlugin.js b/webpack-lib/lib/container/ContainerPlugin.js deleted file mode 100644 index ec3fe84091d..00000000000 --- a/webpack-lib/lib/container/ContainerPlugin.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const createSchemaValidation = require("../util/create-schema-validation"); -const memoize = require("../util/memoize"); -const ContainerEntryDependency = require("./ContainerEntryDependency"); -const ContainerEntryModuleFactory = require("./ContainerEntryModuleFactory"); -const ContainerExposedDependency = require("./ContainerExposedDependency"); -const { parseOptions } = require("./options"); - -/** @typedef {import("../../declarations/plugins/container/ContainerPlugin").ContainerPluginOptions} ContainerPluginOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("./ContainerEntryModule").ExposeOptions} ExposeOptions */ -/** @typedef {import("./ContainerEntryModule").ExposesList} ExposesList */ - -const getModuleFederationPlugin = memoize(() => - require("./ModuleFederationPlugin") -); - -const validate = createSchemaValidation( - require("../../schemas/plugins/container/ContainerPlugin.check.js"), - () => require("../../schemas/plugins/container/ContainerPlugin.json"), - { - name: "Container Plugin", - baseDataPath: "options" - } -); - -const PLUGIN_NAME = "ContainerPlugin"; - -class ContainerPlugin { - /** - * @param {ContainerPluginOptions} options options - */ - constructor(options) { - validate(options); - - this._options = { - name: options.name, - shareScope: options.shareScope || "default", - library: options.library || { - type: "var", - name: options.name - }, - runtime: options.runtime, - filename: options.filename || undefined, - exposes: /** @type {ExposesList} */ ( - parseOptions( - options.exposes, - item => ({ - import: Array.isArray(item) ? item : [item], - name: undefined - }), - item => ({ - import: Array.isArray(item.import) ? item.import : [item.import], - name: item.name || undefined - }) - ) - ) - }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { name, exposes, shareScope, filename, library, runtime } = - this._options; - - if (!compiler.options.output.enabledLibraryTypes.includes(library.type)) { - compiler.options.output.enabledLibraryTypes.push(library.type); - } - - compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => { - const hooks = - getModuleFederationPlugin().getCompilationHooks(compilation); - const dep = new ContainerEntryDependency(name, exposes, shareScope); - dep.loc = { name }; - compilation.addEntry( - /** @type {string} */ (compilation.options.context), - dep, - { - name, - filename, - runtime, - library - }, - error => { - if (error) return callback(error); - hooks.addContainerEntryDependency.call(dep); - callback(); - } - ); - }); - - compiler.hooks.thisCompilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - ContainerEntryDependency, - new ContainerEntryModuleFactory() - ); - - compilation.dependencyFactories.set( - ContainerExposedDependency, - normalModuleFactory - ); - } - ); - } -} - -module.exports = ContainerPlugin; diff --git a/webpack-lib/lib/container/ContainerReferencePlugin.js b/webpack-lib/lib/container/ContainerReferencePlugin.js deleted file mode 100644 index 59657c1ffd7..00000000000 --- a/webpack-lib/lib/container/ContainerReferencePlugin.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const ExternalsPlugin = require("../ExternalsPlugin"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const createSchemaValidation = require("../util/create-schema-validation"); -const FallbackDependency = require("./FallbackDependency"); -const FallbackItemDependency = require("./FallbackItemDependency"); -const FallbackModuleFactory = require("./FallbackModuleFactory"); -const RemoteModule = require("./RemoteModule"); -const RemoteRuntimeModule = require("./RemoteRuntimeModule"); -const RemoteToExternalDependency = require("./RemoteToExternalDependency"); -const { parseOptions } = require("./options"); - -/** @typedef {import("../../declarations/plugins/container/ContainerReferencePlugin").ContainerReferencePluginOptions} ContainerReferencePluginOptions */ -/** @typedef {import("../../declarations/plugins/container/ContainerReferencePlugin").RemotesConfig} RemotesConfig */ -/** @typedef {import("../Compiler")} Compiler */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/container/ContainerReferencePlugin.check.js"), - () => - require("../../schemas/plugins/container/ContainerReferencePlugin.json"), - { - name: "Container Reference Plugin", - baseDataPath: "options" - } -); - -const slashCode = "/".charCodeAt(0); - -class ContainerReferencePlugin { - /** - * @param {ContainerReferencePluginOptions} options options - */ - constructor(options) { - validate(options); - - this._remoteType = options.remoteType; - this._remotes = parseOptions( - options.remotes, - item => ({ - external: Array.isArray(item) ? item : [item], - shareScope: options.shareScope || "default" - }), - item => ({ - external: Array.isArray(item.external) - ? item.external - : [item.external], - shareScope: item.shareScope || options.shareScope || "default" - }) - ); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _remotes: remotes, _remoteType: remoteType } = this; - - /** @type {Record} */ - const remoteExternals = {}; - for (const [key, config] of remotes) { - let i = 0; - for (const external of config.external) { - if (external.startsWith("internal ")) continue; - remoteExternals[ - `webpack/container/reference/${key}${i ? `/fallback-${i}` : ""}` - ] = external; - i++; - } - } - - new ExternalsPlugin(remoteType, remoteExternals).apply(compiler); - - compiler.hooks.compilation.tap( - "ContainerReferencePlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - RemoteToExternalDependency, - normalModuleFactory - ); - - compilation.dependencyFactories.set( - FallbackItemDependency, - normalModuleFactory - ); - - compilation.dependencyFactories.set( - FallbackDependency, - new FallbackModuleFactory() - ); - - normalModuleFactory.hooks.factorize.tap( - "ContainerReferencePlugin", - data => { - if (!data.request.includes("!")) { - for (const [key, config] of remotes) { - if ( - data.request.startsWith(`${key}`) && - (data.request.length === key.length || - data.request.charCodeAt(key.length) === slashCode) - ) { - return new RemoteModule( - data.request, - config.external.map((external, i) => - external.startsWith("internal ") - ? external.slice(9) - : `webpack/container/reference/${key}${ - i ? `/fallback-${i}` : "" - }` - ), - `.${data.request.slice(key.length)}`, - config.shareScope - ); - } - } - } - } - ); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("ContainerReferencePlugin", (chunk, set) => { - set.add(RuntimeGlobals.module); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.hasOwnProperty); - set.add(RuntimeGlobals.initializeSharing); - set.add(RuntimeGlobals.shareScopeMap); - compilation.addRuntimeModule(chunk, new RemoteRuntimeModule()); - }); - } - ); - } -} - -module.exports = ContainerReferencePlugin; diff --git a/webpack-lib/lib/container/FallbackDependency.js b/webpack-lib/lib/container/FallbackDependency.js deleted file mode 100644 index 088720f5e32..00000000000 --- a/webpack-lib/lib/container/FallbackDependency.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class FallbackDependency extends Dependency { - /** - * @param {string[]} requests requests - */ - constructor(requests) { - super(); - this.requests = requests; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return `fallback ${this.requests.join(" ")}`; - } - - get type() { - return "fallback"; - } - - get category() { - return "esm"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.requests); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {FallbackDependency} deserialize fallback dependency - */ - static deserialize(context) { - const { read } = context; - const obj = new FallbackDependency(read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - FallbackDependency, - "webpack/lib/container/FallbackDependency" -); - -module.exports = FallbackDependency; diff --git a/webpack-lib/lib/container/FallbackItemDependency.js b/webpack-lib/lib/container/FallbackItemDependency.js deleted file mode 100644 index f09f8cf8c3c..00000000000 --- a/webpack-lib/lib/container/FallbackItemDependency.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("../dependencies/ModuleDependency"); -const makeSerializable = require("../util/makeSerializable"); - -class FallbackItemDependency extends ModuleDependency { - /** - * @param {string} request request - */ - constructor(request) { - super(request); - } - - get type() { - return "fallback item"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - FallbackItemDependency, - "webpack/lib/container/FallbackItemDependency" -); - -module.exports = FallbackItemDependency; diff --git a/webpack-lib/lib/container/FallbackModule.js b/webpack-lib/lib/container/FallbackModule.js deleted file mode 100644 index 50ea21b7e4d..00000000000 --- a/webpack-lib/lib/container/FallbackModule.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const Module = require("../Module"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const { WEBPACK_MODULE_TYPE_FALLBACK } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const makeSerializable = require("../util/makeSerializable"); -const FallbackItemDependency = require("./FallbackItemDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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 */ - -const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); - -class FallbackModule extends Module { - /** - * @param {string[]} requests list of requests to choose one - */ - constructor(requests) { - super(WEBPACK_MODULE_TYPE_FALLBACK); - this.requests = requests; - this._identifier = `fallback ${this.requests.join(" ")}`; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this._identifier; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return this._identifier; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return `${this.layer ? `(${this.layer})/` : ""}webpack/container/fallback/${ - this.requests[0] - }/and ${this.requests.length - 1} more`; - } - - /** - * @param {Chunk} chunk the chunk which condition should be checked - * @param {Compilation} compilation the compilation - * @returns {boolean} true, if the chunk is ok for the module - */ - chunkCondition(chunk, { chunkGraph }) { - return chunkGraph.getNumberOfEntryModules(chunk) > 0; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - callback(null, !this.buildInfo); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - strict: true - }; - - this.clearDependenciesAndBlocks(); - for (const request of this.requests) - this.addDependency(new FallbackItemDependency(request)); - - callback(); - } - - /** - * @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 this.requests.length * 5 + 42; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { - const ids = this.dependencies.map(dep => - chunkGraph.getModuleId(/** @type {Module} */ (moduleGraph.getModule(dep))) - ); - const code = Template.asString([ - `var ids = ${JSON.stringify(ids)};`, - "var error, result, i = 0;", - `var loop = ${runtimeTemplate.basicFunction("next", [ - "while(i < ids.length) {", - Template.indent([ - `try { next = ${RuntimeGlobals.require}(ids[i++]); } catch(e) { return handleError(e); }`, - "if(next) return next.then ? next.then(handleResult, handleError) : handleResult(next);" - ]), - "}", - "if(error) throw error;" - ])}`, - `var handleResult = ${runtimeTemplate.basicFunction("result", [ - "if(result) return result;", - "return loop();" - ])};`, - `var handleError = ${runtimeTemplate.basicFunction("e", [ - "error = e;", - "return loop();" - ])};`, - "module.exports = loop();" - ]); - const sources = new Map(); - sources.set("javascript", new RawSource(code)); - return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.requests); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {FallbackModule} deserialized fallback module - */ - static deserialize(context) { - const { read } = context; - const obj = new FallbackModule(read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable(FallbackModule, "webpack/lib/container/FallbackModule"); - -module.exports = FallbackModule; diff --git a/webpack-lib/lib/container/FallbackModuleFactory.js b/webpack-lib/lib/container/FallbackModuleFactory.js deleted file mode 100644 index 6a9eaeca0ae..00000000000 --- a/webpack-lib/lib/container/FallbackModuleFactory.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra, Zackary Jackson @ScriptedAlchemy, Marais Rossouw @maraisr -*/ - -"use strict"; - -const ModuleFactory = require("../ModuleFactory"); -const FallbackModule = require("./FallbackModule"); - -/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./FallbackDependency")} FallbackDependency */ - -module.exports = class FallbackModuleFactory extends ModuleFactory { - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create({ dependencies: [dependency] }, callback) { - const dep = /** @type {FallbackDependency} */ (dependency); - callback(null, { - module: new FallbackModule(dep.requests) - }); - } -}; diff --git a/webpack-lib/lib/container/HoistContainerReferencesPlugin.js b/webpack-lib/lib/container/HoistContainerReferencesPlugin.js deleted file mode 100644 index 3435c98ef2f..00000000000 --- a/webpack-lib/lib/container/HoistContainerReferencesPlugin.js +++ /dev/null @@ -1,250 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const ExternalModule = require("../ExternalModule"); -const { STAGE_ADVANCED } = require("../OptimizationStages"); -const memoize = require("../util/memoize"); -const { forEachRuntime } = require("../util/runtime"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Module")} Module */ - -const getModuleFederationPlugin = memoize(() => - require("./ModuleFederationPlugin") -); - -const PLUGIN_NAME = "HoistContainerReferences"; - -/** - * This class is used to hoist container references in the code. - */ -class HoistContainerReferences { - /** - * Apply the plugin to the compiler. - * @param {Compiler} compiler The webpack compiler instance. - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const hooks = - getModuleFederationPlugin().getCompilationHooks(compilation); - const depsToTrace = new Set(); - const entryExternalsToHoist = new Set(); - hooks.addContainerEntryDependency.tap(PLUGIN_NAME, dep => { - depsToTrace.add(dep); - }); - hooks.addFederationRuntimeDependency.tap(PLUGIN_NAME, dep => { - depsToTrace.add(dep); - }); - - compilation.hooks.addEntry.tap(PLUGIN_NAME, entryDep => { - if (entryDep.type === "entry") { - entryExternalsToHoist.add(entryDep); - } - }); - - // Hook into the optimizeChunks phase - compilation.hooks.optimizeChunks.tap( - { - name: PLUGIN_NAME, - // advanced stage is where SplitChunksPlugin runs. - stage: STAGE_ADVANCED + 1 - }, - chunks => { - this.hoistModulesInChunks( - compilation, - depsToTrace, - entryExternalsToHoist - ); - } - ); - }); - } - - /** - * Hoist modules in chunks. - * @param {Compilation} compilation The webpack compilation instance. - * @param {Set} depsToTrace Set of container entry dependencies. - * @param {Set} entryExternalsToHoist Set of container entry dependencies to hoist. - */ - hoistModulesInChunks(compilation, depsToTrace, entryExternalsToHoist) { - const { chunkGraph, moduleGraph } = compilation; - - // loop over entry points - for (const dep of entryExternalsToHoist) { - const entryModule = moduleGraph.getModule(dep); - if (!entryModule) continue; - // get all the external module types and hoist them to the runtime chunk, this will get RemoteModule externals - const allReferencedModules = getAllReferencedModules( - compilation, - entryModule, - "external", - false - ); - - const containerRuntimes = chunkGraph.getModuleRuntimes(entryModule); - const runtimes = new Set(); - - for (const runtimeSpec of containerRuntimes) { - forEachRuntime(runtimeSpec, runtimeKey => { - if (runtimeKey) { - runtimes.add(runtimeKey); - } - }); - } - - for (const runtime of runtimes) { - const runtimeChunk = compilation.namedChunks.get(runtime); - if (!runtimeChunk) continue; - - for (const module of allReferencedModules) { - if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { - chunkGraph.connectChunkAndModule(runtimeChunk, module); - } - } - } - this.cleanUpChunks(compilation, allReferencedModules); - } - - // handle container entry specifically - for (const dep of depsToTrace) { - const containerEntryModule = moduleGraph.getModule(dep); - if (!containerEntryModule) continue; - const allReferencedModules = getAllReferencedModules( - compilation, - containerEntryModule, - "initial", - false - ); - - const allRemoteReferences = getAllReferencedModules( - compilation, - containerEntryModule, - "external", - false - ); - - for (const remote of allRemoteReferences) { - allReferencedModules.add(remote); - } - - const containerRuntimes = - chunkGraph.getModuleRuntimes(containerEntryModule); - const runtimes = new Set(); - - for (const runtimeSpec of containerRuntimes) { - forEachRuntime(runtimeSpec, runtimeKey => { - if (runtimeKey) { - runtimes.add(runtimeKey); - } - }); - } - - for (const runtime of runtimes) { - const runtimeChunk = compilation.namedChunks.get(runtime); - if (!runtimeChunk) continue; - - for (const module of allReferencedModules) { - if (!chunkGraph.isModuleInChunk(module, runtimeChunk)) { - chunkGraph.connectChunkAndModule(runtimeChunk, module); - } - } - } - this.cleanUpChunks(compilation, allReferencedModules); - } - } - - /** - * Clean up chunks by disconnecting unused modules. - * @param {Compilation} compilation The webpack compilation instance. - * @param {Set} modules Set of modules to clean up. - */ - cleanUpChunks(compilation, modules) { - const { chunkGraph } = compilation; - for (const module of modules) { - for (const chunk of chunkGraph.getModuleChunks(module)) { - if (!chunk.hasRuntime()) { - chunkGraph.disconnectChunkAndModule(chunk, module); - if ( - chunkGraph.getNumberOfChunkModules(chunk) === 0 && - chunkGraph.getNumberOfEntryModules(chunk) === 0 - ) { - chunkGraph.disconnectChunk(chunk); - compilation.chunks.delete(chunk); - if (chunk.name) { - compilation.namedChunks.delete(chunk.name); - } - } - } - } - } - modules.clear(); - } -} - -/** - * Helper method to collect all referenced modules recursively. - * @param {Compilation} compilation The webpack compilation instance. - * @param {Module} module The module to start collecting from. - * @param {string} type The type of modules to collect ("initial", "external", or "all"). - * @param {boolean} includeInitial Should include the referenced module passed - * @returns {Set} Set of collected modules. - */ -function getAllReferencedModules(compilation, module, type, includeInitial) { - const collectedModules = new Set(includeInitial ? [module] : []); - const visitedModules = new WeakSet([module]); - const stack = [module]; - - while (stack.length > 0) { - const currentModule = stack.pop(); - if (!currentModule) continue; - - const outgoingConnections = - compilation.moduleGraph.getOutgoingConnections(currentModule); - if (outgoingConnections) { - for (const connection of outgoingConnections) { - const connectedModule = connection.module; - - // Skip if module has already been visited - if (!connectedModule || visitedModules.has(connectedModule)) { - continue; - } - - // Handle 'initial' type (skipping async blocks) - if (type === "initial") { - const parentBlock = compilation.moduleGraph.getParentBlock( - /** @type {Dependency} */ - (connection.dependency) - ); - if (parentBlock instanceof AsyncDependenciesBlock) { - continue; - } - } - - // Handle 'external' type (collecting only external modules) - if (type === "external") { - if (connection.module instanceof ExternalModule) { - collectedModules.add(connectedModule); - } - } else { - // Handle 'all' or unspecified types - collectedModules.add(connectedModule); - } - - // Add connected module to the stack and mark it as visited - visitedModules.add(connectedModule); - stack.push(connectedModule); - } - } - } - - return collectedModules; -} - -module.exports = HoistContainerReferences; diff --git a/webpack-lib/lib/container/ModuleFederationPlugin.js b/webpack-lib/lib/container/ModuleFederationPlugin.js deleted file mode 100644 index 94e2aacee53..00000000000 --- a/webpack-lib/lib/container/ModuleFederationPlugin.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const { SyncHook } = require("tapable"); -const isValidExternalsType = require("../../schemas/plugins/container/ExternalsType.check.js"); -const Compilation = require("../Compilation"); -const SharePlugin = require("../sharing/SharePlugin"); -const createSchemaValidation = require("../util/create-schema-validation"); -const ContainerPlugin = require("./ContainerPlugin"); -const ContainerReferencePlugin = require("./ContainerReferencePlugin"); -const HoistContainerReferences = require("./HoistContainerReferencesPlugin"); - -/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").ExternalsType} ExternalsType */ -/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").ModuleFederationPluginOptions} ModuleFederationPluginOptions */ -/** @typedef {import("../../declarations/plugins/container/ModuleFederationPlugin").Shared} Shared */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency")} Dependency */ - -/** - * @typedef {object} CompilationHooks - * @property {SyncHook} addContainerEntryDependency - * @property {SyncHook} addFederationRuntimeDependency - */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/container/ModuleFederationPlugin.check.js"), - () => require("../../schemas/plugins/container/ModuleFederationPlugin.json"), - { - name: "Module Federation Plugin", - baseDataPath: "options" - } -); - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class ModuleFederationPlugin { - /** - * @param {ModuleFederationPluginOptions} options options - */ - constructor(options) { - validate(options); - - this._options = options; - } - - /** - * Get the compilation hooks associated with this plugin. - * @param {Compilation} compilation The compilation instance. - * @returns {CompilationHooks} The hooks for the compilation. - */ - 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) { - hooks = { - addContainerEntryDependency: new SyncHook(["dependency"]), - addFederationRuntimeDependency: new SyncHook(["dependency"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _options: options } = this; - const library = options.library || { type: "var", name: options.name }; - const remoteType = - options.remoteType || - (options.library && isValidExternalsType(options.library.type) - ? /** @type {ExternalsType} */ (options.library.type) - : "script"); - if ( - library && - !compiler.options.output.enabledLibraryTypes.includes(library.type) - ) { - compiler.options.output.enabledLibraryTypes.push(library.type); - } - compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { - if ( - options.exposes && - (Array.isArray(options.exposes) - ? options.exposes.length > 0 - : Object.keys(options.exposes).length > 0) - ) { - new ContainerPlugin({ - name: /** @type {string} */ (options.name), - library, - filename: options.filename, - runtime: options.runtime, - shareScope: options.shareScope, - exposes: options.exposes - }).apply(compiler); - } - if ( - options.remotes && - (Array.isArray(options.remotes) - ? options.remotes.length > 0 - : Object.keys(options.remotes).length > 0) - ) { - new ContainerReferencePlugin({ - remoteType, - shareScope: options.shareScope, - remotes: options.remotes - }).apply(compiler); - } - if (options.shared) { - new SharePlugin({ - shared: options.shared, - shareScope: options.shareScope - }).apply(compiler); - } - new HoistContainerReferences().apply(compiler); - }); - } -} - -module.exports = ModuleFederationPlugin; diff --git a/webpack-lib/lib/container/RemoteModule.js b/webpack-lib/lib/container/RemoteModule.js deleted file mode 100644 index 4a2cf128de1..00000000000 --- a/webpack-lib/lib/container/RemoteModule.js +++ /dev/null @@ -1,184 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const Module = require("../Module"); -const { - REMOTE_AND_SHARE_INIT_TYPES -} = require("../ModuleSourceTypesConstants"); -const { WEBPACK_MODULE_TYPE_REMOTE } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const FallbackDependency = require("./FallbackDependency"); -const RemoteToExternalDependency = require("./RemoteToExternalDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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 */ - -const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); - -class RemoteModule extends Module { - /** - * @param {string} request request string - * @param {string[]} externalRequests list of external requests to containers - * @param {string} internalRequest name of exposed module in container - * @param {string} shareScope the used share scope name - */ - constructor(request, externalRequests, internalRequest, shareScope) { - super(WEBPACK_MODULE_TYPE_REMOTE); - this.request = request; - this.externalRequests = externalRequests; - this.internalRequest = internalRequest; - this.shareScope = shareScope; - this._identifier = `remote (${shareScope}) ${this.externalRequests.join( - " " - )} ${this.internalRequest}`; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this._identifier; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `remote ${this.request}`; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return `${this.layer ? `(${this.layer})/` : ""}webpack/container/remote/${ - this.request - }`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - callback(null, !this.buildInfo); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - strict: true - }; - - this.clearDependenciesAndBlocks(); - if (this.externalRequests.length === 1) { - this.addDependency( - new RemoteToExternalDependency(this.externalRequests[0]) - ); - } else { - this.addDependency(new FallbackDependency(this.externalRequests)); - } - - callback(); - } - - /** - * @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 6; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return REMOTE_AND_SHARE_INIT_TYPES; - } - - /** - * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) - */ - nameForCondition() { - return this.request; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { - const module = moduleGraph.getModule(this.dependencies[0]); - const id = module && chunkGraph.getModuleId(module); - const sources = new Map(); - sources.set("remote", new RawSource("")); - const data = new Map(); - data.set("share-init", [ - { - shareScope: this.shareScope, - initStage: 20, - init: id === undefined ? "" : `initExternal(${JSON.stringify(id)});` - } - ]); - return { sources, data, runtimeRequirements: RUNTIME_REQUIREMENTS }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.request); - write(this.externalRequests); - write(this.internalRequest); - write(this.shareScope); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RemoteModule} deserialized module - */ - static deserialize(context) { - const { read } = context; - const obj = new RemoteModule(read(), read(), read(), read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable(RemoteModule, "webpack/lib/container/RemoteModule"); - -module.exports = RemoteModule; diff --git a/webpack-lib/lib/container/RemoteRuntimeModule.js b/webpack-lib/lib/container/RemoteRuntimeModule.js deleted file mode 100644 index 1e871e2da2f..00000000000 --- a/webpack-lib/lib/container/RemoteRuntimeModule.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("./RemoteModule")} RemoteModule */ - -class RemoteRuntimeModule extends RuntimeModule { - constructor() { - super("remotes loading"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const { runtimeTemplate, moduleGraph } = compilation; - /** @type {Record} */ - const chunkToRemotesMapping = {}; - /** @type {Record} */ - const idToExternalAndNameMapping = {}; - for (const chunk of /** @type {Chunk} */ ( - this.chunk - ).getAllReferencedChunks()) { - const modules = chunkGraph.getChunkModulesIterableBySourceType( - chunk, - "remote" - ); - if (!modules) continue; - /** @type {ModuleId[]} */ - const remotes = (chunkToRemotesMapping[ - /** @type {ChunkId} */ - (chunk.id) - ] = []); - for (const m of modules) { - const module = /** @type {RemoteModule} */ (m); - const name = module.internalRequest; - const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - const shareScope = module.shareScope; - const dep = module.dependencies[0]; - const externalModule = moduleGraph.getModule(dep); - const externalModuleId = - /** @type {ModuleId} */ - (externalModule && chunkGraph.getModuleId(externalModule)); - remotes.push(id); - idToExternalAndNameMapping[id] = [shareScope, name, externalModuleId]; - } - } - return Template.asString([ - `var chunkMapping = ${JSON.stringify( - chunkToRemotesMapping, - null, - "\t" - )};`, - `var idToExternalAndNameMapping = ${JSON.stringify( - idToExternalAndNameMapping, - null, - "\t" - )};`, - `${ - RuntimeGlobals.ensureChunkHandlers - }.remotes = ${runtimeTemplate.basicFunction("chunkId, promises", [ - `if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`, - Template.indent([ - `chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction("id", [ - `var getScope = ${RuntimeGlobals.currentRemoteGetScope};`, - "if(!getScope) getScope = [];", - "var data = idToExternalAndNameMapping[id];", - "if(getScope.indexOf(data) >= 0) return;", - "getScope.push(data);", - "if(data.p) return promises.push(data.p);", - `var onError = ${runtimeTemplate.basicFunction("error", [ - 'if(!error) error = new Error("Container missing");', - 'if(typeof error.message === "string")', - Template.indent( - "error.message += '\\nwhile loading \"' + data[1] + '\" from ' + data[2];" - ), - `${ - RuntimeGlobals.moduleFactories - }[id] = ${runtimeTemplate.basicFunction("", ["throw error;"])}`, - "data.p = 0;" - ])};`, - `var handleFunction = ${runtimeTemplate.basicFunction( - "fn, arg1, arg2, d, next, first", - [ - "try {", - Template.indent([ - "var promise = fn(arg1, arg2);", - "if(promise && promise.then) {", - Template.indent([ - `var p = promise.then(${runtimeTemplate.returningFunction( - "next(result, d)", - "result" - )}, onError);`, - "if(first) promises.push(data.p = p); else return p;" - ]), - "} else {", - Template.indent(["return next(promise, d, first);"]), - "}" - ]), - "} catch(error) {", - Template.indent(["onError(error);"]), - "}" - ] - )}`, - `var onExternal = ${runtimeTemplate.returningFunction( - `external ? handleFunction(${RuntimeGlobals.initializeSharing}, data[0], 0, external, onInitialized, first) : onError()`, - "external, _, first" - )};`, - `var onInitialized = ${runtimeTemplate.returningFunction( - "handleFunction(external.get, data[1], getScope, 0, onFactory, first)", - "_, external, first" - )};`, - `var onFactory = ${runtimeTemplate.basicFunction("factory", [ - "data.p = 1;", - `${ - RuntimeGlobals.moduleFactories - }[id] = ${runtimeTemplate.basicFunction("module", [ - "module.exports = factory();" - ])}` - ])};`, - `handleFunction(${RuntimeGlobals.require}, data[2], 0, 0, onExternal, 1);` - ])});` - ]), - "}" - ])}` - ]); - } -} - -module.exports = RemoteRuntimeModule; diff --git a/webpack-lib/lib/container/RemoteToExternalDependency.js b/webpack-lib/lib/container/RemoteToExternalDependency.js deleted file mode 100644 index df7fd1f8158..00000000000 --- a/webpack-lib/lib/container/RemoteToExternalDependency.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("../dependencies/ModuleDependency"); -const makeSerializable = require("../util/makeSerializable"); - -class RemoteToExternalDependency extends ModuleDependency { - /** - * @param {string} request request - */ - constructor(request) { - super(request); - } - - get type() { - return "remote to external"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - RemoteToExternalDependency, - "webpack/lib/container/RemoteToExternalDependency" -); - -module.exports = RemoteToExternalDependency; diff --git a/webpack-lib/lib/container/options.js b/webpack-lib/lib/container/options.js deleted file mode 100644 index cb7df0d55fb..00000000000 --- a/webpack-lib/lib/container/options.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template T - * @typedef {Record} Item - */ - -/** - * @template T - * @typedef {(string | Item)[] | Item} ContainerOptionsFormat - */ - -/** - * @template T - * @template N - * @param {ContainerOptionsFormat} options options passed by the user - * @param {function(string | string[], string) : N} normalizeSimple normalize a simple item - * @param {function(T, string) : N} normalizeOptions normalize a complex item - * @param {function(string, N): void} fn processing function - * @returns {void} - */ -const process = (options, normalizeSimple, normalizeOptions, fn) => { - /** - * @param {(string | Item)[]} items items - */ - const array = items => { - for (const item of items) { - if (typeof item === "string") { - fn(item, normalizeSimple(item, item)); - } else if (item && typeof item === "object") { - object(item); - } else { - throw new Error("Unexpected options format"); - } - } - }; - /** - * @param {Item} obj an object - */ - const object = obj => { - for (const [key, value] of Object.entries(obj)) { - if (typeof value === "string" || Array.isArray(value)) { - fn(key, normalizeSimple(value, key)); - } else { - fn(key, normalizeOptions(value, key)); - } - } - }; - if (!options) { - // Do nothing - } else if (Array.isArray(options)) { - array(options); - } else if (typeof options === "object") { - object(options); - } else { - throw new Error("Unexpected options format"); - } -}; - -/** - * @template T - * @template R - * @param {ContainerOptionsFormat} options options passed by the user - * @param {function(string | string[], string) : R} normalizeSimple normalize a simple item - * @param {function(T, string) : R} normalizeOptions normalize a complex item - * @returns {[string, R][]} parsed options - */ -const parseOptions = (options, normalizeSimple, normalizeOptions) => { - /** @type {[string, R][]} */ - const items = []; - process(options, normalizeSimple, normalizeOptions, (key, value) => { - items.push([key, value]); - }); - return items; -}; - -/** - * @template T - * @param {string} scope scope name - * @param {ContainerOptionsFormat} options options passed by the user - * @returns {Record} options to spread or pass - */ -const scope = (scope, options) => { - /** @type {Record} */ - const obj = {}; - process( - options, - item => /** @type {string | string[] | T} */ (item), - item => /** @type {string | string[] | T} */ (item), - (key, value) => { - obj[ - key.startsWith("./") ? `${scope}${key.slice(1)}` : `${scope}/${key}` - ] = value; - } - ); - return obj; -}; - -module.exports.parseOptions = parseOptions; -module.exports.scope = scope; diff --git a/webpack-lib/lib/css/CssGenerator.js b/webpack-lib/lib/css/CssGenerator.js deleted file mode 100644 index 4efdc73c4ce..00000000000 --- a/webpack-lib/lib/css/CssGenerator.js +++ /dev/null @@ -1,261 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const { ReplaceSource, RawSource, ConcatSource } = require("webpack-sources"); -const { UsageState } = require("../ExportsInfo"); -const Generator = require("../Generator"); -const InitFragment = require("../InitFragment"); -const { - JS_AND_CSS_EXPORT_TYPES, - JS_AND_CSS_TYPES -} = require("../ModuleSourceTypesConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").CssAutoGeneratorOptions} CssAutoGeneratorOptions */ -/** @typedef {import("../../declarations/WebpackOptions").CssGlobalGeneratorOptions} CssGlobalGeneratorOptions */ -/** @typedef {import("../../declarations/WebpackOptions").CssModuleGeneratorOptions} CssModuleGeneratorOptions */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").CssData} CssData */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../util/Hash")} Hash */ - -class CssGenerator extends Generator { - /** - * @param {CssAutoGeneratorOptions | CssGlobalGeneratorOptions | CssModuleGeneratorOptions} options options - */ - constructor(options) { - super(); - this.convention = options.exportsConvention; - this.localIdentName = options.localIdentName; - this.exportsOnly = options.exportsOnly; - this.esModule = options.esModule; - } - - /** - * @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) { - if (!this.esModule) { - return "Module is not an ECMAScript module"; - } - - return undefined; - } - - /** - * @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 source = - generateContext.type === "javascript" - ? new ReplaceSource(new RawSource("")) - : new ReplaceSource(/** @type {Source} */ (module.originalSource())); - - /** @type {InitFragment[]} */ - const initFragments = []; - /** @type {CssData} */ - const cssData = { - esModule: this.esModule, - exports: new Map() - }; - - /** @type {InitFragment[] | undefined} */ - let chunkInitFragments; - /** @type {DependencyTemplateContext} */ - const templateContext = { - runtimeTemplate: generateContext.runtimeTemplate, - dependencyTemplates: generateContext.dependencyTemplates, - moduleGraph: generateContext.moduleGraph, - chunkGraph: generateContext.chunkGraph, - module, - runtime: generateContext.runtime, - runtimeRequirements: generateContext.runtimeRequirements, - concatenationScope: generateContext.concatenationScope, - codeGenerationResults: - /** @type {CodeGenerationResults} */ - (generateContext.codeGenerationResults), - initFragments, - cssData, - get chunkInitFragments() { - if (!chunkInitFragments) { - const data = - /** @type {NonNullable} */ - (generateContext.getData)(); - chunkInitFragments = data.get("chunkInitFragments"); - if (!chunkInitFragments) { - chunkInitFragments = []; - data.set("chunkInitFragments", chunkInitFragments); - } - } - - return chunkInitFragments; - } - }; - - /** - * @param {Dependency} dependency dependency - */ - const handleDependency = dependency => { - const constructor = - /** @type {new (...args: EXPECTED_ANY[]) => Dependency} */ - (dependency.constructor); - const template = generateContext.dependencyTemplates.get(constructor); - if (!template) { - throw new Error( - `No template for dependency: ${dependency.constructor.name}` - ); - } - - template.apply(dependency, source, templateContext); - }; - - for (const dependency of module.dependencies) { - handleDependency(dependency); - } - - switch (generateContext.type) { - case "javascript": { - module.buildInfo.cssData = cssData; - - generateContext.runtimeRequirements.add(RuntimeGlobals.module); - - if (generateContext.concatenationScope) { - const source = new ConcatSource(); - const usedIdentifiers = new Set(); - for (const [name, v] of cssData.exports) { - const usedName = generateContext.moduleGraph - .getExportInfo(module, name) - .getUsedName(name, generateContext.runtime); - if (!usedName) { - continue; - } - let identifier = Template.toIdentifier(usedName); - const { RESERVED_IDENTIFIER } = require("../util/propertyName"); - if (RESERVED_IDENTIFIER.has(identifier)) { - identifier = `_${identifier}`; - } - const i = 0; - while (usedIdentifiers.has(identifier)) { - identifier = Template.toIdentifier(name + i); - } - usedIdentifiers.add(identifier); - generateContext.concatenationScope.registerExport(name, identifier); - source.add( - `${ - generateContext.runtimeTemplate.supportsConst() - ? "const" - : "var" - } ${identifier} = ${JSON.stringify(v)};\n` - ); - } - return source; - } - - const needNsObj = - this.esModule && - generateContext.moduleGraph - .getExportsInfo(module) - .otherExportsInfo.getUsed(generateContext.runtime) !== - UsageState.Unused; - - if (needNsObj) { - generateContext.runtimeRequirements.add( - RuntimeGlobals.makeNamespaceObject - ); - } - - const exports = []; - - for (const [name, v] of cssData.exports) { - exports.push(`\t${JSON.stringify(name)}: ${JSON.stringify(v)}`); - } - - return new RawSource( - `${needNsObj ? `${RuntimeGlobals.makeNamespaceObject}(` : ""}${ - module.moduleArgument - }.exports = {\n${exports.join(",\n")}\n}${needNsObj ? ")" : ""};` - ); - } - case "css": { - if (module.presentationalDependencies !== undefined) { - for (const dependency of module.presentationalDependencies) { - handleDependency(dependency); - } - } - - generateContext.runtimeRequirements.add(RuntimeGlobals.hasCssModules); - - return InitFragment.addToSource(source, initFragments, generateContext); - } - } - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - // TODO, find a better way to prevent the original module from being removed after concatenation, maybe it is a bug - return this.exportsOnly ? JS_AND_CSS_EXPORT_TYPES : JS_AND_CSS_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - switch (type) { - case "javascript": { - if (!module.buildInfo.cssData) { - return 42; - } - - const exports = module.buildInfo.cssData.exports; - const stringifiedExports = JSON.stringify( - Array.from(exports).reduce((obj, [key, value]) => { - obj[key] = value; - return obj; - }, {}) - ); - - return stringifiedExports.length + 42; - } - case "css": { - const originalSource = module.originalSource(); - - if (!originalSource) { - return 0; - } - - return originalSource.size(); - } - } - } - - /** - * @param {Hash} hash hash that will be modified - * @param {UpdateHashContext} updateHashContext context for updating hash - */ - updateHash(hash, { module }) { - hash.update(this.esModule.toString()); - } -} - -module.exports = CssGenerator; diff --git a/webpack-lib/lib/css/CssLoadingRuntimeModule.js b/webpack-lib/lib/css/CssLoadingRuntimeModule.js deleted file mode 100644 index a06d329d189..00000000000 --- a/webpack-lib/lib/css/CssLoadingRuntimeModule.js +++ /dev/null @@ -1,503 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const Compilation = require("../Compilation"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); -const { chunkHasCss } = require("./CssModulesPlugin"); - -/** @typedef {import("../../declarations/WebpackOptions").Environment} Environment */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -/** - * @typedef {object} CssLoadingRuntimeModulePluginHooks - * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet - * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload - * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class CssLoadingRuntimeModule extends RuntimeModule { - /** - * @param {Compilation} compilation the compilation - * @returns {CssLoadingRuntimeModulePluginHooks} 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 = { - createStylesheet: new SyncWaterfallHook(["source", "chunk"]), - linkPreload: new SyncWaterfallHook(["source", "chunk"]), - linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("css loading", 10); - - this._runtimeRequirements = runtimeRequirements; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { _runtimeRequirements } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const chunk = /** @type {Chunk} */ (this.chunk); - const { - chunkGraph, - runtimeTemplate, - outputOptions: { - crossOriginLoading, - uniqueName, - chunkLoadTimeout: loadTimeout - } - } = compilation; - const fn = RuntimeGlobals.ensureChunkHandlers; - const conditionMap = chunkGraph.getChunkConditionMap( - /** @type {Chunk} */ (chunk), - /** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {boolean} true, if the chunk has css - */ - (chunk, chunkGraph) => - Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) - ); - const hasCssMatcher = compileBooleanMatcher(conditionMap); - - const withLoading = - _runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) && - hasCssMatcher !== false; - /** @type {boolean} */ - const withHmr = _runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - /** @type {Set} */ - const initialChunkIds = new Set(); - for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) { - if (chunkHasCss(c, chunkGraph)) { - initialChunkIds.add(c.id); - } - } - - if (!withLoading && !withHmr) { - return null; - } - - const environment = - /** @type {Environment} */ - (compilation.outputOptions.environment); - const isNeutralPlatform = runtimeTemplate.isNeutralPlatform(); - const withPrefetch = - this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && - (environment.document || isNeutralPlatform) && - chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasCss); - const withPreload = - this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && - (environment.document || isNeutralPlatform) && - chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasCss); - - const { linkPreload, linkPrefetch } = - CssLoadingRuntimeModule.getCompilationHooks(compilation); - - const withFetchPriority = _runtimeRequirements.has( - RuntimeGlobals.hasFetchPriority - ); - - const { createStylesheet } = - CssLoadingRuntimeModule.getCompilationHooks(compilation); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css` - : undefined; - - const code = Template.asString([ - "link = document.createElement('link');", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - uniqueName - ? 'link.setAttribute("data-webpack", uniqueName + ":" + key);' - : "", - withFetchPriority - ? Template.asString([ - "if(fetchPriority) {", - Template.indent( - 'link.setAttribute("fetchpriority", fetchPriority);' - ), - "}" - ]) - : "", - "link.setAttribute(loadingAttribute, 1);", - 'link.rel = "stylesheet";', - "link.href = url;", - crossOriginLoading - ? crossOriginLoading === "use-credentials" - ? 'link.crossOrigin = "use-credentials";' - : Template.asString([ - "if (link.href.indexOf(window.location.origin + '/') !== 0) {", - Template.indent( - `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` - ), - "}" - ]) - : "" - ]); - - return Template.asString([ - "// object to store loaded and loading chunks", - "// undefined = chunk not loaded, null = chunk preloaded/prefetched", - "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded", - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( - ",\n" - ) - ), - "};", - "", - uniqueName - ? `var uniqueName = ${JSON.stringify( - runtimeTemplate.outputOptions.uniqueName - )};` - : "// data-webpack is not used as build has no uniqueName", - withLoading || withHmr - ? Template.asString([ - 'var loadingAttribute = "data-webpack-loading";', - `var loadStylesheet = ${runtimeTemplate.basicFunction( - `chunkId, url, done${ - withFetchPriority ? ", fetchPriority" : "" - }${withHmr ? ", hmr" : ""}`, - [ - 'var link, needAttach, key = "chunk-" + chunkId;', - withHmr ? "if(!hmr) {" : "", - 'var links = document.getElementsByTagName("link");', - "for(var i = 0; i < links.length; i++) {", - Template.indent([ - "var l = links[i];", - `if(l.rel == "stylesheet" && (${ - withHmr - ? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)' - : 'l.href == url || l.getAttribute("href") == url' - }${ - uniqueName - ? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key' - : "" - })) { link = l; break; }` - ]), - "}", - "if(!done) return link;", - withHmr ? "}" : "", - "if(!link) {", - Template.indent([ - "needAttach = true;", - createStylesheet.call(code, /** @type {Chunk} */ (this.chunk)) - ]), - "}", - `var onLinkComplete = ${runtimeTemplate.basicFunction( - "prev, event", - Template.asString([ - "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)) {", - Template.indent([ - `var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`, - "link.onerror = onLinkComplete.bind(null, link.onerror);", - "link.onload = onLinkComplete.bind(null, link.onload);" - ]), - "} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking - withHmr && withFetchPriority - ? 'if (hmr && hmr.getAttribute("fetchpriority")) link.setAttribute("fetchpriority", hmr.getAttribute("fetchpriority"));' - : "", - withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "", - "needAttach && document.head.appendChild(link);", - "return link;" - ] - )};` - ]) - : "", - withLoading - ? Template.asString([ - `${fn}.css = ${runtimeTemplate.basicFunction( - `chunkId, promises${withFetchPriority ? " , fetchPriority" : ""}`, - [ - "// css chunk loading", - `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, - 'if(installedChunkData !== 0) { // 0 means "already installed".', - Template.indent([ - "", - '// a Promise means "currently loading".', - "if(installedChunkData) {", - Template.indent(["promises.push(installedChunkData[2]);"]), - "} else {", - Template.indent([ - hasCssMatcher === true - ? "if(true) { // all chunks have CSS" - : `if(${hasCssMatcher("chunkId")}) {`, - Template.indent([ - "// setup Promise in chunk cache", - `var promise = new Promise(${runtimeTemplate.expressionFunction( - "installedChunkData = installedChunks[chunkId] = [resolve, reject]", - "resolve, reject" - )});`, - "promises.push(installedChunkData[2] = promise);", - "", - "// start chunk loading", - `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, - "// create error before stack unwound to get useful stacktrace later", - "var error = new Error();", - `var loadingEnded = ${runtimeTemplate.basicFunction( - "event", - [ - `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, - Template.indent([ - "installedChunkData = installedChunks[chunkId];", - "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", - "if(installedChunkData) {", - Template.indent([ - 'if(event.type !== "load") {', - Template.indent([ - "var errorType = event && event.type;", - "var 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 {", - Template.indent([ - "installedChunks[chunkId] = 0;", - "installedChunkData[0]();" - ]), - "}" - ]), - "}" - ]), - "}" - ] - )};`, - isNeutralPlatform - ? "if (typeof document !== 'undefined') {" - : "", - Template.indent([ - `loadStylesheet(chunkId, url, loadingEnded${ - withFetchPriority ? ", fetchPriority" : "" - });` - ]), - isNeutralPlatform - ? "} else { loadingEnded({ type: 'load' }); }" - : "" - ]), - "} else installedChunks[chunkId] = 0;" - ]), - "}" - ]), - "}" - ] - )};` - ]) - : "// no chunk loading", - "", - withPrefetch && hasCssMatcher !== false - ? `${ - RuntimeGlobals.prefetchChunkHandlers - }.s = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - isNeutralPlatform - ? "if (typeof document === 'undefined') return;" - : "", - linkPrefetch.call( - Template.asString([ - "var link = document.createElement('link');", - crossOriginLoading - ? `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - : "", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - 'link.rel = "prefetch";', - 'link.as = "style";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);` - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no prefetching", - "", - withPreload && hasCssMatcher !== false - ? `${ - RuntimeGlobals.preloadChunkHandlers - }.s = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasCssMatcher === true ? "true" : hasCssMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - isNeutralPlatform - ? "if (typeof document === 'undefined') return;" - : "", - linkPreload.call( - Template.asString([ - "var link = document.createElement('link');", - "link.charset = 'utf-8';", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - 'link.rel = "preload";', - 'link.as = "style";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, - crossOriginLoading - ? crossOriginLoading === "use-credentials" - ? 'link.crossOrigin = "use-credentials";' - : Template.asString([ - "if (link.href.indexOf(window.location.origin + '/') !== 0) {", - Template.indent( - `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - ), - "}" - ]) - : "" - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no preloaded", - withHmr - ? Template.asString([ - "var oldTags = [];", - "var newTags = [];", - `var applyHandler = ${runtimeTemplate.basicFunction("options", [ - `return { dispose: ${runtimeTemplate.basicFunction("", [ - "while(oldTags.length) {", - Template.indent([ - "var oldTag = oldTags.pop();", - "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);" - ]), - "}" - ])}, apply: ${runtimeTemplate.basicFunction("", [ - "while(newTags.length) {", - Template.indent([ - "var newTag = newTags.pop();", - "newTag.sheet.disabled = false" - ]), - "}" - ])} };` - ])}`, - `var cssTextKey = ${runtimeTemplate.returningFunction( - `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction( - "r.cssText", - "r" - )}).join()`, - "link" - )};`, - `${ - RuntimeGlobals.hmrDownloadUpdateHandlers - }.css = ${runtimeTemplate.basicFunction( - "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList", - [ - isNeutralPlatform - ? "if (typeof document === 'undefined') return;" - : "", - "applyHandlers.push(applyHandler);", - `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [ - `var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`, - `var url = ${RuntimeGlobals.publicPath} + filename;`, - "var oldTag = loadStylesheet(chunkId, url);", - "if(!oldTag) return;", - `promises.push(new Promise(${runtimeTemplate.basicFunction( - "resolve, reject", - [ - `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction( - "event", - [ - 'if(event.type !== "load") {', - Template.indent([ - "var errorType = event && event.type;", - "var realHref = event && event.target && event.target.href;", - "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';", - "error.name = 'ChunkLoadError';", - "error.type = errorType;", - "error.request = realHref;", - "reject(error);" - ]), - "} else {", - Template.indent([ - "try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}", - "link.sheet.disabled = true;", - "oldTags.push(oldTag);", - "newTags.push(link);", - "resolve();" - ]), - "}" - ] - )}, ${withFetchPriority ? "undefined," : ""} oldTag);` - ] - )}));` - ])});` - ] - )}` - ]) - : "// no hmr" - ]); - } -} - -module.exports = CssLoadingRuntimeModule; diff --git a/webpack-lib/lib/css/CssModulesPlugin.js b/webpack-lib/lib/css/CssModulesPlugin.js deleted file mode 100644 index fcf451926f1..00000000000 --- a/webpack-lib/lib/css/CssModulesPlugin.js +++ /dev/null @@ -1,887 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncWaterfallHook, SyncHook } = require("tapable"); -const { - ConcatSource, - PrefixSource, - ReplaceSource, - CachedSource, - RawSource -} = require("webpack-sources"); -const Compilation = require("../Compilation"); -const CssModule = require("../CssModule"); -const { tryRunOrWebpackError } = require("../HookWebpackError"); -const HotUpdateChunk = require("../HotUpdateChunk"); -const { - CSS_MODULE_TYPE, - CSS_MODULE_TYPE_GLOBAL, - CSS_MODULE_TYPE_MODULE, - CSS_MODULE_TYPE_AUTO -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const SelfModuleFactory = require("../SelfModuleFactory"); -const Template = require("../Template"); -const WebpackError = require("../WebpackError"); -const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency"); -const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency"); -const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency"); -const CssImportDependency = require("../dependencies/CssImportDependency"); -const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); -const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); -const CssUrlDependency = require("../dependencies/CssUrlDependency"); -const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); -const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const createHash = require("../util/createHash"); -const { getUndoPath } = require("../util/identifier"); -const memoize = require("../util/memoize"); -const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); -const CssGenerator = require("./CssGenerator"); -const CssParser = require("./CssParser"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../CssModule").Inheritance} Inheritance */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Template").RuntimeTemplate} RuntimeTemplate */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/createHash").Algorithm} Algorithm */ -/** @typedef {import("../util/memoize")} Memoize */ - -/** - * @typedef {object} RenderContext - * @property {Chunk} chunk the chunk - * @property {ChunkGraph} chunkGraph the chunk graph - * @property {CodeGenerationResults} codeGenerationResults results of code generation - * @property {RuntimeTemplate} runtimeTemplate the runtime template - * @property {string} uniqueName the unique name - * @property {string} undoPath undo path to css file - * @property {CssModule[]} modules modules - */ - -/** - * @typedef {object} ChunkRenderContext - * @property {Chunk} chunk the chunk - * @property {ChunkGraph} chunkGraph the chunk graph - * @property {CodeGenerationResults} codeGenerationResults results of code generation - * @property {RuntimeTemplate} runtimeTemplate the runtime template - * @property {string} undoPath undo path to css file - */ - -/** - * @typedef {object} CompilationHooks - * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage - * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash - */ - -const getCssLoadingRuntimeModule = memoize(() => - require("./CssLoadingRuntimeModule") -); - -/** - * @param {string} name name - * @returns {{oneOf: [{$ref: string}], definitions: *}} schema - */ -const getSchema = name => { - const { definitions } = require("../../schemas/WebpackOptions.json"); - return { - definitions, - oneOf: [{ $ref: `#/definitions/${name}` }] - }; -}; - -const generatorValidationOptions = { - name: "Css Modules Plugin", - baseDataPath: "generator" -}; -const validateGeneratorOptions = { - css: createSchemaValidation( - require("../../schemas/plugins/css/CssGeneratorOptions.check.js"), - () => getSchema("CssGeneratorOptions"), - generatorValidationOptions - ), - "css/auto": createSchemaValidation( - require("../../schemas/plugins/css/CssAutoGeneratorOptions.check.js"), - () => getSchema("CssAutoGeneratorOptions"), - generatorValidationOptions - ), - "css/module": createSchemaValidation( - require("../../schemas/plugins/css/CssModuleGeneratorOptions.check.js"), - () => getSchema("CssModuleGeneratorOptions"), - generatorValidationOptions - ), - "css/global": createSchemaValidation( - require("../../schemas/plugins/css/CssGlobalGeneratorOptions.check.js"), - () => getSchema("CssGlobalGeneratorOptions"), - generatorValidationOptions - ) -}; - -const parserValidationOptions = { - name: "Css Modules Plugin", - baseDataPath: "parser" -}; -const validateParserOptions = { - css: createSchemaValidation( - require("../../schemas/plugins/css/CssParserOptions.check.js"), - () => getSchema("CssParserOptions"), - parserValidationOptions - ), - "css/auto": createSchemaValidation( - require("../../schemas/plugins/css/CssAutoParserOptions.check.js"), - () => getSchema("CssAutoParserOptions"), - parserValidationOptions - ), - "css/module": createSchemaValidation( - require("../../schemas/plugins/css/CssModuleParserOptions.check.js"), - () => getSchema("CssModuleParserOptions"), - parserValidationOptions - ), - "css/global": createSchemaValidation( - require("../../schemas/plugins/css/CssGlobalParserOptions.check.js"), - () => getSchema("CssGlobalParserOptions"), - parserValidationOptions - ) -}; - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -const PLUGIN_NAME = "CssModulesPlugin"; - -class CssModulesPlugin { - /** - * @param {Compilation} compilation the compilation - * @returns {CompilationHooks} 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 = { - renderModulePackage: new SyncWaterfallHook([ - "source", - "module", - "renderContext" - ]), - chunkHash: new SyncHook(["chunk", "hash", "context"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - constructor() { - /** @type {WeakMap} */ - this._moduleFactoryCache = new WeakMap(); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const hooks = CssModulesPlugin.getCompilationHooks(compilation); - const selfFactory = new SelfModuleFactory(compilation.moduleGraph); - compilation.dependencyFactories.set( - CssImportDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CssImportDependency, - new CssImportDependency.Template() - ); - compilation.dependencyFactories.set( - CssUrlDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CssUrlDependency, - new CssUrlDependency.Template() - ); - compilation.dependencyTemplates.set( - CssLocalIdentifierDependency, - new CssLocalIdentifierDependency.Template() - ); - compilation.dependencyFactories.set( - CssSelfLocalIdentifierDependency, - selfFactory - ); - compilation.dependencyTemplates.set( - CssSelfLocalIdentifierDependency, - new CssSelfLocalIdentifierDependency.Template() - ); - compilation.dependencyFactories.set( - CssIcssImportDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CssIcssImportDependency, - new CssIcssImportDependency.Template() - ); - compilation.dependencyTemplates.set( - CssIcssExportDependency, - new CssIcssExportDependency.Template() - ); - compilation.dependencyTemplates.set( - CssIcssSymbolDependency, - new CssIcssSymbolDependency.Template() - ); - compilation.dependencyTemplates.set( - StaticExportsDependency, - new StaticExportsDependency.Template() - ); - for (const type of [ - CSS_MODULE_TYPE, - CSS_MODULE_TYPE_GLOBAL, - CSS_MODULE_TYPE_MODULE, - CSS_MODULE_TYPE_AUTO - ]) { - normalModuleFactory.hooks.createParser - .for(type) - .tap(PLUGIN_NAME, parserOptions => { - validateParserOptions[type](parserOptions); - const { url, import: importOption, namedExports } = parserOptions; - - switch (type) { - case CSS_MODULE_TYPE: - return new CssParser({ - importOption, - url, - namedExports - }); - case CSS_MODULE_TYPE_GLOBAL: - return new CssParser({ - defaultMode: "global", - importOption, - url, - namedExports - }); - case CSS_MODULE_TYPE_MODULE: - return new CssParser({ - defaultMode: "local", - importOption, - url, - namedExports - }); - case CSS_MODULE_TYPE_AUTO: - return new CssParser({ - defaultMode: "auto", - importOption, - url, - namedExports - }); - } - }); - normalModuleFactory.hooks.createGenerator - .for(type) - .tap(PLUGIN_NAME, generatorOptions => { - validateGeneratorOptions[type](generatorOptions); - - return new CssGenerator(generatorOptions); - }); - normalModuleFactory.hooks.createModuleClass - .for(type) - .tap(PLUGIN_NAME, (createData, resolveData) => { - if (resolveData.dependencies.length > 0) { - // When CSS is imported from CSS there is only one dependency - const dependency = resolveData.dependencies[0]; - - if (dependency instanceof CssImportDependency) { - const parent = - /** @type {CssModule} */ - (compilation.moduleGraph.getParentModule(dependency)); - - if (parent instanceof CssModule) { - /** @type {import("../CssModule").Inheritance | undefined} */ - let inheritance; - - if ( - parent.cssLayer !== undefined || - parent.supports || - parent.media - ) { - if (!inheritance) { - inheritance = []; - } - - inheritance.push([ - parent.cssLayer, - parent.supports, - parent.media - ]); - } - - if (parent.inheritance) { - if (!inheritance) { - inheritance = []; - } - - inheritance.push(...parent.inheritance); - } - - return new CssModule({ - ...createData, - cssLayer: dependency.layer, - supports: dependency.supports, - media: dependency.media, - inheritance - }); - } - - return new CssModule({ - ...createData, - cssLayer: dependency.layer, - supports: dependency.supports, - media: dependency.media - }); - } - } - - return new CssModule(createData); - }); - } - - JavascriptModulesPlugin.getCompilationHooks( - compilation - ).renderModuleContent.tap(PLUGIN_NAME, (source, module) => { - if (module instanceof CssModule && module.hot) { - const exports = module.buildInfo.cssData.exports; - const stringifiedExports = JSON.stringify( - JSON.stringify( - Array.from(exports).reduce((obj, [key, value]) => { - obj[key] = value; - return obj; - }, {}) - ) - ); - - const hmrCode = Template.asString([ - "", - `var __webpack_css_exports__ = ${stringifiedExports};`, - "// only invalidate when locals change", - "if (module.hot.data && module.hot.data.__webpack_css_exports__ && module.hot.data.__webpack_css_exports__ != __webpack_css_exports__) {", - Template.indent("module.hot.invalidate();"), - "} else {", - Template.indent("module.hot.accept();"), - "}", - "module.hot.dispose(function(data) { data.__webpack_css_exports__ = __webpack_css_exports__; });" - ]); - - return new ConcatSource(source, "\n", new RawSource(hmrCode)); - } - }); - const orderedCssModulesPerChunk = new WeakMap(); - compilation.hooks.afterCodeGeneration.tap(PLUGIN_NAME, () => { - const { chunkGraph } = compilation; - for (const chunk of compilation.chunks) { - if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) { - orderedCssModulesPerChunk.set( - chunk, - this.getOrderedChunkCssModules(chunk, chunkGraph, compilation) - ); - } - } - }); - compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => { - hooks.chunkHash.call(chunk, hash, context); - }); - compilation.hooks.contentHash.tap(PLUGIN_NAME, chunk => { - const { - chunkGraph, - codeGenerationResults, - moduleGraph, - runtimeTemplate, - outputOptions: { - hashSalt, - hashDigest, - hashDigestLength, - hashFunction - } - } = compilation; - const hash = createHash(/** @type {Algorithm} */ (hashFunction)); - if (hashSalt) hash.update(hashSalt); - hooks.chunkHash.call(chunk, hash, { - chunkGraph, - codeGenerationResults, - moduleGraph, - runtimeTemplate - }); - const modules = orderedCssModulesPerChunk.get(chunk); - if (modules) { - for (const module of modules) { - hash.update(chunkGraph.getModuleHash(module, chunk.runtime)); - } - } - const digest = /** @type {string} */ (hash.digest(hashDigest)); - chunk.contentHash.css = nonNumericOnlyHash( - digest, - /** @type {number} */ - (hashDigestLength) - ); - }); - compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { - const { chunkGraph } = compilation; - const { hash, chunk, codeGenerationResults, runtimeTemplate } = - options; - - if (chunk instanceof HotUpdateChunk) return result; - - /** @type {CssModule[] | undefined} */ - const modules = orderedCssModulesPerChunk.get(chunk); - if (modules !== undefined) { - const { path: filename, info } = compilation.getPathWithInfo( - CssModulesPlugin.getChunkFilenameTemplate( - chunk, - compilation.outputOptions - ), - { - hash, - runtime: chunk.runtime, - chunk, - contentHashType: "css" - } - ); - const undoPath = getUndoPath( - filename, - /** @type {string} */ - (compilation.outputOptions.path), - false - ); - result.push({ - render: () => - this.renderChunk( - { - chunk, - chunkGraph, - codeGenerationResults, - uniqueName: compilation.outputOptions.uniqueName, - undoPath, - modules, - runtimeTemplate - }, - hooks - ), - filename, - info, - identifier: `css${chunk.id}`, - hash: chunk.contentHash.css - }); - } - return result; - }); - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk the chunk - * @returns {boolean} true, when enabled - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === "jsonp" || chunkLoading === "import"; - }; - const onceForChunkSet = new WeakSet(); - /** - * @param {Chunk} chunk chunk to check - * @param {Set} set runtime requirements - */ - const handler = (chunk, set) => { - if (onceForChunkSet.has(chunk)) return; - onceForChunkSet.add(chunk); - if (!isEnabledForChunk(chunk)) return; - - set.add(RuntimeGlobals.makeNamespaceObject); - - const CssLoadingRuntimeModule = getCssLoadingRuntimeModule(); - compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set)); - }; - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hasCssModules) - .tap(PLUGIN_NAME, handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => - m.type === CSS_MODULE_TYPE || - m.type === CSS_MODULE_TYPE_GLOBAL || - m.type === CSS_MODULE_TYPE_MODULE || - m.type === CSS_MODULE_TYPE_AUTO - ) - ) { - return; - } - - set.add(RuntimeGlobals.hasOwnProperty); - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getChunkCssFilename); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => - m.type === CSS_MODULE_TYPE || - m.type === CSS_MODULE_TYPE_GLOBAL || - m.type === CSS_MODULE_TYPE_MODULE || - m.type === CSS_MODULE_TYPE_AUTO - ) - ) { - return; - } - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getChunkCssFilename); - }); - } - ); - } - - /** - * @param {Chunk} chunk chunk - * @param {Iterable} modules unordered modules - * @param {Compilation} compilation compilation - * @returns {Module[]} ordered modules - */ - getModulesInOrder(chunk, modules, compilation) { - if (!modules) return []; - - /** @type {Module[]} */ - const modulesList = [...modules]; - - // Get ordered list of modules per chunk group - // Lists are in reverse order to allow to use Array.pop() - const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => { - const sortedModules = modulesList - .map(module => ({ - module, - index: chunkGroup.getModulePostOrderIndex(module) - })) - .filter(item => item.index !== undefined) - .sort( - (a, b) => - /** @type {number} */ (b.index) - /** @type {number} */ (a.index) - ) - .map(item => item.module); - - return { list: sortedModules, set: new Set(sortedModules) }; - }); - - if (modulesByChunkGroup.length === 1) - return modulesByChunkGroup[0].list.reverse(); - - /** - * @param {{ list: Module[] }} a a - * @param {{ list: Module[] }} b b - * @returns {-1 | 0 | 1} result - */ - const compareModuleLists = ({ list: a }, { list: b }) => { - if (a.length === 0) { - return b.length === 0 ? 0 : 1; - } - if (b.length === 0) return -1; - return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]); - }; - - modulesByChunkGroup.sort(compareModuleLists); - - /** @type {Module[]} */ - const finalModules = []; - - for (;;) { - const failedModules = new Set(); - const list = modulesByChunkGroup[0].list; - if (list.length === 0) { - // done, everything empty - break; - } - /** @type {Module} */ - let selectedModule = list[list.length - 1]; - let hasFailed; - outer: for (;;) { - for (const { list, set } of modulesByChunkGroup) { - if (list.length === 0) continue; - const lastModule = list[list.length - 1]; - if (lastModule === selectedModule) continue; - if (!set.has(selectedModule)) continue; - failedModules.add(selectedModule); - if (failedModules.has(lastModule)) { - // There is a conflict, try other alternatives - hasFailed = lastModule; - continue; - } - selectedModule = lastModule; - hasFailed = false; - continue outer; // restart - } - break; - } - if (hasFailed) { - // There is a not resolve-able conflict with the selectedModule - // TODO print better warning - compilation.warnings.push( - new WebpackError( - `chunk ${chunk.name || chunk.id}\nConflicting order between ${ - /** @type {Module} */ - (hasFailed).readableIdentifier(compilation.requestShortener) - } and ${selectedModule.readableIdentifier( - compilation.requestShortener - )}` - ) - ); - selectedModule = /** @type {Module} */ (hasFailed); - } - // Insert the selected module into the final modules list - finalModules.push(selectedModule); - // Remove the selected module from all lists - for (const { list, set } of modulesByChunkGroup) { - const lastModule = list[list.length - 1]; - if (lastModule === selectedModule) list.pop(); - else if (hasFailed && set.has(selectedModule)) { - const idx = list.indexOf(selectedModule); - if (idx >= 0) list.splice(idx, 1); - } - } - modulesByChunkGroup.sort(compareModuleLists); - } - return finalModules; - } - - /** - * @param {Chunk} chunk chunk - * @param {ChunkGraph} chunkGraph chunk graph - * @param {Compilation} compilation compilation - * @returns {Module[]} ordered css modules - */ - getOrderedChunkCssModules(chunk, chunkGraph, compilation) { - return [ - ...this.getModulesInOrder( - chunk, - /** @type {Iterable} */ - ( - chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - "css-import", - compareModulesByIdentifier - ) - ), - compilation - ), - ...this.getModulesInOrder( - chunk, - /** @type {Iterable} */ - ( - chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - "css", - compareModulesByIdentifier - ) - ), - compilation - ) - ]; - } - - /** - * @param {CssModule} module css module - * @param {ChunkRenderContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @returns {Source} css module source - */ - renderModule(module, renderContext, hooks) { - const { codeGenerationResults, chunk, undoPath } = renderContext; - const codeGenResult = codeGenerationResults.get(module, chunk.runtime); - const moduleSourceContent = - /** @type {Source} */ - ( - codeGenResult.sources.get("css") || - codeGenResult.sources.get("css-import") - ); - const cacheEntry = this._moduleFactoryCache.get(moduleSourceContent); - - /** @type {Inheritance} */ - const inheritance = [[module.cssLayer, module.supports, module.media]]; - if (module.inheritance) { - inheritance.push(...module.inheritance); - } - - let source; - if ( - cacheEntry && - cacheEntry.undoPath === undoPath && - cacheEntry.inheritance.every(([layer, supports, media], i) => { - const item = inheritance[i]; - if (Array.isArray(item)) { - return layer === item[0] && supports === item[1] && media === item[2]; - } - return false; - }) - ) { - source = cacheEntry.source; - } else { - const moduleSourceCode = - /** @type {string} */ - (moduleSourceContent.source()); - const publicPathAutoRegex = new RegExp( - CssUrlDependency.PUBLIC_PATH_AUTO, - "g" - ); - /** @type {Source} */ - let moduleSource = new ReplaceSource(moduleSourceContent); - let match; - while ((match = publicPathAutoRegex.exec(moduleSourceCode))) { - /** @type {ReplaceSource} */ (moduleSource).replace( - match.index, - (match.index += match[0].length - 1), - undoPath - ); - } - - for (let i = 0; i < inheritance.length; i++) { - const layer = inheritance[i][0]; - const supports = inheritance[i][1]; - const media = inheritance[i][2]; - - if (media) { - moduleSource = new ConcatSource( - `@media ${media} {\n`, - new PrefixSource("\t", moduleSource), - "}\n" - ); - } - - if (supports) { - moduleSource = new ConcatSource( - `@supports (${supports}) {\n`, - new PrefixSource("\t", moduleSource), - "}\n" - ); - } - - // Layer can be anonymous - if (layer !== undefined && layer !== null) { - moduleSource = new ConcatSource( - `@layer${layer ? ` ${layer}` : ""} {\n`, - new PrefixSource("\t", moduleSource), - "}\n" - ); - } - } - - if (moduleSource) { - moduleSource = new ConcatSource(moduleSource, "\n"); - } - - source = new CachedSource(moduleSource); - this._moduleFactoryCache.set(moduleSourceContent, { - inheritance, - undoPath, - source - }); - } - - return tryRunOrWebpackError( - () => hooks.renderModulePackage.call(source, module, renderContext), - "CssModulesPlugin.getCompilationHooks().renderModulePackage" - ); - } - - /** - * @param {RenderContext} renderContext the render context - * @param {CompilationHooks} hooks hooks - * @returns {Source} generated source - */ - renderChunk( - { - undoPath, - chunk, - chunkGraph, - codeGenerationResults, - modules, - runtimeTemplate - }, - hooks - ) { - const source = new ConcatSource(); - for (const module of modules) { - try { - const moduleSource = this.renderModule( - module, - { - undoPath, - chunk, - chunkGraph, - codeGenerationResults, - runtimeTemplate - }, - hooks - ); - source.add(moduleSource); - } catch (err) { - /** @type {Error} */ - (err).message += `\nduring rendering of css ${module.identifier()}`; - throw err; - } - } - chunk.rendered = true; - return source; - } - - /** - * @param {Chunk} chunk chunk - * @param {OutputOptions} outputOptions output options - * @returns {TemplatePath} used filename template - */ - static getChunkFilenameTemplate(chunk, outputOptions) { - if (chunk.cssFilenameTemplate) { - return chunk.cssFilenameTemplate; - } else if (chunk.canBeInitial()) { - return /** @type {TemplatePath} */ (outputOptions.cssFilename); - } - return /** @type {TemplatePath} */ (outputOptions.cssChunkFilename); - } - - /** - * @param {Chunk} chunk chunk - * @param {ChunkGraph} chunkGraph chunk graph - * @returns {boolean} true, when the chunk has css - */ - static chunkHasCss(chunk, chunkGraph) { - return ( - Boolean(chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")) || - Boolean( - chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import") - ) - ); - } -} - -module.exports = CssModulesPlugin; diff --git a/webpack-lib/lib/css/CssParser.js b/webpack-lib/lib/css/CssParser.js deleted file mode 100644 index c8ae863d9a5..00000000000 --- a/webpack-lib/lib/css/CssParser.js +++ /dev/null @@ -1,1602 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const vm = require("vm"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); -const ModuleDependencyWarning = require("../ModuleDependencyWarning"); -const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants"); -const Parser = require("../Parser"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const WebpackError = require("../WebpackError"); -const ConstDependency = require("../dependencies/ConstDependency"); -const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency"); -const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency"); -const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency"); -const CssImportDependency = require("../dependencies/CssImportDependency"); -const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency"); -const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency"); -const CssUrlDependency = require("../dependencies/CssUrlDependency"); -const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); -const binarySearchBounds = require("../util/binarySearchBounds"); -const { parseResource } = require("../util/identifier"); -const { - webpackCommentRegExp, - createMagicCommentContext -} = require("../util/magicComment"); -const walkCssTokens = require("./walkCssTokens"); - -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ -/** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */ - -/** @typedef {[number, number]} Range */ -/** @typedef {{ line: number, column: number }} Position */ -/** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */ - -const CC_COLON = ":".charCodeAt(0); -const CC_SLASH = "/".charCodeAt(0); -const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); -const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); -const CC_LOWER_F = "f".charCodeAt(0); -const CC_UPPER_F = "F".charCodeAt(0); - -// https://www.w3.org/TR/css-syntax-3/#newline -// We don't have `preprocessing` stage, so we need specify all of them -const STRING_MULTILINE = /\\[\n\r\f]/g; -// https://www.w3.org/TR/css-syntax-3/#whitespace -const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g; -const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g; -const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i; -const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/; -const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY = - /^(-\w+-)?animation(-name)?$/i; -const IS_MODULES = /\.module(s)?\.[^.]+$/i; -const CSS_COMMENT = /\/\*((?!\*\/).*?)\*\//g; - -/** - * @param {string} str url string - * @param {boolean} isString is url wrapped in quotes - * @returns {string} normalized url - */ -const normalizeUrl = (str, isString) => { - // Remove extra spaces and newlines: - // `url("im\ - // g.png")` - if (isString) { - str = str.replace(STRING_MULTILINE, ""); - } - - str = str - // Remove unnecessary spaces from `url(" img.png ")` - .replace(TRIM_WHITE_SPACES, "") - // Unescape - .replace(UNESCAPE, match => { - if (match.length > 2) { - return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16)); - } - return match[1]; - }); - - if (/^data:/i.test(str)) { - return str; - } - - if (str.includes("%")) { - // Convert `url('%2E/img.png')` -> `url('./img.png')` - try { - str = decodeURIComponent(str); - } catch (_err) { - // Ignore - } - } - - return str; -}; - -// eslint-disable-next-line no-useless-escape -const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/; -const regexExcessiveSpaces = - /(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g; - -/** - * @param {string} str string - * @returns {string} escaped identifier - */ -const escapeIdentifier = str => { - let output = ""; - let counter = 0; - - while (counter < str.length) { - const character = str.charAt(counter++); - - let value; - - if (/[\t\n\f\r\u000B]/.test(character)) { - const codePoint = character.charCodeAt(0); - - value = `\\${codePoint.toString(16).toUpperCase()} `; - } else if (character === "\\" || regexSingleEscape.test(character)) { - value = `\\${character}`; - } else { - value = character; - } - - output += value; - } - - const firstChar = str.charAt(0); - - if (/^-[-\d]/.test(output)) { - output = `\\-${output.slice(1)}`; - } else if (/\d/.test(firstChar)) { - output = `\\3${firstChar} ${output.slice(1)}`; - } - - // Remove spaces after `\HEX` escapes that are not followed by a hex digit, - // since they’re redundant. Note that this is only possible if the escape - // sequence isn’t preceded by an odd number of backslashes. - output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => { - if ($1 && $1.length % 2) { - // It’s not safe to remove the space, so don’t. - return $0; - } - - // Strip the space. - return ($1 || "") + $2; - }); - - return output; -}; - -const CONTAINS_ESCAPE = /\\/; - -/** - * @param {string} str string - * @returns {[string, number] | undefined} hex - */ -const gobbleHex = str => { - const lower = str.toLowerCase(); - let hex = ""; - let spaceTerminated = false; - - for (let i = 0; i < 6 && lower[i] !== undefined; i++) { - const code = lower.charCodeAt(i); - // check to see if we are dealing with a valid hex char [a-f|0-9] - const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57); - // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point - spaceTerminated = code === 32; - if (!valid) break; - hex += lower[i]; - } - - if (hex.length === 0) return undefined; - - const codePoint = Number.parseInt(hex, 16); - const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff; - - // Add special case for - // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point" - // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point - if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) { - return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)]; - } - - return [ - String.fromCodePoint(codePoint), - hex.length + (spaceTerminated ? 1 : 0) - ]; -}; - -/** - * @param {string} str string - * @returns {string} unescaped string - */ -const unescapeIdentifier = str => { - const needToProcess = CONTAINS_ESCAPE.test(str); - if (!needToProcess) return str; - let ret = ""; - for (let i = 0; i < str.length; i++) { - if (str[i] === "\\") { - const gobbled = gobbleHex(str.slice(i + 1, i + 7)); - if (gobbled !== undefined) { - ret += gobbled[0]; - i += gobbled[1]; - continue; - } - // Retain a pair of \\ if double escaped `\\\\` - // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e - if (str[i + 1] === "\\") { - ret += "\\"; - i += 1; - continue; - } - // if \\ is at the end of the string retain it - // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb - if (str.length === i + 1) { - ret += str[i]; - } - continue; - } - ret += str[i]; - } - - return ret; -}; - -class LocConverter { - /** - * @param {string} input input - */ - constructor(input) { - this._input = input; - this.line = 1; - this.column = 0; - this.pos = 0; - } - - /** - * @param {number} pos position - * @returns {LocConverter} location converter - */ - get(pos) { - if (this.pos !== pos) { - if (this.pos < pos) { - const str = this._input.slice(this.pos, pos); - let i = str.lastIndexOf("\n"); - if (i === -1) { - this.column += str.length; - } else { - this.column = str.length - i - 1; - this.line++; - while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) - this.line++; - } - } else { - let i = this._input.lastIndexOf("\n", this.pos); - while (i >= pos) { - this.line--; - i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1; - } - this.column = pos - i; - } - this.pos = pos; - } - return this; - } -} - -const EMPTY_COMMENT_OPTIONS = { - options: null, - errors: null -}; - -const CSS_MODE_TOP_LEVEL = 0; -const CSS_MODE_IN_BLOCK = 1; - -const eatUntilSemi = walkCssTokens.eatUntil(";"); -const eatUntilLeftCurly = walkCssTokens.eatUntil("{"); -const eatSemi = walkCssTokens.eatUntil(";"); - -class CssParser extends Parser { - /** - * @param {object} options options - * @param {boolean=} options.importOption need handle `@import` - * @param {boolean=} options.url need handle URLs - * @param {("pure" | "global" | "local" | "auto")=} options.defaultMode default mode - * @param {boolean=} options.namedExports is named exports - */ - constructor({ - defaultMode = "pure", - importOption = true, - url = true, - namedExports = true - } = {}) { - super(); - this.defaultMode = defaultMode; - this.import = importOption; - this.url = url; - this.namedExports = namedExports; - /** @type {Comment[] | undefined} */ - this.comments = undefined; - this.magicCommentContext = createMagicCommentContext(); - } - - /** - * @param {ParserState} state parser state - * @param {string} message warning message - * @param {LocConverter} locConverter location converter - * @param {number} start start offset - * @param {number} end end offset - */ - _emitWarning(state, message, locConverter, start, end) { - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - - state.current.addWarning( - new ModuleDependencyWarning(state.module, new WebpackError(message), { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - }) - ); - } - - /** - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - if (Buffer.isBuffer(source)) { - source = source.toString("utf-8"); - } else if (typeof source === "object") { - throw new Error("webpackAst is unexpected for the CssParser"); - } - if (source[0] === "\uFEFF") { - source = source.slice(1); - } - - let mode = this.defaultMode; - - const module = state.module; - - if ( - mode === "auto" && - module.type === CSS_MODULE_TYPE_AUTO && - IS_MODULES.test( - parseResource(module.matchResource || module.resource).path - ) - ) { - mode = "local"; - } - - const isModules = mode === "global" || mode === "local"; - - const locConverter = new LocConverter(source); - - /** @type {number} */ - let scope = CSS_MODE_TOP_LEVEL; - /** @type {boolean} */ - let allowImportAtRule = true; - /** @type [string, number, number][] */ - const balanced = []; - let lastTokenEndForComments = 0; - - /** @type {boolean} */ - let isNextRulePrelude = isModules; - /** @type {number} */ - let blockNestingLevel = 0; - /** @type {"local" | "global" | undefined} */ - let modeData; - /** @type {boolean} */ - let inAnimationProperty = false; - /** @type {[number, number, boolean] | undefined} */ - let lastIdentifier; - /** @type {Set} */ - const declaredCssVariables = new Set(); - /** @type {Map} */ - const icssDefinitions = new Map(); - - /** - * @param {string} input input - * @param {number} pos position - * @returns {boolean} true, when next is nested syntax - */ - const isNextNestedSyntax = (input, pos) => { - pos = walkCssTokens.eatWhitespaceAndComments(input, pos); - - if (input[pos] === "}") { - return false; - } - - // According spec only identifier can be used as a property name - const isIdentifier = walkCssTokens.isIdentStartCodePoint( - input.charCodeAt(pos) - ); - - return !isIdentifier; - }; - /** - * @returns {boolean} true, when in local scope - */ - const isLocalMode = () => - modeData === "local" || (mode === "local" && modeData === undefined); - - /** - * @param {string} input input - * @param {number} pos start position - * @param {(input: string, pos: number) => number} eater eater - * @returns {[number,string]} new position and text - */ - const eatText = (input, pos, eater) => { - let text = ""; - for (;;) { - if (input.charCodeAt(pos) === CC_SLASH) { - const newPos = walkCssTokens.eatComments(input, pos); - if (pos !== newPos) { - pos = newPos; - if (pos === input.length) break; - } else { - text += "/"; - pos++; - if (pos === input.length) break; - } - } - const newPos = eater(input, pos); - if (pos !== newPos) { - text += input.slice(pos, newPos); - pos = newPos; - } else { - break; - } - if (pos === input.length) break; - } - return [pos, text.trimEnd()]; - }; - - /** - * @param {0 | 1} type import or export - * @param {string} input input - * @param {number} pos start position - * @returns {number} position after parse - */ - const parseImportOrExport = (type, input, pos) => { - pos = walkCssTokens.eatWhitespaceAndComments(input, pos); - let importPath; - if (type === 0) { - let cc = input.charCodeAt(pos); - if (cc !== CC_LEFT_PARENTHESIS) { - this._emitWarning( - state, - `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`, - locConverter, - pos, - pos - ); - return pos; - } - pos++; - const stringStart = pos; - const str = walkCssTokens.eatString(input, pos); - if (!str) { - this._emitWarning( - state, - `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected string)`, - locConverter, - stringStart, - pos - ); - return pos; - } - importPath = input.slice(str[0] + 1, str[1] - 1); - pos = str[1]; - pos = walkCssTokens.eatWhitespaceAndComments(input, pos); - cc = input.charCodeAt(pos); - if (cc !== CC_RIGHT_PARENTHESIS) { - this._emitWarning( - state, - `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`, - locConverter, - pos, - pos - ); - return pos; - } - pos++; - pos = walkCssTokens.eatWhitespaceAndComments(input, pos); - } - - /** - * @param {string} name name - * @param {string} value value - * @param {number} start start of position - * @param {number} end end of position - */ - const createDep = (name, value, start, end) => { - if (type === 0) { - icssDefinitions.set(name, { - path: /** @type {string} */ (importPath), - value - }); - } else if (type === 1) { - const dep = new CssIcssExportDependency(name, value); - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } - }; - - let needTerminate = false; - let balanced = 0; - /** @type {undefined | 0 | 1 | 2} */ - let scope; - - /** @type {[number, number] | undefined} */ - let name; - /** @type {number | undefined} */ - let value; - - /** @type {CssTokenCallbacks} */ - const callbacks = { - leftCurlyBracket: (_input, _start, end) => { - balanced++; - - if (scope === undefined) { - scope = 0; - } - - return end; - }, - rightCurlyBracket: (_input, _start, end) => { - balanced--; - - if (scope === 2) { - createDep( - input.slice(name[0], name[1]), - input.slice(value, end - 1).trim(), - name[1], - end - 1 - ); - scope = 0; - } - - if (balanced === 0 && scope === 0) { - needTerminate = true; - } - - return end; - }, - identifier: (_input, start, end) => { - if (scope === 0) { - name = [start, end]; - scope = 1; - } - - return end; - }, - colon: (_input, _start, end) => { - if (scope === 1) { - scope = 2; - value = walkCssTokens.eatWhitespace(input, end); - return value; - } - - return end; - }, - semicolon: (input, _start, end) => { - if (scope === 2) { - createDep( - input.slice(name[0], name[1]), - input.slice(value, end - 1), - name[1], - end - 1 - ); - scope = 0; - } - - return end; - }, - needTerminate: () => needTerminate - }; - - pos = walkCssTokens(input, pos, callbacks); - pos = walkCssTokens.eatWhiteLine(input, pos); - - return pos; - }; - const eatPropertyName = walkCssTokens.eatUntil(":{};"); - /** - * @param {string} input input - * @param {number} pos name start position - * @param {number} end name end position - * @returns {number} position after handling - */ - const processLocalDeclaration = (input, pos, end) => { - modeData = undefined; - pos = walkCssTokens.eatWhitespaceAndComments(input, pos); - const propertyNameStart = pos; - const [propertyNameEnd, propertyName] = eatText( - input, - pos, - eatPropertyName - ); - if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end; - pos = propertyNameEnd + 1; - if (propertyName.startsWith("--") && propertyName.length >= 3) { - // CSS Variable - const { line: sl, column: sc } = locConverter.get(propertyNameStart); - const { line: el, column: ec } = locConverter.get(propertyNameEnd); - const name = unescapeIdentifier(propertyName.slice(2)); - const dep = new CssLocalIdentifierDependency( - name, - [propertyNameStart, propertyNameEnd], - "--" - ); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - declaredCssVariables.add(name); - } else if ( - OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName) - ) { - inAnimationProperty = true; - } - return pos; - }; - /** - * @param {string} input input - */ - const processDeclarationValueDone = input => { - if (inAnimationProperty && lastIdentifier) { - const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]); - const { line: el, column: ec } = locConverter.get(lastIdentifier[1]); - const name = unescapeIdentifier( - lastIdentifier[2] - ? input.slice(lastIdentifier[0], lastIdentifier[1]) - : input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1) - ); - const dep = new CssSelfLocalIdentifierDependency(name, [ - lastIdentifier[0], - lastIdentifier[1] - ]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - lastIdentifier = undefined; - } - }; - - /** - * @param {string} input input - * @param {number} start start - * @param {number} end end - * @returns {number} end - */ - const comment = (input, start, end) => { - if (!this.comments) this.comments = []; - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - - /** @type {Comment} */ - const comment = { - value: input.slice(start + 2, end - 2), - range: [start, end], - loc: { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - } - }; - this.comments.push(comment); - return end; - }; - - walkCssTokens(source, 0, { - comment, - leftCurlyBracket: (input, start, end) => { - switch (scope) { - case CSS_MODE_TOP_LEVEL: { - allowImportAtRule = false; - scope = CSS_MODE_IN_BLOCK; - - if (isModules) { - blockNestingLevel = 1; - isNextRulePrelude = isNextNestedSyntax(input, end); - } - - break; - } - case CSS_MODE_IN_BLOCK: { - if (isModules) { - blockNestingLevel++; - isNextRulePrelude = isNextNestedSyntax(input, end); - } - break; - } - } - return end; - }, - rightCurlyBracket: (input, start, end) => { - switch (scope) { - case CSS_MODE_IN_BLOCK: { - if (--blockNestingLevel === 0) { - scope = CSS_MODE_TOP_LEVEL; - - if (isModules) { - isNextRulePrelude = true; - modeData = undefined; - } - } else if (isModules) { - if (isLocalMode()) { - processDeclarationValueDone(input); - inAnimationProperty = false; - } - - isNextRulePrelude = isNextNestedSyntax(input, end); - } - break; - } - } - return end; - }, - url: (input, start, end, contentStart, contentEnd) => { - if (!this.url) { - return end; - } - - const { options, errors: commentErrors } = this.parseCommentOptions([ - lastTokenEndForComments, - end - ]); - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - comment.loc - ) - ); - } - } - if (options && options.webpackIgnore !== undefined) { - if (typeof options.webpackIgnore !== "boolean") { - const { line: sl, column: sc } = locConverter.get( - lastTokenEndForComments - ); - const { line: el, column: ec } = locConverter.get(end); - - state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, - { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - } - ) - ); - } else if (options.webpackIgnore) { - return end; - } - } - const value = normalizeUrl( - input.slice(contentStart, contentEnd), - false - ); - // Ignore `url()`, `url('')` and `url("")`, they are valid by spec - if (value.length === 0) return end; - const dep = new CssUrlDependency(value, [start, end], "url"); - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - module.addCodeGenerationDependency(dep); - return end; - }, - string: (_input, start, end) => { - switch (scope) { - case CSS_MODE_IN_BLOCK: { - if (inAnimationProperty && balanced.length === 0) { - lastIdentifier = [start, end, false]; - } - } - } - return end; - }, - atKeyword: (input, start, end) => { - const name = input.slice(start, end).toLowerCase(); - - switch (name) { - case "@namespace": { - this._emitWarning( - state, - "'@namespace' is not supported in bundled CSS", - locConverter, - start, - end - ); - - return eatUntilSemi(input, start); - } - case "@import": { - if (!this.import) { - return eatSemi(input, end); - } - - if (!allowImportAtRule) { - this._emitWarning( - state, - "Any '@import' rules must precede all other rules", - locConverter, - start, - end - ); - return end; - } - - const tokens = walkCssTokens.eatImportTokens(input, end, { - comment - }); - if (!tokens[3]) return end; - const semi = tokens[3][1]; - if (!tokens[0]) { - this._emitWarning( - state, - `Expected URL in '${input.slice(start, semi)}'`, - locConverter, - start, - semi - ); - return end; - } - - const urlToken = tokens[0]; - const url = normalizeUrl( - input.slice(urlToken[2], urlToken[3]), - true - ); - const newline = walkCssTokens.eatWhiteLine(input, semi); - const { options, errors: commentErrors } = this.parseCommentOptions( - [end, urlToken[1]] - ); - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - comment.loc - ) - ); - } - } - if (options && options.webpackIgnore !== undefined) { - if (typeof options.webpackIgnore !== "boolean") { - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(newline); - - state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, - { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - } - ) - ); - } else if (options.webpackIgnore) { - return newline; - } - } - if (url.length === 0) { - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(newline); - const dep = new ConstDependency("", [start, newline]); - module.addPresentationalDependency(dep); - dep.setLoc(sl, sc, el, ec); - - return newline; - } - - let layer; - - if (tokens[1]) { - layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim(); - } - - let supports; - - if (tokens[2]) { - supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim(); - } - - const last = tokens[2] || tokens[1] || tokens[0]; - const mediaStart = walkCssTokens.eatWhitespaceAndComments( - input, - last[1] - ); - - let media; - - if (mediaStart !== semi - 1) { - media = input.slice(mediaStart, semi - 1).trim(); - } - - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(newline); - const dep = new CssImportDependency( - url, - [start, newline], - layer, - supports && supports.length > 0 ? supports : undefined, - media && media.length > 0 ? media : undefined - ); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - - return newline; - } - default: { - if (isModules) { - if (name === "@value") { - const semi = eatUntilSemi(input, end); - const atRuleEnd = semi + 1; - const params = input.slice(end, semi); - let [alias, from] = params.split(/\s*from\s*/); - - if (from) { - const aliases = alias - .replace(CSS_COMMENT, " ") - .trim() - .replace(/^\(|\)$/g, "") - .split(/\s*,\s*/); - - from = from.replace(CSS_COMMENT, "").trim(); - - const isExplicitImport = from[0] === "'" || from[0] === '"'; - - if (isExplicitImport) { - from = from.slice(1, -1); - } - - for (const alias of aliases) { - const [name, aliasName] = alias.split(/\s*as\s*/); - - icssDefinitions.set(aliasName || name, { - value: name, - path: from - }); - } - } else { - const ident = walkCssTokens.eatIdentSequence(alias, 0); - - if (!ident) { - this._emitWarning( - state, - `Broken '@value' at-rule: ${input.slice( - start, - atRuleEnd - )}'`, - locConverter, - start, - atRuleEnd - ); - - const dep = new ConstDependency("", [start, atRuleEnd]); - module.addPresentationalDependency(dep); - return atRuleEnd; - } - - const pos = walkCssTokens.eatWhitespaceAndComments( - alias, - ident[1] - ); - - const name = alias.slice(ident[0], ident[1]); - let value = - alias.charCodeAt(pos) === CC_COLON - ? alias.slice(pos + 1) - : alias.slice(ident[1]); - - if (value && !/^\s+$/.test(value)) { - value = value.trim(); - } - - if (icssDefinitions.has(value)) { - const def = icssDefinitions.get(value); - - value = def.value; - } - - icssDefinitions.set(name, { value }); - - const dep = new CssIcssExportDependency(name, value); - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } - - const dep = new ConstDependency("", [start, atRuleEnd]); - module.addPresentationalDependency(dep); - return atRuleEnd; - } else if ( - OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) && - isLocalMode() - ) { - const ident = walkCssTokens.eatIdentSequenceOrString( - input, - end - ); - if (!ident) return end; - const name = unescapeIdentifier( - ident[2] === true - ? input.slice(ident[0], ident[1]) - : input.slice(ident[0] + 1, ident[1] - 1) - ); - const { line: sl, column: sc } = locConverter.get(ident[0]); - const { line: el, column: ec } = locConverter.get(ident[1]); - const dep = new CssLocalIdentifierDependency(name, [ - ident[0], - ident[1] - ]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - return ident[1]; - } else if (name === "@property" && isLocalMode()) { - const ident = walkCssTokens.eatIdentSequence(input, end); - if (!ident) return end; - let name = input.slice(ident[0], ident[1]); - if (!name.startsWith("--") || name.length < 3) return end; - name = unescapeIdentifier(name.slice(2)); - declaredCssVariables.add(name); - const { line: sl, column: sc } = locConverter.get(ident[0]); - const { line: el, column: ec } = locConverter.get(ident[1]); - const dep = new CssLocalIdentifierDependency( - name, - [ident[0], ident[1]], - "--" - ); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - return ident[1]; - } else if (name === "@scope") { - isNextRulePrelude = true; - return end; - } - - isNextRulePrelude = false; - } - } - } - - return end; - }, - semicolon: (input, start, end) => { - if (isModules && scope === CSS_MODE_IN_BLOCK) { - if (isLocalMode()) { - processDeclarationValueDone(input); - inAnimationProperty = false; - } - - isNextRulePrelude = isNextNestedSyntax(input, end); - } - return end; - }, - identifier: (input, start, end) => { - if (isModules) { - if (icssDefinitions.has(input.slice(start, end))) { - const name = input.slice(start, end); - let { path, value } = icssDefinitions.get(name); - - if (path) { - if (icssDefinitions.has(path)) { - const definition = icssDefinitions.get(path); - - path = definition.value.slice(1, -1); - } - - const dep = new CssIcssImportDependency(path, value, [ - start, - end - 1 - ]); - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end - 1); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } else { - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - const dep = new CssIcssSymbolDependency(name, value, [ - start, - end - ]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } - - return end; - } - - switch (scope) { - case CSS_MODE_IN_BLOCK: { - if (isLocalMode()) { - // Handle only top level values and not inside functions - if (inAnimationProperty && balanced.length === 0) { - lastIdentifier = [start, end, true]; - } else { - return processLocalDeclaration(input, start, end); - } - } - break; - } - } - } - - return end; - }, - delim: (input, start, end) => { - if (isNextRulePrelude && isLocalMode()) { - const ident = walkCssTokens.skipCommentsAndEatIdentSequence( - input, - end - ); - if (!ident) return end; - const name = unescapeIdentifier(input.slice(ident[0], ident[1])); - const dep = new CssLocalIdentifierDependency(name, [ - ident[0], - ident[1] - ]); - const { line: sl, column: sc } = locConverter.get(ident[0]); - const { line: el, column: ec } = locConverter.get(ident[1]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - return ident[1]; - } - - return end; - }, - hash: (input, start, end, isID) => { - if (isNextRulePrelude && isLocalMode() && isID) { - const valueStart = start + 1; - const name = unescapeIdentifier(input.slice(valueStart, end)); - const dep = new CssLocalIdentifierDependency(name, [valueStart, end]); - const { line: sl, column: sc } = locConverter.get(start); - const { line: el, column: ec } = locConverter.get(end); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } - - return end; - }, - colon: (input, start, end) => { - if (isModules) { - const ident = walkCssTokens.skipCommentsAndEatIdentSequence( - input, - end - ); - if (!ident) return end; - const name = input.slice(ident[0], ident[1]).toLowerCase(); - - switch (scope) { - case CSS_MODE_TOP_LEVEL: { - if (name === "import") { - const pos = parseImportOrExport(0, input, ident[1]); - const dep = new ConstDependency("", [start, pos]); - module.addPresentationalDependency(dep); - return pos; - } else if (name === "export") { - const pos = parseImportOrExport(1, input, ident[1]); - const dep = new ConstDependency("", [start, pos]); - module.addPresentationalDependency(dep); - return pos; - } - } - // falls through - default: { - if (isNextRulePrelude) { - const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS; - - if (isFn && name === "local") { - const end = ident[1] + 1; - modeData = "local"; - const dep = new ConstDependency("", [start, end]); - module.addPresentationalDependency(dep); - balanced.push([":local", start, end]); - return end; - } else if (name === "local") { - modeData = "local"; - // Eat extra whitespace - end = walkCssTokens.eatWhitespace(input, ident[1]); - - if (ident[1] === end) { - this._emitWarning( - state, - `Missing whitespace after ':local' in '${input.slice( - start, - eatUntilLeftCurly(input, end) + 1 - )}'`, - locConverter, - start, - end - ); - } - - const dep = new ConstDependency("", [start, end]); - module.addPresentationalDependency(dep); - return end; - } else if (isFn && name === "global") { - const end = ident[1] + 1; - modeData = "global"; - const dep = new ConstDependency("", [start, end]); - module.addPresentationalDependency(dep); - balanced.push([":global", start, end]); - return end; - } else if (name === "global") { - modeData = "global"; - // Eat extra whitespace - end = walkCssTokens.eatWhitespace(input, ident[1]); - - if (ident[1] === end) { - this._emitWarning( - state, - `Missing whitespace after ':global' in '${input.slice( - start, - eatUntilLeftCurly(input, end) + 1 - )}'`, - locConverter, - start, - end - ); - } - - const dep = new ConstDependency("", [start, end]); - module.addPresentationalDependency(dep); - return end; - } - } - } - } - } - - lastTokenEndForComments = end; - - return end; - }, - function: (input, start, end) => { - const name = input - .slice(start, end - 1) - .replace(/\\/g, "") - .toLowerCase(); - - balanced.push([name, start, end]); - - switch (name) { - case "src": - case "url": { - if (!this.url) { - return end; - } - - const string = walkCssTokens.eatString(input, end); - if (!string) return end; - const { options, errors: commentErrors } = this.parseCommentOptions( - [lastTokenEndForComments, end] - ); - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - comment.loc - ) - ); - } - } - if (options && options.webpackIgnore !== undefined) { - if (typeof options.webpackIgnore !== "boolean") { - const { line: sl, column: sc } = locConverter.get(string[0]); - const { line: el, column: ec } = locConverter.get(string[1]); - - state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, - { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - } - ) - ); - } else if (options.webpackIgnore) { - return end; - } - } - const value = normalizeUrl( - input.slice(string[0] + 1, string[1] - 1), - true - ); - // Ignore `url()`, `url('')` and `url("")`, they are valid by spec - if (value.length === 0) return end; - const isUrl = name === "url" || name === "src"; - const dep = new CssUrlDependency( - value, - [string[0], string[1]], - isUrl ? "string" : "url" - ); - const { line: sl, column: sc } = locConverter.get(string[0]); - const { line: el, column: ec } = locConverter.get(string[1]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - module.addCodeGenerationDependency(dep); - return string[1]; - } - default: { - if (this.url && IMAGE_SET_FUNCTION.test(name)) { - lastTokenEndForComments = end; - const values = walkCssTokens.eatImageSetStrings(input, end, { - comment - }); - if (values.length === 0) return end; - for (const [index, string] of values.entries()) { - const value = normalizeUrl( - input.slice(string[0] + 1, string[1] - 1), - true - ); - if (value.length === 0) return end; - const { options, errors: commentErrors } = - this.parseCommentOptions([ - index === 0 ? start : values[index - 1][1], - string[1] - ]); - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - comment.loc - ) - ); - } - } - if (options && options.webpackIgnore !== undefined) { - if (typeof options.webpackIgnore !== "boolean") { - const { line: sl, column: sc } = locConverter.get( - string[0] - ); - const { line: el, column: ec } = locConverter.get( - string[1] - ); - - state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`, - { - start: { line: sl, column: sc }, - end: { line: el, column: ec } - } - ) - ); - } else if (options.webpackIgnore) { - continue; - } - } - const dep = new CssUrlDependency( - value, - [string[0], string[1]], - "url" - ); - const { line: sl, column: sc } = locConverter.get(string[0]); - const { line: el, column: ec } = locConverter.get(string[1]); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - module.addCodeGenerationDependency(dep); - } - // Can contain `url()` inside, so let's return end to allow parse them - return end; - } else if (isLocalMode()) { - // Don't rename animation name when we have `var()` function - if (inAnimationProperty && balanced.length === 1) { - lastIdentifier = undefined; - } - - if (name === "var") { - const customIdent = walkCssTokens.eatIdentSequence(input, end); - if (!customIdent) return end; - let name = input.slice(customIdent[0], customIdent[1]); - // A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo. - // The production corresponds to this: - // it’s defined as any (a valid identifier that starts with two dashes), - // except -- itself, which is reserved for future use by CSS. - if (!name.startsWith("--") || name.length < 3) return end; - name = unescapeIdentifier( - input.slice(customIdent[0] + 2, customIdent[1]) - ); - const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments( - input, - customIdent[1] - ); - if ( - input.charCodeAt(afterCustomIdent) === CC_LOWER_F || - input.charCodeAt(afterCustomIdent) === CC_UPPER_F - ) { - const fromWord = walkCssTokens.eatIdentSequence( - input, - afterCustomIdent - ); - if ( - !fromWord || - input.slice(fromWord[0], fromWord[1]).toLowerCase() !== - "from" - ) { - return end; - } - const from = walkCssTokens.eatIdentSequenceOrString( - input, - walkCssTokens.eatWhitespaceAndComments(input, fromWord[1]) - ); - if (!from) { - return end; - } - const path = input.slice(from[0], from[1]); - if (from[2] === true && path === "global") { - const dep = new ConstDependency("", [ - customIdent[1], - from[1] - ]); - module.addPresentationalDependency(dep); - return end; - } else if (from[2] === false) { - const dep = new CssIcssImportDependency( - path.slice(1, -1), - name, - [customIdent[0], from[1] - 1] - ); - const { line: sl, column: sc } = locConverter.get( - customIdent[0] - ); - const { line: el, column: ec } = locConverter.get( - from[1] - 1 - ); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - } - } else { - const { line: sl, column: sc } = locConverter.get( - customIdent[0] - ); - const { line: el, column: ec } = locConverter.get( - customIdent[1] - ); - const dep = new CssSelfLocalIdentifierDependency( - name, - [customIdent[0], customIdent[1]], - "--", - declaredCssVariables - ); - dep.setLoc(sl, sc, el, ec); - module.addDependency(dep); - return end; - } - } - } - } - } - - return end; - }, - leftParenthesis: (input, start, end) => { - balanced.push(["(", start, end]); - - return end; - }, - rightParenthesis: (input, start, end) => { - const popped = balanced.pop(); - - if ( - isModules && - popped && - (popped[0] === ":local" || popped[0] === ":global") - ) { - modeData = balanced[balanced.length - 1] - ? /** @type {"local" | "global"} */ - (balanced[balanced.length - 1][0]) - : undefined; - const dep = new ConstDependency("", [start, end]); - module.addPresentationalDependency(dep); - } - - return end; - }, - comma: (input, start, end) => { - if (isModules) { - // Reset stack for `:global .class :local .class-other` selector after - modeData = undefined; - - if (scope === CSS_MODE_IN_BLOCK && isLocalMode()) { - processDeclarationValueDone(input); - } - } - - lastTokenEndForComments = start; - - return end; - } - }); - - /** @type {BuildInfo} */ - (module.buildInfo).strict = true; - /** @type {BuildMeta} */ - (module.buildMeta).exportsType = this.namedExports - ? "namespace" - : "default"; - - if (!this.namedExports) { - /** @type {BuildMeta} */ - (module.buildMeta).defaultObject = "redirect"; - } - - module.addDependency(new StaticExportsDependency([], true)); - return state; - } - - /** - * @param {Range} range range - * @returns {Comment[]} comments in the range - */ - getComments(range) { - if (!this.comments) return []; - const [rangeStart, rangeEnd] = range; - /** - * @param {Comment} comment comment - * @param {number} needle needle - * @returns {number} compared - */ - const compare = (comment, needle) => - /** @type {Range} */ (comment.range)[0] - needle; - const comments = /** @type {Comment[]} */ (this.comments); - let idx = binarySearchBounds.ge(comments, rangeStart, compare); - /** @type {Comment[]} */ - const commentsInRange = []; - while ( - comments[idx] && - /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd - ) { - commentsInRange.push(comments[idx]); - idx++; - } - - return commentsInRange; - } - - /** - * @param {Range} range range of the comment - * @returns {{ options: Record | null, errors: (Error & { comment: Comment })[] | null }} result - */ - parseCommentOptions(range) { - const comments = this.getComments(range); - if (comments.length === 0) { - return EMPTY_COMMENT_OPTIONS; - } - /** @type {Record } */ - const options = {}; - /** @type {(Error & { comment: Comment })[]} */ - const errors = []; - for (const comment of comments) { - const { value } = comment; - if (value && webpackCommentRegExp.test(value)) { - // try compile only if webpack options comment is present - try { - for (let [key, val] of Object.entries( - vm.runInContext( - `(function(){return {${value}};})()`, - this.magicCommentContext - ) - )) { - if (typeof val === "object" && val !== null) { - val = - val.constructor.name === "RegExp" - ? new RegExp(val) - : JSON.parse(JSON.stringify(val)); - } - options[key] = val; - } - } catch (err) { - const newErr = new Error(String(/** @type {Error} */ (err).message)); - newErr.stack = String(/** @type {Error} */ (err).stack); - Object.assign(newErr, { comment }); - errors.push(/** @type (Error & { comment: Comment }) */ (newErr)); - } - } - } - return { options, errors }; - } -} - -module.exports = CssParser; -module.exports.escapeIdentifier = escapeIdentifier; -module.exports.unescapeIdentifier = unescapeIdentifier; diff --git a/webpack-lib/lib/css/walkCssTokens.js b/webpack-lib/lib/css/walkCssTokens.js deleted file mode 100644 index abef4f01e71..00000000000 --- a/webpack-lib/lib/css/walkCssTokens.js +++ /dev/null @@ -1,1627 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @typedef {object} CssTokenCallbacks - * @property {(function(string, number, number, number, number): number)=} url - * @property {(function(string, number, number): number)=} comment - * @property {(function(string, number, number): number)=} string - * @property {(function(string, number, number): number)=} leftParenthesis - * @property {(function(string, number, number): number)=} rightParenthesis - * @property {(function(string, number, number): number)=} function - * @property {(function(string, number, number): number)=} colon - * @property {(function(string, number, number): number)=} atKeyword - * @property {(function(string, number, number): number)=} delim - * @property {(function(string, number, number): number)=} identifier - * @property {(function(string, number, number, boolean): number)=} hash - * @property {(function(string, number, number): number)=} leftCurlyBracket - * @property {(function(string, number, number): number)=} rightCurlyBracket - * @property {(function(string, number, number): number)=} semicolon - * @property {(function(string, number, number): number)=} comma - * @property {(function(): boolean)=} needTerminate - */ - -/** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */ - -// spec: https://drafts.csswg.org/css-syntax/ - -const CC_LINE_FEED = "\n".charCodeAt(0); -const CC_CARRIAGE_RETURN = "\r".charCodeAt(0); -const CC_FORM_FEED = "\f".charCodeAt(0); - -const CC_TAB = "\t".charCodeAt(0); -const CC_SPACE = " ".charCodeAt(0); - -const CC_SOLIDUS = "/".charCodeAt(0); -const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0); -const CC_ASTERISK = "*".charCodeAt(0); - -const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); -const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); -const CC_LEFT_CURLY = "{".charCodeAt(0); -const CC_RIGHT_CURLY = "}".charCodeAt(0); -const CC_LEFT_SQUARE = "[".charCodeAt(0); -const CC_RIGHT_SQUARE = "]".charCodeAt(0); - -const CC_QUOTATION_MARK = '"'.charCodeAt(0); -const CC_APOSTROPHE = "'".charCodeAt(0); - -const CC_FULL_STOP = ".".charCodeAt(0); -const CC_COLON = ":".charCodeAt(0); -const CC_SEMICOLON = ";".charCodeAt(0); -const CC_COMMA = ",".charCodeAt(0); -const CC_PERCENTAGE = "%".charCodeAt(0); -const CC_AT_SIGN = "@".charCodeAt(0); - -const CC_LOW_LINE = "_".charCodeAt(0); -const CC_LOWER_A = "a".charCodeAt(0); -const CC_LOWER_F = "f".charCodeAt(0); -const CC_LOWER_E = "e".charCodeAt(0); -const CC_LOWER_U = "u".charCodeAt(0); -const CC_LOWER_Z = "z".charCodeAt(0); -const CC_UPPER_A = "A".charCodeAt(0); -const CC_UPPER_F = "F".charCodeAt(0); -const CC_UPPER_E = "E".charCodeAt(0); -const CC_UPPER_U = "E".charCodeAt(0); -const CC_UPPER_Z = "Z".charCodeAt(0); -const CC_0 = "0".charCodeAt(0); -const CC_9 = "9".charCodeAt(0); - -const CC_NUMBER_SIGN = "#".charCodeAt(0); -const CC_PLUS_SIGN = "+".charCodeAt(0); -const CC_HYPHEN_MINUS = "-".charCodeAt(0); - -const CC_LESS_THAN_SIGN = "<".charCodeAt(0); -const CC_GREATER_THAN_SIGN = ">".charCodeAt(0); - -/** @type {CharHandler} */ -const consumeSpace = (input, pos, _callbacks) => { - // Consume as much whitespace as possible. - while (_isWhiteSpace(input.charCodeAt(pos))) { - pos++; - } - - // Return a . - return pos; -}; - -// U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, -// as they are converted to U+000A LINE FEED during preprocessing. -// -// Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE FEED (LF) in input by a single U+000A LINE FEED (LF) code point. - -/** - * @param {number} cc char code - * @returns {boolean} true, if cc is a newline - */ -const _isNewline = cc => - cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; - -/** - * @param {number} cc char code - * @param {string} input input - * @param {number} pos position - * @returns {number} position - */ -const consumeExtraNewline = (cc, input, pos) => { - if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) { - pos++; - } - - return pos; -}; - -/** - * @param {number} cc char code - * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE) - */ -const _isSpace = cc => cc === CC_TAB || cc === CC_SPACE; - -/** - * @param {number} cc char code - * @returns {boolean} true, if cc is a whitespace - */ -const _isWhiteSpace = cc => _isNewline(cc) || _isSpace(cc); - -/** - * ident-start code point - * - * A letter, a non-ASCII code point, or U+005F LOW LINE (_). - * @param {number} cc char code - * @returns {boolean} true, if cc is a start code point of an identifier - */ -const isIdentStartCodePoint = cc => - (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || - (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) || - cc === CC_LOW_LINE || - cc >= 0x80; - -/** @type {CharHandler} */ -const consumeDelimToken = (input, pos, _callbacks) => - // Return a with its value set to the current input code point. - pos; - -/** @type {CharHandler} */ -const consumeComments = (input, pos, callbacks) => { - // This section describes how to consume comments from a stream of code points. It returns nothing. - // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*), - // consume them and all following code points up to and including the first U+002A ASTERISK (*) - // followed by a U+002F SOLIDUS (/), or up to an EOF code point. - // Return to the start of this step. - while ( - input.charCodeAt(pos) === CC_SOLIDUS && - input.charCodeAt(pos + 1) === CC_ASTERISK - ) { - const start = pos; - pos += 2; - - for (;;) { - if (pos === input.length) { - // If the preceding paragraph ended by consuming an EOF code point, this is a parse error. - return pos; - } - - if ( - input.charCodeAt(pos) === CC_ASTERISK && - input.charCodeAt(pos + 1) === CC_SOLIDUS - ) { - pos += 2; - - if (callbacks.comment) { - pos = callbacks.comment(input, start, pos); - } - - break; - } - - pos++; - } - } - - return pos; -}; - -/** - * @param {number} cc char code - * @returns {boolean} true, if cc is a hex digit - */ -const _isHexDigit = cc => - _isDigit(cc) || - (cc >= CC_UPPER_A && cc <= CC_UPPER_F) || - (cc >= CC_LOWER_A && cc <= CC_LOWER_F); - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position - */ -const _consumeAnEscapedCodePoint = (input, pos) => { - // This section describes how to consume an escaped code point. - // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and that the next input code point has already been verified to be part of a valid escape. - // It will return a code point. - - // Consume the next input code point. - const cc = input.charCodeAt(pos); - pos++; - - // EOF - // This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (�). - if (pos === input.length) { - return pos; - } - - // hex digit - // Consume as many hex digits as possible, but no more than 5. - // Note that this means 1-6 hex digits have been consumed in total. - // If the next input code point is whitespace, consume it as well. - // Interpret the hex digits as a hexadecimal number. - // If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point, return U+FFFD REPLACEMENT CHARACTER (�). - // Otherwise, return the code point with that value. - if (_isHexDigit(cc)) { - for (let i = 0; i < 5; i++) { - if (_isHexDigit(input.charCodeAt(pos))) { - pos++; - } - } - - const cc = input.charCodeAt(pos); - - if (_isWhiteSpace(cc)) { - pos++; - pos = consumeExtraNewline(cc, input, pos); - } - - return pos; - } - - // anything else - // Return the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeAStringToken = (input, pos, callbacks) => { - // This section describes how to consume a string token from a stream of code points. - // It returns either a or . - // - // This algorithm may be called with an ending code point, which denotes the code point that ends the string. - // If an ending code point is not specified, the current input code point is used. - const start = pos - 1; - const endingCodePoint = input.charCodeAt(pos - 1); - - // Initially create a with its value set to the empty string. - - // Repeatedly consume the next input code point from the stream: - for (;;) { - // EOF - // This is a parse error. Return the . - if (pos === input.length) { - if (callbacks.string !== undefined) { - return callbacks.string(input, start, pos); - } - - return pos; - } - - const cc = input.charCodeAt(pos); - pos++; - - // ending code point - // Return the . - if (cc === endingCodePoint) { - if (callbacks.string !== undefined) { - return callbacks.string(input, start, pos); - } - - return pos; - } - // newline - // This is a parse error. - // Reconsume the current input code point, create a , and return it. - else if (_isNewline(cc)) { - pos--; - // bad string - return pos; - } - // U+005C REVERSE SOLIDUS (\) - else if (cc === CC_REVERSE_SOLIDUS) { - // If the next input code point is EOF, do nothing. - if (pos === input.length) { - return pos; - } - // Otherwise, if the next input code point is a newline, consume it. - else if (_isNewline(input.charCodeAt(pos))) { - const cc = input.charCodeAt(pos); - pos++; - pos = consumeExtraNewline(cc, input, pos); - } - // Otherwise, (the stream starts with a valid escape) consume an escaped code point and append the returned code point to the ’s value. - else if (_ifTwoCodePointsAreValidEscape(input, pos)) { - pos = _consumeAnEscapedCodePoint(input, pos); - } - } - // anything else - // Append the current input code point to the ’s value. - else { - // Append - } - } -}; - -/** - * @param {number} cc char code - * @param {number} q char code - * @returns {boolean} is non-ASCII code point - */ -const isNonASCIICodePoint = (cc, q) => - // Simplify - cc > 0x80; -/** - * @param {number} cc char code - * @returns {boolean} is letter - */ -const isLetter = cc => - (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || - (cc >= CC_UPPER_A && cc <= CC_UPPER_Z); - -/** - * @param {number} cc char code - * @param {number} q char code - * @returns {boolean} is identifier start code - */ -const _isIdentStartCodePoint = (cc, q) => - isLetter(cc) || isNonASCIICodePoint(cc, q) || cc === CC_LOW_LINE; - -/** - * @param {number} cc char code - * @param {number} q char code - * @returns {boolean} is identifier code - */ -const _isIdentCodePoint = (cc, q) => - _isIdentStartCodePoint(cc, q) || _isDigit(cc) || cc === CC_HYPHEN_MINUS; -/** - * @param {number} cc char code - * @returns {boolean} is digit - */ -const _isDigit = cc => cc >= CC_0 && cc <= CC_9; - -/** - * @param {string} input input - * @param {number} pos position - * @param {number=} f first code point - * @param {number=} s second code point - * @returns {boolean} true if two code points are a valid escape - */ -const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => { - // This section describes how to check if two code points are a valid escape. - // The algorithm described here can be called explicitly with two code points, or can be called with the input stream itself. - // In the latter case, the two code points in question are the current input code point and the next input code point, in that order. - - // Note: This algorithm will not consume any additional code point. - const first = f || input.charCodeAt(pos - 1); - const second = s || input.charCodeAt(pos); - - // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. - if (first !== CC_REVERSE_SOLIDUS) return false; - // Otherwise, if the second code point is a newline, return false. - if (_isNewline(second)) return false; - // Otherwise, return true. - return true; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @param {number=} f first - * @param {number=} s second - * @param {number=} t third - * @returns {boolean} true, if input at pos starts an identifier - */ -const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => { - // This section describes how to check if three code points would start an ident sequence. - // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself. - // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order. - - // Note: This algorithm will not consume any additional code points. - - const first = f || input.charCodeAt(pos - 1); - const second = s || input.charCodeAt(pos); - const third = t || input.charCodeAt(pos + 1); - - // Look at the first code point: - - // U+002D HYPHEN-MINUS - if (first === CC_HYPHEN_MINUS) { - // If the second code point is an ident-start code point or a U+002D HYPHEN-MINUS - // or a U+002D HYPHEN-MINUS, or the second and third code points are a valid escape, return true. - if ( - _isIdentStartCodePoint(second, pos) || - second === CC_HYPHEN_MINUS || - _ifTwoCodePointsAreValidEscape(input, pos, second, third) - ) { - return true; - } - return false; - } - // ident-start code point - else if (_isIdentStartCodePoint(first, pos - 1)) { - return true; - } - // U+005C REVERSE SOLIDUS (\) - // If the first and second code points are a valid escape, return true. Otherwise, return false. - else if (first === CC_REVERSE_SOLIDUS) { - if (_ifTwoCodePointsAreValidEscape(input, pos, first, second)) { - return true; - } - - return false; - } - // anything else - // Return false. - return false; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @param {number=} f first - * @param {number=} s second - * @param {number=} t third - * @returns {boolean} true, if input at pos starts an identifier - */ -const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => { - // This section describes how to check if three code points would start a number. - // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself. - // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order. - - // Note: This algorithm will not consume any additional code points. - - const first = f || input.charCodeAt(pos - 1); - const second = s || input.charCodeAt(pos); - const third = t || input.charCodeAt(pos); - - // Look at the first code point: - - // U+002B PLUS SIGN (+) - // U+002D HYPHEN-MINUS (-) - // - // If the second code point is a digit, return true. - // Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code point is a digit, return true. - // Otherwise, return false. - if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) { - if (_isDigit(second)) { - return true; - } else if (second === CC_FULL_STOP && _isDigit(third)) { - return true; - } - - return false; - } - // U+002E FULL STOP (.) - // If the second code point is a digit, return true. Otherwise, return false. - else if (first === CC_FULL_STOP) { - if (_isDigit(second)) { - return true; - } - - return false; - } - // digit - // Return true. - else if (_isDigit(first)) { - return true; - } - - // anything else - // Return false. - return false; -}; - -/** @type {CharHandler} */ -const consumeNumberSign = (input, pos, callbacks) => { - // If the next input code point is an ident code point or the next two input code points are a valid escape, then: - // - Create a . - // - If the next 3 input code points would start an ident sequence, set the ’s type flag to "id". - // - Consume an ident sequence, and set the ’s value to the returned string. - // - Return the . - const start = pos - 1; - const first = input.charCodeAt(pos); - const second = input.charCodeAt(pos + 1); - - if ( - _isIdentCodePoint(first, pos - 1) || - _ifTwoCodePointsAreValidEscape(input, pos, first, second) - ) { - const third = input.charCodeAt(pos + 2); - let isId = false; - - if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - first, - second, - third - ) - ) { - isId = true; - } - - pos = _consumeAnIdentSequence(input, pos, callbacks); - - if (callbacks.hash !== undefined) { - return callbacks.hash(input, start, pos, isId); - } - - return pos; - } - - // Otherwise, return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeHyphenMinus = (input, pos, callbacks) => { - // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. - if (_ifThreeCodePointsWouldStartANumber(input, pos)) { - pos--; - return consumeANumericToken(input, pos, callbacks); - } - // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a . - else if ( - input.charCodeAt(pos) === CC_HYPHEN_MINUS && - input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN - ) { - return pos + 2; - } - // Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it. - else if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) { - pos--; - return consumeAnIdentLikeToken(input, pos, callbacks); - } - - // Otherwise, return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeFullStop = (input, pos, callbacks) => { - const start = pos - 1; - - // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. - if (_ifThreeCodePointsWouldStartANumber(input, pos)) { - pos--; - return consumeANumericToken(input, pos, callbacks); - } - - // Otherwise, return a with its value set to the current input code point. - if (callbacks.delim !== undefined) { - return callbacks.delim(input, start, pos); - } - - return pos; -}; - -/** @type {CharHandler} */ -const consumePlusSign = (input, pos, callbacks) => { - // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. - if (_ifThreeCodePointsWouldStartANumber(input, pos)) { - pos--; - return consumeANumericToken(input, pos, callbacks); - } - - // Otherwise, return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const _consumeANumber = (input, pos) => { - // This section describes how to consume a number from a stream of code points. - // It returns a numeric value, and a type which is either "integer" or "number". - - // Execute the following steps in order: - // Initially set type to "integer". Let repr be the empty string. - - // If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume it and append it to repr. - if ( - input.charCodeAt(pos) === CC_HYPHEN_MINUS || - input.charCodeAt(pos) === CC_PLUS_SIGN - ) { - pos++; - } - - // While the next input code point is a digit, consume it and append it to repr. - while (_isDigit(input.charCodeAt(pos))) { - pos++; - } - - // If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: - // 1. Consume the next input code point and append it to number part. - // 2. While the next input code point is a digit, consume it and append it to number part. - // 3. Set type to "number". - if ( - input.charCodeAt(pos) === CC_FULL_STOP && - _isDigit(input.charCodeAt(pos + 1)) - ) { - pos++; - - while (_isDigit(input.charCodeAt(pos))) { - pos++; - } - } - - // If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+), followed by a digit, then: - // 1. Consume the next input code point. - // 2. If the next input code point is "+" or "-", consume it and append it to exponent part. - // 3. While the next input code point is a digit, consume it and append it to exponent part. - // 4. Set type to "number". - if ( - (input.charCodeAt(pos) === CC_LOWER_E || - input.charCodeAt(pos) === CC_UPPER_E) && - (((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS || - input.charCodeAt(pos + 1) === CC_PLUS_SIGN) && - _isDigit(input.charCodeAt(pos + 2))) || - _isDigit(input.charCodeAt(pos + 1))) - ) { - pos++; - - if ( - input.charCodeAt(pos) === CC_PLUS_SIGN || - input.charCodeAt(pos) === CC_HYPHEN_MINUS - ) { - pos++; - } - - while (_isDigit(input.charCodeAt(pos))) { - pos++; - } - } - - // Let value be the result of interpreting number part as a base-10 number. - - // If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the power of the result, multiply it by value, and set value to that result. - - // Return value and type. - return pos; -}; - -/** @type {CharHandler} */ -const consumeANumericToken = (input, pos, callbacks) => { - // This section describes how to consume a numeric token from a stream of code points. - // It returns either a , , or . - - // Consume a number and let number be the result. - pos = _consumeANumber(input, pos, callbacks); - - // If the next 3 input code points would start an ident sequence, then: - // - // - Create a with the same value and type flag as number, and a unit set initially to the empty string. - // - Consume an ident sequence. Set the ’s unit to the returned value. - // - Return the . - - const first = input.charCodeAt(pos); - const second = input.charCodeAt(pos + 1); - const third = input.charCodeAt(pos + 2); - - if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - first, - second, - third - ) - ) { - return _consumeAnIdentSequence(input, pos, callbacks); - } - // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. - // Create a with the same value as number, and return it. - else if (first === CC_PERCENTAGE) { - return pos + 1; - } - - // Otherwise, create a with the same value and type flag as number, and return it. - return pos; -}; - -/** @type {CharHandler} */ -const consumeColon = (input, pos, callbacks) => { - // Return a . - if (callbacks.colon !== undefined) { - return callbacks.colon(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeLeftParenthesis = (input, pos, callbacks) => { - // Return a <(-token>. - if (callbacks.leftParenthesis !== undefined) { - return callbacks.leftParenthesis(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeRightParenthesis = (input, pos, callbacks) => { - // Return a <)-token>. - if (callbacks.rightParenthesis !== undefined) { - return callbacks.rightParenthesis(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeLeftSquareBracket = (input, pos, callbacks) => - // Return a <]-token>. - pos; - -/** @type {CharHandler} */ -const consumeRightSquareBracket = (input, pos, callbacks) => - // Return a <]-token>. - pos; - -/** @type {CharHandler} */ -const consumeLeftCurlyBracket = (input, pos, callbacks) => { - // Return a <{-token>. - if (callbacks.leftCurlyBracket !== undefined) { - return callbacks.leftCurlyBracket(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeRightCurlyBracket = (input, pos, callbacks) => { - // Return a <}-token>. - if (callbacks.rightCurlyBracket !== undefined) { - return callbacks.rightCurlyBracket(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeSemicolon = (input, pos, callbacks) => { - // Return a . - if (callbacks.semicolon !== undefined) { - return callbacks.semicolon(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const consumeComma = (input, pos, callbacks) => { - // Return a . - if (callbacks.comma !== undefined) { - return callbacks.comma(input, pos - 1, pos); - } - return pos; -}; - -/** @type {CharHandler} */ -const _consumeAnIdentSequence = (input, pos) => { - // This section describes how to consume an ident sequence from a stream of code points. - // It returns a string containing the largest name that can be formed from adjacent code points in the stream, starting from the first. - - // Note: This algorithm does not do the verification of the first few code points that are necessary to ensure the returned code points would constitute an . - // If that is the intended use, ensure that the stream starts with an ident sequence before calling this algorithm. - - // Let result initially be an empty string. - - // Repeatedly consume the next input code point from the stream: - for (;;) { - const cc = input.charCodeAt(pos); - pos++; - - // ident code point - // Append the code point to result. - if (_isIdentCodePoint(cc, pos - 1)) { - // Nothing - } - // the stream starts with a valid escape - // Consume an escaped code point. Append the returned code point to result. - else if (_ifTwoCodePointsAreValidEscape(input, pos)) { - pos = _consumeAnEscapedCodePoint(input, pos); - } - // anything else - // Reconsume the current input code point. Return result. - else { - return pos - 1; - } - } -}; - -/** - * @param {number} cc char code - * @returns {boolean} true, when cc is the non-printable code point, otherwise false - */ -const _isNonPrintableCodePoint = cc => - (cc >= 0x00 && cc <= 0x08) || - cc === 0x0b || - (cc >= 0x0e && cc <= 0x1f) || - cc === 0x7f; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position - */ -const consumeTheRemnantsOfABadUrl = (input, pos) => { - // This section describes how to consume the remnants of a bad url from a stream of code points, - // "cleaning up" after the tokenizer realizes that it’s in the middle of a rather than a . - // It returns nothing; its sole use is to consume enough of the input stream to reach a recovery point where normal tokenizing can resume. - - // Repeatedly consume the next input code point from the stream: - for (;;) { - // EOF - // Return. - if (pos === input.length) { - return pos; - } - - const cc = input.charCodeAt(pos); - pos++; - - // U+0029 RIGHT PARENTHESIS ()) - // Return. - if (cc === CC_RIGHT_PARENTHESIS) { - return pos; - } - // the input stream starts with a valid escape - // Consume an escaped code point. - // This allows an escaped right parenthesis ("\)") to be encountered without ending the . - // This is otherwise identical to the "anything else" clause. - else if (_ifTwoCodePointsAreValidEscape(input, pos)) { - pos = _consumeAnEscapedCodePoint(input, pos); - } - // anything else - // Do nothing. - else { - // Do nothing. - } - } -}; - -/** - * @param {string} input input - * @param {number} pos position - * @param {number} fnStart start - * @param {CssTokenCallbacks} callbacks callbacks - * @returns {pos} pos - */ -const consumeAUrlToken = (input, pos, fnStart, callbacks) => { - // This section describes how to consume a url token from a stream of code points. - // It returns either a or a . - - // Note: This algorithm assumes that the initial "url(" has already been consumed. - // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo). - // A quoted value, like url("foo"), is parsed as a . - // Consume an ident-like token automatically handles this distinction; this algorithm shouldn’t be called directly otherwise. - - // Initially create a with its value set to the empty string. - - // Consume as much whitespace as possible. - while (_isWhiteSpace(input.charCodeAt(pos))) { - pos++; - } - - const contentStart = pos; - - // Repeatedly consume the next input code point from the stream: - for (;;) { - // EOF - // This is a parse error. Return the . - if (pos === input.length) { - if (callbacks.url !== undefined) { - return callbacks.url(input, fnStart, pos, contentStart, pos - 1); - } - - return pos; - } - - const cc = input.charCodeAt(pos); - pos++; - - // U+0029 RIGHT PARENTHESIS ()) - // Return the . - if (cc === CC_RIGHT_PARENTHESIS) { - if (callbacks.url !== undefined) { - return callbacks.url(input, fnStart, pos, contentStart, pos - 1); - } - - return pos; - } - // whitespace - // Consume as much whitespace as possible. - // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, consume it and return the - // (if EOF was encountered, this is a parse error); otherwise, consume the remnants of a bad url, create a , and return it. - else if (_isWhiteSpace(cc)) { - const end = pos - 1; - - while (_isWhiteSpace(input.charCodeAt(pos))) { - pos++; - } - - if (pos === input.length) { - if (callbacks.url !== undefined) { - return callbacks.url(input, fnStart, pos, contentStart, end); - } - - return pos; - } - - if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) { - pos++; - - if (callbacks.url !== undefined) { - return callbacks.url(input, fnStart, pos, contentStart, end); - } - - return pos; - } - - // Don't handle bad urls - return consumeTheRemnantsOfABadUrl(input, pos); - } - // U+0022 QUOTATION MARK (") - // U+0027 APOSTROPHE (') - // U+0028 LEFT PARENTHESIS (() - // non-printable code point - // This is a parse error. Consume the remnants of a bad url, create a , and return it. - else if ( - cc === CC_QUOTATION_MARK || - cc === CC_APOSTROPHE || - cc === CC_LEFT_PARENTHESIS || - _isNonPrintableCodePoint(cc) - ) { - // Don't handle bad urls - return consumeTheRemnantsOfABadUrl(input, pos); - } - // // U+005C REVERSE SOLIDUS (\) - // // If the stream starts with a valid escape, consume an escaped code point and append the returned code point to the ’s value. - // // Otherwise, this is a parse error. Consume the remnants of a bad url, create a , and return it. - else if (cc === CC_REVERSE_SOLIDUS) { - if (_ifTwoCodePointsAreValidEscape(input, pos)) { - pos = _consumeAnEscapedCodePoint(input, pos); - } else { - // Don't handle bad urls - return consumeTheRemnantsOfABadUrl(input, pos); - } - } - // anything else - // Append the current input code point to the ’s value. - else { - // Nothing - } - } -}; - -/** @type {CharHandler} */ -const consumeAnIdentLikeToken = (input, pos, callbacks) => { - const start = pos; - // This section describes how to consume an ident-like token from a stream of code points. - // It returns an , , , or . - pos = _consumeAnIdentSequence(input, pos, callbacks); - - // If string’s value is an ASCII case-insensitive match for "url", and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. - // While the next two input code points are whitespace, consume the next input code point. - // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), then create a with its value set to string and return it. - // Otherwise, consume a url token, and return it. - if ( - input.slice(start, pos).toLowerCase() === "url" && - input.charCodeAt(pos) === CC_LEFT_PARENTHESIS - ) { - pos++; - const end = pos; - - while ( - _isWhiteSpace(input.charCodeAt(pos)) && - _isWhiteSpace(input.charCodeAt(pos + 1)) - ) { - pos++; - } - - if ( - input.charCodeAt(pos) === CC_QUOTATION_MARK || - input.charCodeAt(pos) === CC_APOSTROPHE || - (_isWhiteSpace(input.charCodeAt(pos)) && - (input.charCodeAt(pos + 1) === CC_QUOTATION_MARK || - input.charCodeAt(pos + 1) === CC_APOSTROPHE)) - ) { - if (callbacks.function !== undefined) { - return callbacks.function(input, start, end); - } - - return pos; - } - - return consumeAUrlToken(input, pos, start, callbacks); - } - - // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. - // Create a with its value set to string and return it. - if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) { - pos++; - - if (callbacks.function !== undefined) { - return callbacks.function(input, start, pos); - } - - return pos; - } - - // Otherwise, create an with its value set to string and return it. - if (callbacks.identifier !== undefined) { - return callbacks.identifier(input, start, pos); - } - - return pos; -}; - -/** @type {CharHandler} */ -const consumeLessThan = (input, pos, _callbacks) => { - // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a . - if (input.slice(pos, pos + 3) === "!--") { - return pos + 3; - } - - // Otherwise, return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeCommercialAt = (input, pos, callbacks) => { - const start = pos - 1; - - // If the next 3 input code points would start an ident sequence, consume an ident sequence, create an with its value set to the returned value, and return it. - if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - input.charCodeAt(pos), - input.charCodeAt(pos + 1), - input.charCodeAt(pos + 2) - ) - ) { - pos = _consumeAnIdentSequence(input, pos, callbacks); - - if (callbacks.atKeyword !== undefined) { - pos = callbacks.atKeyword(input, start, pos); - } - - return pos; - } - - // Otherwise, return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeReverseSolidus = (input, pos, callbacks) => { - // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it. - if (_ifTwoCodePointsAreValidEscape(input, pos)) { - pos--; - return consumeAnIdentLikeToken(input, pos, callbacks); - } - - // Otherwise, this is a parse error. Return a with its value set to the current input code point. - return pos; -}; - -/** @type {CharHandler} */ -const consumeAToken = (input, pos, callbacks) => { - const cc = input.charCodeAt(pos - 1); - - // https://drafts.csswg.org/css-syntax/#consume-token - switch (cc) { - // whitespace - case CC_LINE_FEED: - case CC_CARRIAGE_RETURN: - case CC_FORM_FEED: - case CC_TAB: - case CC_SPACE: - return consumeSpace(input, pos, callbacks); - // U+0022 QUOTATION MARK (") - case CC_QUOTATION_MARK: - return consumeAStringToken(input, pos, callbacks); - // U+0023 NUMBER SIGN (#) - case CC_NUMBER_SIGN: - return consumeNumberSign(input, pos, callbacks); - // U+0027 APOSTROPHE (') - case CC_APOSTROPHE: - return consumeAStringToken(input, pos, callbacks); - // U+0028 LEFT PARENTHESIS (() - case CC_LEFT_PARENTHESIS: - return consumeLeftParenthesis(input, pos, callbacks); - // U+0029 RIGHT PARENTHESIS ()) - case CC_RIGHT_PARENTHESIS: - return consumeRightParenthesis(input, pos, callbacks); - // U+002B PLUS SIGN (+) - case CC_PLUS_SIGN: - return consumePlusSign(input, pos, callbacks); - // U+002C COMMA (,) - case CC_COMMA: - return consumeComma(input, pos, callbacks); - // U+002D HYPHEN-MINUS (-) - case CC_HYPHEN_MINUS: - return consumeHyphenMinus(input, pos, callbacks); - // U+002E FULL STOP (.) - case CC_FULL_STOP: - return consumeFullStop(input, pos, callbacks); - // U+003A COLON (:) - case CC_COLON: - return consumeColon(input, pos, callbacks); - // U+003B SEMICOLON (;) - case CC_SEMICOLON: - return consumeSemicolon(input, pos, callbacks); - // U+003C LESS-THAN SIGN (<) - case CC_LESS_THAN_SIGN: - return consumeLessThan(input, pos, callbacks); - // U+0040 COMMERCIAL AT (@) - case CC_AT_SIGN: - return consumeCommercialAt(input, pos, callbacks); - // U+005B LEFT SQUARE BRACKET ([) - case CC_LEFT_SQUARE: - return consumeLeftSquareBracket(input, pos, callbacks); - // U+005C REVERSE SOLIDUS (\) - case CC_REVERSE_SOLIDUS: - return consumeReverseSolidus(input, pos, callbacks); - // U+005D RIGHT SQUARE BRACKET (]) - case CC_RIGHT_SQUARE: - return consumeRightSquareBracket(input, pos, callbacks); - // U+007B LEFT CURLY BRACKET ({) - case CC_LEFT_CURLY: - return consumeLeftCurlyBracket(input, pos, callbacks); - // U+007D RIGHT CURLY BRACKET (}) - case CC_RIGHT_CURLY: - return consumeRightCurlyBracket(input, pos, callbacks); - default: - // digit - // Reconsume the current input code point, consume a numeric token, and return it. - if (_isDigit(cc)) { - pos--; - return consumeANumericToken(input, pos, callbacks); - } else if (cc === CC_LOWER_U || cc === CC_UPPER_U) { - // If unicode ranges allowed is true and the input stream would start a unicode-range, - // reconsume the current input code point, consume a unicode-range token, and return it. - // Skip now - // if (_ifThreeCodePointsWouldStartAUnicodeRange(input, pos)) { - // pos--; - // return consumeAUnicodeRangeToken(input, pos, callbacks); - // } - - // Otherwise, reconsume the current input code point, consume an ident-like token, and return it. - pos--; - return consumeAnIdentLikeToken(input, pos, callbacks); - } - // ident-start code point - // Reconsume the current input code point, consume an ident-like token, and return it. - else if (isIdentStartCodePoint(cc)) { - pos--; - return consumeAnIdentLikeToken(input, pos, callbacks); - } - - // EOF, but we don't have it - - // anything else - // Return a with its value set to the current input code point. - return consumeDelimToken(input, pos, callbacks); - } -}; - -/** - * @param {string} input input css - * @param {number=} pos pos - * @param {CssTokenCallbacks=} callbacks callbacks - * @returns {number} pos - */ -module.exports = (input, pos = 0, callbacks = {}) => { - // This section describes how to consume a token from a stream of code points. It will return a single token of any type. - while (pos < input.length) { - // Consume comments. - pos = consumeComments(input, pos, callbacks); - - // Consume the next input code point. - pos++; - pos = consumeAToken(input, pos, callbacks); - - if (callbacks.needTerminate && callbacks.needTerminate()) { - break; - } - } - - return pos; -}; - -module.exports.isIdentStartCodePoint = isIdentStartCodePoint; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position after comments - */ -module.exports.eatComments = (input, pos) => { - for (;;) { - const originalPos = pos; - pos = consumeComments(input, pos, {}); - if (originalPos === pos) { - break; - } - } - - return pos; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position after whitespace - */ -module.exports.eatWhitespace = (input, pos) => { - while (_isWhiteSpace(input.charCodeAt(pos))) { - pos++; - } - - return pos; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position after whitespace and comments - */ -module.exports.eatWhitespaceAndComments = (input, pos) => { - for (;;) { - const originalPos = pos; - pos = consumeComments(input, pos, {}); - while (_isWhiteSpace(input.charCodeAt(pos))) { - pos++; - } - if (originalPos === pos) { - break; - } - } - - return pos; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position after whitespace and comments - */ -module.exports.eatComments = (input, pos) => { - for (;;) { - const originalPos = pos; - pos = consumeComments(input, pos, {}); - if (originalPos === pos) { - break; - } - } - - return pos; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {number} position after whitespace - */ -module.exports.eatWhiteLine = (input, pos) => { - for (;;) { - const cc = input.charCodeAt(pos); - if (_isSpace(cc)) { - pos++; - continue; - } - if (_isNewline(cc)) pos++; - pos = consumeExtraNewline(cc, input, pos); - break; - } - - return pos; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {[number, number] | undefined} positions of ident sequence - */ -module.exports.skipCommentsAndEatIdentSequence = (input, pos) => { - pos = module.exports.eatComments(input, pos); - - const start = pos; - - if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - input.charCodeAt(pos), - input.charCodeAt(pos + 1), - input.charCodeAt(pos + 2) - ) - ) { - return [start, _consumeAnIdentSequence(input, pos, {})]; - } - - return undefined; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {[number, number] | undefined} positions of ident sequence - */ -module.exports.eatString = (input, pos) => { - pos = module.exports.eatWhitespaceAndComments(input, pos); - - const start = pos; - - if ( - input.charCodeAt(pos) === CC_QUOTATION_MARK || - input.charCodeAt(pos) === CC_APOSTROPHE - ) { - return [start, consumeAStringToken(input, pos + 1, {})]; - } - - return undefined; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @param {CssTokenCallbacks} cbs callbacks - * @returns {[number, number][]} positions of ident sequence - */ -module.exports.eatImageSetStrings = (input, pos, cbs) => { - /** @type {[number, number][]} */ - const result = []; - - let isFirst = true; - let needStop = false; - // We already in `func(` token - let balanced = 1; - - /** @type {CssTokenCallbacks} */ - const callbacks = { - ...cbs, - string: (_input, start, end) => { - if (isFirst && balanced === 1) { - result.push([start, end]); - isFirst = false; - } - - return end; - }, - comma: (_input, _start, end) => { - if (balanced === 1) { - isFirst = true; - } - - return end; - }, - leftParenthesis: (input, start, end) => { - balanced++; - - return end; - }, - function: (_input, start, end) => { - balanced++; - - return end; - }, - rightParenthesis: (_input, _start, end) => { - balanced--; - - if (balanced === 0) { - needStop = true; - } - - return end; - } - }; - - while (pos < input.length) { - // Consume comments. - pos = consumeComments(input, pos, callbacks); - - // Consume the next input code point. - pos++; - pos = consumeAToken(input, pos, callbacks); - - if (needStop) { - break; - } - } - - return result; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @param {CssTokenCallbacks} cbs callbacks - * @returns {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} positions of top level tokens - */ -module.exports.eatImportTokens = (input, pos, cbs) => { - const result = - /** @type {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} */ - (new Array(4)); - - /** @type {0 | 1 | 2 | undefined} */ - let scope; - let needStop = false; - let balanced = 0; - - /** @type {CssTokenCallbacks} */ - const callbacks = { - ...cbs, - url: (_input, start, end, contentStart, contentEnd) => { - if ( - result[0] === undefined && - balanced === 0 && - result[1] === undefined && - result[2] === undefined && - result[3] === undefined - ) { - result[0] = [start, end, contentStart, contentEnd]; - scope = undefined; - } - - return end; - }, - string: (_input, start, end) => { - if ( - balanced === 0 && - result[0] === undefined && - result[1] === undefined && - result[2] === undefined && - result[3] === undefined - ) { - result[0] = [start, end, start + 1, end - 1]; - scope = undefined; - } else if (result[0] !== undefined && scope === 0) { - result[0][2] = start + 1; - result[0][3] = end - 1; - } - - return end; - }, - leftParenthesis: (_input, _start, end) => { - balanced++; - - return end; - }, - rightParenthesis: (_input, _start, end) => { - balanced--; - - if (balanced === 0 && scope !== undefined) { - /** @type {[number, number]} */ - (result[scope])[1] = end; - scope = undefined; - } - - return end; - }, - function: (input, start, end) => { - if (balanced === 0) { - const name = input - .slice(start, end - 1) - .replace(/\\/g, "") - .toLowerCase(); - - if ( - name === "url" && - result[0] === undefined && - result[1] === undefined && - result[2] === undefined && - result[3] === undefined - ) { - scope = 0; - result[scope] = [start, end + 1, end + 1, end + 1]; - } else if ( - name === "layer" && - result[1] === undefined && - result[2] === undefined - ) { - scope = 1; - result[scope] = [start, end]; - } else if (name === "supports" && result[2] === undefined) { - scope = 2; - result[scope] = [start, end]; - } else { - scope = undefined; - } - } - - balanced++; - - return end; - }, - identifier: (input, start, end) => { - if ( - balanced === 0 && - result[1] === undefined && - result[2] === undefined - ) { - const name = input.slice(start, end).replace(/\\/g, "").toLowerCase(); - - if (name === "layer") { - result[1] = [start, end]; - scope = undefined; - } - } - - return end; - }, - semicolon: (_input, start, end) => { - if (balanced === 0) { - needStop = true; - result[3] = [start, end]; - } - - return end; - } - }; - - while (pos < input.length) { - // Consume comments. - pos = consumeComments(input, pos, callbacks); - - // Consume the next input code point. - pos++; - pos = consumeAToken(input, pos, callbacks); - - if (needStop) { - break; - } - } - - return result; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {[number, number] | undefined} positions of ident sequence - */ -module.exports.eatIdentSequence = (input, pos) => { - pos = module.exports.eatWhitespaceAndComments(input, pos); - - const start = pos; - - if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - input.charCodeAt(pos), - input.charCodeAt(pos + 1), - input.charCodeAt(pos + 2) - ) - ) { - return [start, _consumeAnIdentSequence(input, pos, {})]; - } - - return undefined; -}; - -/** - * @param {string} input input - * @param {number} pos position - * @returns {[number, number, boolean] | undefined} positions of ident sequence or string - */ -module.exports.eatIdentSequenceOrString = (input, pos) => { - pos = module.exports.eatWhitespaceAndComments(input, pos); - - const start = pos; - - if ( - input.charCodeAt(pos) === CC_QUOTATION_MARK || - input.charCodeAt(pos) === CC_APOSTROPHE - ) { - return [start, consumeAStringToken(input, pos + 1, {}), false]; - } else if ( - _ifThreeCodePointsWouldStartAnIdentSequence( - input, - pos, - input.charCodeAt(pos), - input.charCodeAt(pos + 1), - input.charCodeAt(pos + 2) - ) - ) { - return [start, _consumeAnIdentSequence(input, pos, {}), true]; - } - - return undefined; -}; - -/** - * @param {string} chars characters - * @returns {(input: string, pos: number) => number} function to eat characters - */ -module.exports.eatUntil = chars => { - const charCodes = Array.from({ length: chars.length }, (_, i) => - chars.charCodeAt(i) - ); - const arr = Array.from( - { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 }, - () => false - ); - for (const cc of charCodes) { - arr[cc] = true; - } - - return (input, pos) => { - for (;;) { - const cc = input.charCodeAt(pos); - if (cc < arr.length && arr[cc]) { - return pos; - } - pos++; - if (pos === input.length) return pos; - } - }; -}; diff --git a/webpack-lib/lib/debug/ProfilingPlugin.js b/webpack-lib/lib/debug/ProfilingPlugin.js deleted file mode 100644 index 9f2d445a0d0..00000000000 --- a/webpack-lib/lib/debug/ProfilingPlugin.js +++ /dev/null @@ -1,504 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { Tracer } = require("chrome-trace-event"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM, - WEBASSEMBLY_MODULE_TYPE_ASYNC, - WEBASSEMBLY_MODULE_TYPE_SYNC, - JSON_MODULE_TYPE -} = require("../ModuleTypeConstants"); -const createSchemaValidation = require("../util/create-schema-validation"); -const { dirname, mkdirpSync } = require("../util/fs"); - -/** @typedef {import("../../declarations/plugins/debug/ProfilingPlugin").ProfilingPluginOptions} ProfilingPluginOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../ContextModuleFactory")} ContextModuleFactory */ -/** @typedef {import("../ModuleFactory")} ModuleFactory */ -/** @typedef {import("../NormalModuleFactory")} NormalModuleFactory */ -/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ - -/** @typedef {TODO} Inspector */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/debug/ProfilingPlugin.check.js"), - () => require("../../schemas/plugins/debug/ProfilingPlugin.json"), - { - name: "Profiling Plugin", - baseDataPath: "options" - } -); - -/** @type {Inspector | undefined} */ -let inspector; - -try { - // eslint-disable-next-line n/no-unsupported-features/node-builtins - inspector = require("inspector"); -} catch (_err) { - console.log("Unable to CPU profile in < node 8.0"); -} - -class Profiler { - /** - * @param {Inspector} inspector inspector - */ - constructor(inspector) { - this.session = undefined; - this.inspector = inspector; - this._startTime = 0; - } - - hasSession() { - return this.session !== undefined; - } - - startProfiling() { - if (this.inspector === undefined) { - return Promise.resolve(); - } - - try { - this.session = new inspector.Session(); - this.session.connect(); - } catch (_) { - this.session = undefined; - return Promise.resolve(); - } - - const hrtime = process.hrtime(); - this._startTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000); - - return Promise.all([ - this.sendCommand("Profiler.setSamplingInterval", { - interval: 100 - }), - this.sendCommand("Profiler.enable"), - this.sendCommand("Profiler.start") - ]); - } - - /** - * @param {string} method method name - * @param {object} [params] params - * @returns {Promise} Promise for the result - */ - sendCommand(method, params) { - if (this.hasSession()) { - return new Promise((res, rej) => { - this.session.post(method, params, (err, params) => { - if (err !== null) { - rej(err); - } else { - res(params); - } - }); - }); - } - return Promise.resolve(); - } - - destroy() { - if (this.hasSession()) { - this.session.disconnect(); - } - - return Promise.resolve(); - } - - stopProfiling() { - return this.sendCommand("Profiler.stop").then(({ profile }) => { - const hrtime = process.hrtime(); - const endTime = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000); - // Avoid coverage problems due indirect changes - /* istanbul ignore next */ - if (profile.startTime < this._startTime || profile.endTime > endTime) { - // In some cases timestamps mismatch and we need to adjust them - // Both process.hrtime and the inspector timestamps claim to be relative - // to a unknown point in time. But they do not guarantee that this is the - // same point in time. - const duration = profile.endTime - profile.startTime; - const ownDuration = endTime - this._startTime; - const untracked = Math.max(0, ownDuration - duration); - profile.startTime = this._startTime + untracked / 2; - profile.endTime = endTime - untracked / 2; - } - return { profile }; - }); - } -} - -/** - * an object that wraps Tracer and Profiler with a counter - * @typedef {object} Trace - * @property {Tracer} trace instance of Tracer - * @property {number} counter Counter - * @property {Profiler} profiler instance of Profiler - * @property {Function} end the end function - */ - -/** - * @param {IntermediateFileSystem} fs filesystem used for output - * @param {string} outputPath The location where to write the log. - * @returns {Trace} The trace object - */ -const createTrace = (fs, outputPath) => { - const trace = new Tracer(); - const profiler = new Profiler(/** @type {Inspector} */ (inspector)); - if (/\/|\\/.test(outputPath)) { - const dirPath = dirname(fs, outputPath); - mkdirpSync(fs, dirPath); - } - const fsStream = fs.createWriteStream(outputPath); - - let counter = 0; - - trace.pipe(fsStream); - // These are critical events that need to be inserted so that tools like - // chrome dev tools can load the profile. - trace.instantEvent({ - name: "TracingStartedInPage", - id: ++counter, - cat: ["disabled-by-default-devtools.timeline"], - args: { - data: { - sessionId: "-1", - page: "0xfff", - frames: [ - { - frame: "0xfff", - url: "webpack", - name: "" - } - ] - } - } - }); - - trace.instantEvent({ - name: "TracingStartedInBrowser", - id: ++counter, - cat: ["disabled-by-default-devtools.timeline"], - args: { - data: { - sessionId: "-1" - } - } - }); - - return { - trace, - counter, - profiler, - end: callback => { - trace.push("]"); - // Wait until the write stream finishes. - fsStream.on("close", () => { - callback(); - }); - // Tear down the readable trace stream. - trace.push(null); - } - }; -}; - -const PLUGIN_NAME = "ProfilingPlugin"; - -class ProfilingPlugin { - /** - * @param {ProfilingPluginOptions=} options options object - */ - constructor(options = {}) { - validate(options); - this.outputPath = options.outputPath || "events.json"; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const tracer = createTrace( - /** @type {IntermediateFileSystem} */ - (compiler.intermediateFileSystem), - this.outputPath - ); - tracer.profiler.startProfiling(); - - // Compiler Hooks - for (const hookName of Object.keys(compiler.hooks)) { - const hook = - compiler.hooks[/** @type {keyof Compiler["hooks"]} */ (hookName)]; - if (hook) { - hook.intercept(makeInterceptorFor("Compiler", tracer)(hookName)); - } - } - - for (const hookName of Object.keys(compiler.resolverFactory.hooks)) { - const hook = compiler.resolverFactory.hooks[hookName]; - if (hook) { - hook.intercept(makeInterceptorFor("Resolver", tracer)(hookName)); - } - } - - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory, contextModuleFactory }) => { - interceptAllHooksFor(compilation, tracer, "Compilation"); - interceptAllHooksFor( - normalModuleFactory, - tracer, - "Normal Module Factory" - ); - interceptAllHooksFor( - contextModuleFactory, - tracer, - "Context Module Factory" - ); - interceptAllParserHooks(normalModuleFactory, tracer); - interceptAllJavascriptModulesPluginHooks(compilation, tracer); - } - ); - - // We need to write out the CPU profile when we are all done. - compiler.hooks.done.tapAsync( - { - name: PLUGIN_NAME, - stage: Infinity - }, - (stats, callback) => { - if (compiler.watchMode) return callback(); - tracer.profiler.stopProfiling().then(parsedResults => { - if (parsedResults === undefined) { - tracer.profiler.destroy(); - tracer.end(callback); - return; - } - - const cpuStartTime = parsedResults.profile.startTime; - const cpuEndTime = parsedResults.profile.endTime; - - tracer.trace.completeEvent({ - name: "TaskQueueManager::ProcessTaskFromWorkQueue", - id: ++tracer.counter, - cat: ["toplevel"], - ts: cpuStartTime, - args: { - // eslint-disable-next-line camelcase - src_file: "../../ipc/ipc_moji_bootstrap.cc", - // eslint-disable-next-line camelcase - src_func: "Accept" - } - }); - - tracer.trace.completeEvent({ - name: "EvaluateScript", - id: ++tracer.counter, - cat: ["devtools.timeline"], - ts: cpuStartTime, - dur: cpuEndTime - cpuStartTime, - args: { - data: { - url: "webpack", - lineNumber: 1, - columnNumber: 1, - frame: "0xFFF" - } - } - }); - - tracer.trace.instantEvent({ - name: "CpuProfile", - id: ++tracer.counter, - cat: ["disabled-by-default-devtools.timeline"], - ts: cpuEndTime, - args: { - data: { - cpuProfile: parsedResults.profile - } - } - }); - - tracer.profiler.destroy(); - tracer.end(callback); - }); - } - ); - } -} - -/** - * @param {any} instance instance - * @param {Trace} tracer tracer - * @param {string} logLabel log label - */ -const interceptAllHooksFor = (instance, tracer, logLabel) => { - if (Reflect.has(instance, "hooks")) { - for (const hookName of Object.keys(instance.hooks)) { - const hook = instance.hooks[hookName]; - if (hook && !hook._fakeHook) { - hook.intercept(makeInterceptorFor(logLabel, tracer)(hookName)); - } - } - } -}; - -/** - * @param {NormalModuleFactory} moduleFactory normal module factory - * @param {Trace} tracer tracer - */ -const interceptAllParserHooks = (moduleFactory, tracer) => { - const moduleTypes = [ - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM, - JSON_MODULE_TYPE, - WEBASSEMBLY_MODULE_TYPE_ASYNC, - WEBASSEMBLY_MODULE_TYPE_SYNC - ]; - - for (const moduleType of moduleTypes) { - moduleFactory.hooks.parser - .for(moduleType) - .tap(PLUGIN_NAME, (parser, parserOpts) => { - interceptAllHooksFor(parser, tracer, "Parser"); - }); - } -}; - -/** - * @param {Compilation} compilation compilation - * @param {Trace} tracer tracer - */ -const interceptAllJavascriptModulesPluginHooks = (compilation, tracer) => { - interceptAllHooksFor( - { - hooks: - require("../javascript/JavascriptModulesPlugin").getCompilationHooks( - compilation - ) - }, - tracer, - "JavascriptModulesPlugin" - ); -}; - -/** - * @param {string} instance instance - * @param {Trace} tracer tracer - * @returns {TODO} interceptor - */ -const makeInterceptorFor = (instance, tracer) => hookName => ({ - register: tapInfo => { - const { name, type, fn } = tapInfo; - const newFn = - // Don't tap our own hooks to ensure stream can close cleanly - name === PLUGIN_NAME - ? fn - : makeNewProfiledTapFn(hookName, tracer, { - name, - type, - fn - }); - return { ...tapInfo, fn: newFn }; - } -}); - -// TODO improve typing -/** @typedef {(...args: TODO[]) => void | Promise} PluginFunction */ - -/** - * @param {string} hookName Name of the hook to profile. - * @param {Trace} tracer The trace object. - * @param {object} options Options for the profiled fn. - * @param {string} options.name Plugin name - * @param {string} options.type Plugin type (sync | async | promise) - * @param {PluginFunction} options.fn Plugin function - * @returns {PluginFunction} Chainable hooked function. - */ -const makeNewProfiledTapFn = (hookName, tracer, { name, type, fn }) => { - const defaultCategory = ["blink.user_timing"]; - - switch (type) { - case "promise": - return (...args) => { - const id = ++tracer.counter; - tracer.trace.begin({ - name, - id, - cat: defaultCategory - }); - const promise = /** @type {Promise<*>} */ (fn(...args)); - return promise.then(r => { - tracer.trace.end({ - name, - id, - cat: defaultCategory - }); - return r; - }); - }; - case "async": - return (...args) => { - const id = ++tracer.counter; - tracer.trace.begin({ - name, - id, - cat: defaultCategory - }); - const callback = args.pop(); - fn(...args, (...r) => { - tracer.trace.end({ - name, - id, - cat: defaultCategory - }); - callback(...r); - }); - }; - case "sync": - return (...args) => { - const id = ++tracer.counter; - // Do not instrument ourself due to the CPU - // profile needing to be the last event in the trace. - if (name === PLUGIN_NAME) { - return fn(...args); - } - - tracer.trace.begin({ - name, - id, - cat: defaultCategory - }); - let r; - try { - r = fn(...args); - } catch (err) { - tracer.trace.end({ - name, - id, - cat: defaultCategory - }); - throw err; - } - tracer.trace.end({ - name, - id, - cat: defaultCategory - }); - return r; - }; - default: - break; - } -}; - -module.exports = ProfilingPlugin; -module.exports.Profiler = Profiler; diff --git a/webpack-lib/lib/dependencies/AMDDefineDependency.js b/webpack-lib/lib/dependencies/AMDDefineDependency.js deleted file mode 100644 index 4acb1525271..00000000000 --- a/webpack-lib/lib/dependencies/AMDDefineDependency.js +++ /dev/null @@ -1,265 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** @type {Record} */ -const DEFINITIONS = { - f: { - definition: "var __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_RESULT__ = (#).call(exports, ${RuntimeGlobals.require}, exports, module), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [ - RuntimeGlobals.require, - RuntimeGlobals.exports, - RuntimeGlobals.module - ] - }, - o: { - definition: "", - content: "!(module.exports = #)", - requests: [RuntimeGlobals.module] - }, - of: { - definition: - "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_FACTORY__ = (#), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, ${RuntimeGlobals.require}, exports, module)) : - __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [ - RuntimeGlobals.require, - RuntimeGlobals.exports, - RuntimeGlobals.module - ] - }, - af: { - definition: - "var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_RESULT__ = (#).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [RuntimeGlobals.exports, RuntimeGlobals.module] - }, - ao: { - definition: "", - content: "!(#, module.exports = #)", - requests: [RuntimeGlobals.module] - }, - aof: { - definition: - "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;", - content: `!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_FACTORY__ = (#), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))`, - requests: [RuntimeGlobals.exports, RuntimeGlobals.module] - }, - lf: { - definition: "var XXX, XXXmodule;", - content: `!(XXXmodule = { id: YYY, exports: {}, loaded: false }, XXX = (#).call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule), XXXmodule.loaded = true, XXX === undefined && (XXX = XXXmodule.exports))`, - requests: [RuntimeGlobals.require, RuntimeGlobals.module] - }, - lo: { - definition: "var XXX;", - content: "!(XXX = #)", - requests: [] - }, - lof: { - definition: "var XXX, XXXfactory, XXXmodule;", - content: `!(XXXfactory = (#), (typeof XXXfactory === 'function' ? ((XXXmodule = { id: YYY, exports: {}, loaded: false }), (XXX = XXXfactory.call(XXXmodule.exports, ${RuntimeGlobals.require}, XXXmodule.exports, XXXmodule)), (XXXmodule.loaded = true), XXX === undefined && (XXX = XXXmodule.exports)) : XXX = XXXfactory))`, - requests: [RuntimeGlobals.require, RuntimeGlobals.module] - }, - laf: { - definition: "var __WEBPACK_AMD_DEFINE_ARRAY__, XXX, XXXexports;", - content: - "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, XXX = (#).apply(XXXexports = {}, __WEBPACK_AMD_DEFINE_ARRAY__), XXX === undefined && (XXX = XXXexports))", - requests: [] - }, - lao: { - definition: "var XXX;", - content: "!(#, XXX = #)", - requests: [] - }, - laof: { - definition: "var XXXarray, XXXfactory, XXXexports, XXX;", - content: `!(XXXarray = #, XXXfactory = (#), - (typeof XXXfactory === 'function' ? - ((XXX = XXXfactory.apply(XXXexports = {}, XXXarray)), XXX === undefined && (XXX = XXXexports)) : - (XXX = XXXfactory) - ))`, - requests: [] - } -}; - -class AMDDefineDependency extends NullDependency { - /** - * @param {Range} range range - * @param {Range | null} arrayRange array range - * @param {Range | null} functionRange function range - * @param {Range | null} objectRange object range - * @param {string | null} namedModule true, when define is called with a name - */ - constructor(range, arrayRange, functionRange, objectRange, namedModule) { - super(); - this.range = range; - this.arrayRange = arrayRange; - this.functionRange = functionRange; - this.objectRange = objectRange; - this.namedModule = namedModule; - this.localModule = null; - } - - get type() { - return "amd define"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.arrayRange); - write(this.functionRange); - write(this.objectRange); - write(this.namedModule); - write(this.localModule); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.arrayRange = read(); - this.functionRange = read(); - this.objectRange = read(); - this.namedModule = read(); - this.localModule = read(); - super.deserialize(context); - } -} - -makeSerializable( - AMDDefineDependency, - "webpack/lib/dependencies/AMDDefineDependency" -); - -AMDDefineDependency.Template = class AMDDefineDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { runtimeRequirements }) { - const dep = /** @type {AMDDefineDependency} */ (dependency); - const branch = this.branch(dep); - const { definition, content, requests } = DEFINITIONS[branch]; - for (const req of requests) { - runtimeRequirements.add(req); - } - this.replace(dep, source, definition, content); - } - - /** - * @param {AMDDefineDependency} dependency dependency - * @returns {string} variable name - */ - localModuleVar(dependency) { - return ( - dependency.localModule && - dependency.localModule.used && - dependency.localModule.variableName() - ); - } - - /** - * @param {AMDDefineDependency} dependency dependency - * @returns {string} branch - */ - branch(dependency) { - const localModuleVar = this.localModuleVar(dependency) ? "l" : ""; - const arrayRange = dependency.arrayRange ? "a" : ""; - const objectRange = dependency.objectRange ? "o" : ""; - const functionRange = dependency.functionRange ? "f" : ""; - return localModuleVar + arrayRange + objectRange + functionRange; - } - - /** - * @param {AMDDefineDependency} dependency dependency - * @param {ReplaceSource} source source - * @param {string} definition definition - * @param {string} text text - */ - replace(dependency, source, definition, text) { - const localModuleVar = this.localModuleVar(dependency); - if (localModuleVar) { - text = text.replace(/XXX/g, localModuleVar.replace(/\$/g, "$$$$")); - definition = definition.replace( - /XXX/g, - localModuleVar.replace(/\$/g, "$$$$") - ); - } - - if (dependency.namedModule) { - text = text.replace(/YYY/g, JSON.stringify(dependency.namedModule)); - } - - const texts = text.split("#"); - - if (definition) source.insert(0, definition); - - let current = dependency.range[0]; - if (dependency.arrayRange) { - source.replace( - current, - dependency.arrayRange[0] - 1, - /** @type {string} */ (texts.shift()) - ); - current = dependency.arrayRange[1]; - } - - if (dependency.objectRange) { - source.replace( - current, - dependency.objectRange[0] - 1, - /** @type {string} */ (texts.shift()) - ); - current = dependency.objectRange[1]; - } else if (dependency.functionRange) { - source.replace( - current, - dependency.functionRange[0] - 1, - /** @type {string} */ (texts.shift()) - ); - current = dependency.functionRange[1]; - } - source.replace( - current, - dependency.range[1] - 1, - /** @type {string} */ (texts.shift()) - ); - if (texts.length > 0) throw new Error("Implementation error"); - } -}; - -module.exports = AMDDefineDependency; diff --git a/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js b/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js deleted file mode 100644 index 14fbe4af218..00000000000 --- a/webpack-lib/lib/dependencies/AMDDefineDependencyParserPlugin.js +++ /dev/null @@ -1,497 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const AMDDefineDependency = require("./AMDDefineDependency"); -const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); -const AMDRequireContextDependency = require("./AMDRequireContextDependency"); -const AMDRequireItemDependency = require("./AMDRequireItemDependency"); -const ConstDependency = require("./ConstDependency"); -const ContextDependencyHelpers = require("./ContextDependencyHelpers"); -const DynamicExports = require("./DynamicExports"); -const LocalModuleDependency = require("./LocalModuleDependency"); -const { addLocalModule, getLocalModule } = require("./LocalModulesHelpers"); - -/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").FunctionExpression} FunctionExpression */ -/** @typedef {import("estree").Identifier} Identifier */ -/** @typedef {import("estree").Literal} Literal */ -/** @typedef {import("estree").MemberExpression} MemberExpression */ -/** @typedef {import("estree").ObjectExpression} ObjectExpression */ -/** @typedef {import("estree").SimpleCallExpression} SimpleCallExpression */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -/** - * @param {Expression | SpreadElement} expr expression - * @returns {expr is CallExpression} true if it's a bound function expression - */ -const isBoundFunctionExpression = expr => { - if (expr.type !== "CallExpression") return false; - if (expr.callee.type !== "MemberExpression") return false; - if (expr.callee.computed) return false; - if (expr.callee.object.type !== "FunctionExpression") return false; - if (expr.callee.property.type !== "Identifier") return false; - if (expr.callee.property.name !== "bind") return false; - return true; -}; - -/** @typedef {FunctionExpression | ArrowFunctionExpression} UnboundFunctionExpression */ - -/** - * @param {Expression | SpreadElement} expr expression - * @returns {expr is FunctionExpression | ArrowFunctionExpression} true when unbound function expression - */ -const isUnboundFunctionExpression = expr => { - if (expr.type === "FunctionExpression") return true; - if (expr.type === "ArrowFunctionExpression") return true; - return false; -}; - -/** - * @param {Expression | SpreadElement} expr expression - * @returns {expr is FunctionExpression | ArrowFunctionExpression | CallExpression} true when callable - */ -const isCallable = expr => { - if (isUnboundFunctionExpression(expr)) return true; - if (isBoundFunctionExpression(expr)) return true; - return false; -}; - -class AMDDefineDependencyParserPlugin { - /** - * @param {JavascriptParserOptions} options parserOptions - */ - constructor(options) { - this.options = options; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.call - .for("define") - .tap( - "AMDDefineDependencyParserPlugin", - this.processCallDefine.bind(this, parser) - ); - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @param {Record} identifiers identifiers - * @param {string=} namedModule named module - * @returns {boolean | undefined} result - */ - processArray(parser, expr, param, identifiers, namedModule) { - if (param.isArray()) { - const items = /** @type {BasicEvaluatedExpression[]} */ (param.items); - for (const [idx, item] of items.entries()) { - if ( - item.isString() && - ["require", "module", "exports"].includes( - /** @type {string} */ (item.string) - ) - ) - identifiers[/** @type {number} */ (idx)] = /** @type {string} */ ( - item.string - ); - const result = this.processItem(parser, expr, item, namedModule); - if (result === undefined) { - this.processContext(parser, expr, item); - } - } - return true; - } else if (param.isConstArray()) { - /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */ - const deps = []; - const array = /** @type {string[]} */ (param.array); - for (const [idx, request] of array.entries()) { - let dep; - let localModule; - if (request === "require") { - identifiers[idx] = request; - dep = RuntimeGlobals.require; - } else if (["exports", "module"].includes(request)) { - identifiers[idx] = request; - dep = request; - } else if ((localModule = getLocalModule(parser.state, request))) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, undefined, false); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } else { - dep = this.newRequireItemDependency(request); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - } - deps.push(dep); - } - const dep = this.newRequireArrayDependency( - deps, - /** @type {Range} */ (param.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.module.addPresentationalDependency(dep); - return true; - } - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @param {string=} namedModule named module - * @returns {boolean | undefined} result - */ - processItem(parser, expr, param, namedModule) { - if (param.isConditional()) { - const options = /** @type {BasicEvaluatedExpression[]} */ (param.options); - for (const item of options) { - const result = this.processItem(parser, expr, item); - if (result === undefined) { - this.processContext(parser, expr, item); - } - } - - return true; - } else if (param.isString()) { - let dep; - let localModule; - - if (param.string === "require") { - dep = new ConstDependency( - RuntimeGlobals.require, - /** @type {Range} */ (param.range), - [RuntimeGlobals.require] - ); - } else if (param.string === "exports") { - dep = new ConstDependency( - "exports", - /** @type {Range} */ (param.range), - [RuntimeGlobals.exports] - ); - } else if (param.string === "module") { - dep = new ConstDependency( - "module", - /** @type {Range} */ (param.range), - [RuntimeGlobals.module] - ); - } else if ( - (localModule = getLocalModule( - parser.state, - /** @type {string} */ (param.string), - namedModule - )) - ) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, param.range, false); - } else { - dep = this.newRequireItemDependency( - /** @type {string} */ (param.string), - param.range - ); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | undefined} result - */ - processContext(parser, expr, param) { - const dep = ContextDependencyHelpers.create( - AMDRequireContextDependency, - /** @type {Range} */ (param.range), - param, - expr, - this.options, - { - category: "amd" - }, - parser - ); - if (!dep) return; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @returns {boolean | undefined} result - */ - processCallDefine(parser, expr) { - /** @type {TODO} */ - let array; - /** @type {FunctionExpression | ArrowFunctionExpression | CallExpression | Identifier | undefined} */ - let fn; - /** @type {ObjectExpression | Identifier | undefined} */ - let obj; - /** @type {string | undefined} */ - let namedModule; - switch (expr.arguments.length) { - case 1: - if (isCallable(expr.arguments[0])) { - // define(f() {…}) - fn = expr.arguments[0]; - } else if (expr.arguments[0].type === "ObjectExpression") { - // define({…}) - obj = expr.arguments[0]; - } else { - // define(expr) - // unclear if function or object - obj = fn = /** @type {Identifier} */ (expr.arguments[0]); - } - break; - case 2: - if (expr.arguments[0].type === "Literal") { - namedModule = /** @type {string} */ (expr.arguments[0].value); - // define("…", …) - if (isCallable(expr.arguments[1])) { - // define("…", f() {…}) - fn = expr.arguments[1]; - } else if (expr.arguments[1].type === "ObjectExpression") { - // define("…", {…}) - obj = expr.arguments[1]; - } else { - // define("…", expr) - // unclear if function or object - obj = fn = /** @type {Identifier} */ (expr.arguments[1]); - } - } else { - array = expr.arguments[0]; - if (isCallable(expr.arguments[1])) { - // define([…], f() {}) - fn = expr.arguments[1]; - } else if (expr.arguments[1].type === "ObjectExpression") { - // define([…], {…}) - obj = expr.arguments[1]; - } else { - // define([…], expr) - // unclear if function or object - obj = fn = /** @type {Identifier} */ (expr.arguments[1]); - } - } - break; - case 3: - // define("…", […], f() {…}) - namedModule = - /** @type {string} */ - ( - /** @type {Literal} */ - (expr.arguments[0]).value - ); - array = expr.arguments[1]; - if (isCallable(expr.arguments[2])) { - // define("…", […], f() {}) - fn = expr.arguments[2]; - } else if (expr.arguments[2].type === "ObjectExpression") { - // define("…", […], {…}) - obj = expr.arguments[2]; - } else { - // define("…", […], expr) - // unclear if function or object - obj = fn = /** @type {Identifier} */ (expr.arguments[2]); - } - break; - default: - return; - } - DynamicExports.bailout(parser.state); - /** @type {Identifier[] | null} */ - let fnParams = null; - let fnParamsOffset = 0; - if (fn) { - if (isUnboundFunctionExpression(fn)) { - fnParams = - /** @type {Identifier[]} */ - (fn.params); - } else if (isBoundFunctionExpression(fn)) { - const object = - /** @type {FunctionExpression} */ - (/** @type {MemberExpression} */ (fn.callee).object); - - fnParams = - /** @type {Identifier[]} */ - (object.params); - fnParamsOffset = fn.arguments.length - 1; - if (fnParamsOffset < 0) { - fnParamsOffset = 0; - } - } - } - const fnRenames = new Map(); - if (array) { - /** @type {Record} */ - const identifiers = {}; - const param = parser.evaluateExpression(array); - const result = this.processArray( - parser, - expr, - param, - identifiers, - namedModule - ); - if (!result) return; - if (fnParams) { - fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { - if (identifiers[idx]) { - fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); - return false; - } - return true; - }); - } - } else { - const identifiers = ["require", "exports", "module"]; - if (fnParams) { - fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { - if (identifiers[idx]) { - fnRenames.set(param.name, parser.getVariableInfo(identifiers[idx])); - return false; - } - return true; - }); - } - } - /** @type {boolean | undefined} */ - let inTry; - if (fn && isUnboundFunctionExpression(fn)) { - inTry = parser.scope.inTry; - parser.inScope(fnParams, () => { - for (const [name, varInfo] of fnRenames) { - parser.setVariable(name, varInfo); - } - parser.scope.inTry = /** @type {boolean} */ (inTry); - if (fn.body.type === "BlockStatement") { - parser.detectMode(fn.body.body); - const prev = parser.prevStatement; - parser.preWalkStatement(fn.body); - parser.prevStatement = prev; - parser.walkStatement(fn.body); - } else { - parser.walkExpression(fn.body); - } - }); - } else if (fn && isBoundFunctionExpression(fn)) { - inTry = parser.scope.inTry; - - const object = - /** @type {FunctionExpression} */ - (/** @type {MemberExpression} */ (fn.callee).object); - - parser.inScope( - /** @type {Identifier[]} */ - (object.params).filter( - i => !["require", "module", "exports"].includes(i.name) - ), - () => { - for (const [name, varInfo] of fnRenames) { - parser.setVariable(name, varInfo); - } - parser.scope.inTry = /** @type {boolean} */ (inTry); - - if (object.body.type === "BlockStatement") { - parser.detectMode(object.body.body); - const prev = parser.prevStatement; - parser.preWalkStatement(object.body); - parser.prevStatement = prev; - parser.walkStatement(object.body); - } else { - parser.walkExpression(object.body); - } - } - ); - if (fn.arguments) { - parser.walkExpressions(fn.arguments); - } - } else if (fn || obj) { - parser.walkExpression(fn || obj); - } - - const dep = this.newDefineDependency( - /** @type {Range} */ (expr.range), - array ? /** @type {Range} */ (array.range) : null, - fn ? /** @type {Range} */ (fn.range) : null, - obj ? /** @type {Range} */ (obj.range) : null, - namedModule || null - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - if (namedModule) { - dep.localModule = addLocalModule(parser.state, namedModule); - } - parser.state.module.addPresentationalDependency(dep); - return true; - } - - /** - * @param {Range} range range - * @param {Range | null} arrayRange array range - * @param {Range | null} functionRange function range - * @param {Range | null} objectRange object range - * @param {string | null} namedModule true, when define is called with a name - * @returns {AMDDefineDependency} AMDDefineDependency - */ - newDefineDependency( - range, - arrayRange, - functionRange, - objectRange, - namedModule - ) { - return new AMDDefineDependency( - range, - arrayRange, - functionRange, - objectRange, - namedModule - ); - } - - /** - * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array - * @param {Range} range range - * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency - */ - newRequireArrayDependency(depsArray, range) { - return new AMDRequireArrayDependency(depsArray, range); - } - - /** - * @param {string} request request - * @param {Range=} range range - * @returns {AMDRequireItemDependency} AMDRequireItemDependency - */ - newRequireItemDependency(request, range) { - return new AMDRequireItemDependency(request, range); - } -} - -module.exports = AMDDefineDependencyParserPlugin; diff --git a/webpack-lib/lib/dependencies/AMDPlugin.js b/webpack-lib/lib/dependencies/AMDPlugin.js deleted file mode 100644 index 2ae03b78bc7..00000000000 --- a/webpack-lib/lib/dependencies/AMDPlugin.js +++ /dev/null @@ -1,236 +0,0 @@ -/* - 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 -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const { - approve, - evaluateToIdentifier, - evaluateToString, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); - -const AMDDefineDependency = require("./AMDDefineDependency"); -const AMDDefineDependencyParserPlugin = require("./AMDDefineDependencyParserPlugin"); -const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); -const AMDRequireContextDependency = require("./AMDRequireContextDependency"); -const AMDRequireDependenciesBlockParserPlugin = require("./AMDRequireDependenciesBlockParserPlugin"); -const AMDRequireDependency = require("./AMDRequireDependency"); -const AMDRequireItemDependency = require("./AMDRequireItemDependency"); -const { - AMDDefineRuntimeModule, - AMDOptionsRuntimeModule -} = require("./AMDRuntimeModules"); -const ConstDependency = require("./ConstDependency"); -const LocalModuleDependency = require("./LocalModuleDependency"); -const UnsupportedDependency = require("./UnsupportedDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const PLUGIN_NAME = "AMDPlugin"; - -class AMDPlugin { - /** - * @param {Record} amdOptions the AMD options - */ - constructor(amdOptions) { - this.amdOptions = amdOptions; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const amdOptions = this.amdOptions; - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { contextModuleFactory, normalModuleFactory }) => { - compilation.dependencyTemplates.set( - AMDRequireDependency, - new AMDRequireDependency.Template() - ); - - compilation.dependencyFactories.set( - AMDRequireItemDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - AMDRequireItemDependency, - new AMDRequireItemDependency.Template() - ); - - compilation.dependencyTemplates.set( - AMDRequireArrayDependency, - new AMDRequireArrayDependency.Template() - ); - - compilation.dependencyFactories.set( - AMDRequireContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - AMDRequireContextDependency, - new AMDRequireContextDependency.Template() - ); - - compilation.dependencyTemplates.set( - AMDDefineDependency, - new AMDDefineDependency.Template() - ); - - compilation.dependencyTemplates.set( - UnsupportedDependency, - new UnsupportedDependency.Template() - ); - - compilation.dependencyTemplates.set( - LocalModuleDependency, - new LocalModuleDependency.Template() - ); - - compilation.hooks.runtimeRequirementInModule - .for(RuntimeGlobals.amdDefine) - .tap(PLUGIN_NAME, (module, set) => { - set.add(RuntimeGlobals.require); - }); - - compilation.hooks.runtimeRequirementInModule - .for(RuntimeGlobals.amdOptions) - .tap(PLUGIN_NAME, (module, set) => { - set.add(RuntimeGlobals.requireScope); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.amdDefine) - .tap(PLUGIN_NAME, (chunk, set) => { - compilation.addRuntimeModule(chunk, new AMDDefineRuntimeModule()); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.amdOptions) - .tap(PLUGIN_NAME, (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new AMDOptionsRuntimeModule(amdOptions) - ); - }); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.amd !== undefined && !parserOptions.amd) return; - - /** - * @param {string} optionExpr option expression - * @param {string} rootName root name - * @param {function(): TODO} getMembers callback - */ - const tapOptionsHooks = (optionExpr, rootName, getMembers) => { - parser.hooks.expression - .for(optionExpr) - .tap( - PLUGIN_NAME, - toConstantDependency(parser, RuntimeGlobals.amdOptions, [ - RuntimeGlobals.amdOptions - ]) - ); - parser.hooks.evaluateIdentifier - .for(optionExpr) - .tap( - PLUGIN_NAME, - evaluateToIdentifier(optionExpr, rootName, getMembers, true) - ); - parser.hooks.evaluateTypeof - .for(optionExpr) - .tap(PLUGIN_NAME, evaluateToString("object")); - parser.hooks.typeof - .for(optionExpr) - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("object")) - ); - }; - - new AMDRequireDependenciesBlockParserPlugin(parserOptions).apply( - parser - ); - new AMDDefineDependencyParserPlugin(parserOptions).apply(parser); - - tapOptionsHooks("define.amd", "define", () => "amd"); - tapOptionsHooks("require.amd", "require", () => ["amd"]); - tapOptionsHooks( - "__webpack_amd_options__", - "__webpack_amd_options__", - () => [] - ); - - parser.hooks.expression.for("define").tap(PLUGIN_NAME, expr => { - const dep = new ConstDependency( - RuntimeGlobals.amdDefine, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.amdDefine] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - parser.hooks.typeof - .for("define") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("function")) - ); - parser.hooks.evaluateTypeof - .for("define") - .tap(PLUGIN_NAME, evaluateToString("function")); - parser.hooks.canRename.for("define").tap(PLUGIN_NAME, approve); - parser.hooks.rename.for("define").tap(PLUGIN_NAME, expr => { - const dep = new ConstDependency( - RuntimeGlobals.amdDefine, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.amdDefine] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return false; - }); - parser.hooks.typeof - .for("require") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("function")) - ); - parser.hooks.evaluateTypeof - .for("require") - .tap(PLUGIN_NAME, evaluateToString("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); - } - ); - } -} - -module.exports = AMDPlugin; diff --git a/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js b/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js deleted file mode 100644 index a182f6c230f..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireArrayDependency.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const DependencyTemplate = require("../DependencyTemplate"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./AMDRequireItemDependency")} AMDRequireItemDependency */ -/** @typedef {import("./LocalModuleDependency")} LocalModuleDependency */ - -class AMDRequireArrayDependency extends NullDependency { - /** - * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array - * @param {Range} range range - */ - constructor(depsArray, range) { - super(); - - this.depsArray = depsArray; - this.range = range; - } - - get type() { - return "amd require array"; - } - - get category() { - return "amd"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.depsArray); - write(this.range); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.depsArray = read(); - this.range = read(); - - super.deserialize(context); - } -} - -makeSerializable( - AMDRequireArrayDependency, - "webpack/lib/dependencies/AMDRequireArrayDependency" -); - -AMDRequireArrayDependency.Template = class AMDRequireArrayDependencyTemplate extends ( - DependencyTemplate -) { - /** - * @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 dep = /** @type {AMDRequireArrayDependency} */ (dependency); - const content = this.getContent(dep, templateContext); - source.replace(dep.range[0], dep.range[1] - 1, content); - } - - /** - * @param {AMDRequireArrayDependency} dep the dependency for which the template should be applied - * @param {DependencyTemplateContext} templateContext the context object - * @returns {string} content - */ - getContent(dep, templateContext) { - const requires = dep.depsArray.map(dependency => - this.contentForDependency(dependency, templateContext) - ); - return `[${requires.join(", ")}]`; - } - - /** - * @param {TODO} dep the dependency for which the template should be applied - * @param {DependencyTemplateContext} templateContext the context object - * @returns {string} content - */ - contentForDependency( - dep, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - if (typeof dep === "string") { - return dep; - } - - if (dep.localModule) { - return dep.localModule.variableName(); - } - return runtimeTemplate.moduleExports({ - module: moduleGraph.getModule(dep), - chunkGraph, - request: dep.request, - runtimeRequirements - }); - } -}; - -module.exports = AMDRequireArrayDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireContextDependency.js b/webpack-lib/lib/dependencies/AMDRequireContextDependency.js deleted file mode 100644 index f040cb6e861..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireContextDependency.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class AMDRequireContextDependency extends ContextDependency { - /** - * @param {TODO} options options - * @param {Range} range range - * @param {Range} valueRange value range - */ - constructor(options, range, valueRange) { - super(options); - - this.range = range; - this.valueRange = valueRange; - } - - get type() { - return "amd require context"; - } - - get category() { - return "amd"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.range); - write(this.valueRange); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.range = read(); - this.valueRange = read(); - - super.deserialize(context); - } -} - -makeSerializable( - AMDRequireContextDependency, - "webpack/lib/dependencies/AMDRequireContextDependency" -); - -AMDRequireContextDependency.Template = require("./ContextDependencyTemplateAsRequireCall"); - -module.exports = AMDRequireContextDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js deleted file mode 100644 index 615660c3c9e..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlock.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ - -class AMDRequireDependenciesBlock extends AsyncDependenciesBlock { - /** - * @param {DependencyLocation} loc location info - * @param {string=} request request - */ - constructor(loc, request) { - super(null, loc, request); - } -} - -makeSerializable( - AMDRequireDependenciesBlock, - "webpack/lib/dependencies/AMDRequireDependenciesBlock" -); - -module.exports = AMDRequireDependenciesBlock; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js b/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js deleted file mode 100644 index 803ce398bee..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +++ /dev/null @@ -1,410 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const AMDRequireArrayDependency = require("./AMDRequireArrayDependency"); -const AMDRequireContextDependency = require("./AMDRequireContextDependency"); -const AMDRequireDependenciesBlock = require("./AMDRequireDependenciesBlock"); -const AMDRequireDependency = require("./AMDRequireDependency"); -const AMDRequireItemDependency = require("./AMDRequireItemDependency"); -const ConstDependency = require("./ConstDependency"); -const ContextDependencyHelpers = require("./ContextDependencyHelpers"); -const LocalModuleDependency = require("./LocalModuleDependency"); -const { getLocalModule } = require("./LocalModulesHelpers"); -const UnsupportedDependency = require("./UnsupportedDependency"); -const getFunctionExpression = require("./getFunctionExpression"); - -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").Identifier} Identifier */ -/** @typedef {import("estree").SourceLocation} SourceLocation */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class AMDRequireDependenciesBlockParserPlugin { - /** - * @param {JavascriptParserOptions} options parserOptions - */ - constructor(options) { - this.options = options; - } - - /** - * @param {JavascriptParser} parser the parser - * @param {Expression | SpreadElement} expression expression - * @returns {boolean} need bind this - */ - processFunctionArgument(parser, expression) { - let bindThis = true; - const fnData = getFunctionExpression(expression); - if (fnData) { - parser.inScope( - fnData.fn.params.filter( - i => - !["require", "module", "exports"].includes( - /** @type {Identifier} */ (i).name - ) - ), - () => { - if (fnData.fn.body.type === "BlockStatement") { - parser.walkStatement(fnData.fn.body); - } else { - parser.walkExpression(fnData.fn.body); - } - } - ); - parser.walkExpressions(fnData.expressions); - if (fnData.needThis === false) { - bindThis = false; - } - } else { - parser.walkExpression(expression); - } - return bindThis; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.call - .for("require") - .tap( - "AMDRequireDependenciesBlockParserPlugin", - this.processCallRequire.bind(this, parser) - ); - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | undefined} result - */ - processArray(parser, expr, param) { - if (param.isArray()) { - for (const p of /** @type {BasicEvaluatedExpression[]} */ (param.items)) { - const result = this.processItem(parser, expr, p); - if (result === undefined) { - this.processContext(parser, expr, p); - } - } - return true; - } else if (param.isConstArray()) { - /** @type {(string | LocalModuleDependency | AMDRequireItemDependency)[]} */ - const deps = []; - for (const request of /** @type {any[]} */ (param.array)) { - let dep; - let localModule; - if (request === "require") { - dep = RuntimeGlobals.require; - } else if (["exports", "module"].includes(request)) { - dep = request; - } else if ((localModule = getLocalModule(parser.state, request))) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, undefined, false); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } else { - dep = this.newRequireItemDependency(request); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - } - deps.push(dep); - } - const dep = this.newRequireArrayDependency( - deps, - /** @type {Range} */ (param.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.module.addPresentationalDependency(dep); - return true; - } - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | undefined} result - */ - processItem(parser, expr, param) { - if (param.isConditional()) { - for (const p of /** @type {BasicEvaluatedExpression[]} */ ( - param.options - )) { - const result = this.processItem(parser, expr, p); - if (result === undefined) { - this.processContext(parser, expr, p); - } - } - return true; - } else if (param.isString()) { - let dep; - let localModule; - if (param.string === "require") { - dep = new ConstDependency( - RuntimeGlobals.require, - /** @type {TODO} */ (param.string), - [RuntimeGlobals.require] - ); - } else if (param.string === "module") { - dep = new ConstDependency( - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).moduleArgument, - /** @type {Range} */ (param.range), - [RuntimeGlobals.module] - ); - } else if (param.string === "exports") { - dep = new ConstDependency( - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).exportsArgument, - /** @type {Range} */ (param.range), - [RuntimeGlobals.exports] - ); - } else if ( - (localModule = getLocalModule( - parser.state, - /** @type {string} */ (param.string) - )) - ) { - localModule.flagUsed(); - dep = new LocalModuleDependency(localModule, param.range, false); - } else { - dep = this.newRequireItemDependency( - /** @type {string} */ (param.string), - param.range - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | undefined} result - */ - processContext(parser, expr, param) { - const dep = ContextDependencyHelpers.create( - AMDRequireContextDependency, - /** @type {Range} */ (param.range), - param, - expr, - this.options, - { - category: "amd" - }, - parser - ); - if (!dep) return; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - - /** - * @param {BasicEvaluatedExpression} param param - * @returns {string | undefined} result - */ - processArrayForRequestString(param) { - if (param.isArray()) { - const result = - /** @type {BasicEvaluatedExpression[]} */ - (param.items).map(item => this.processItemForRequestString(item)); - if (result.every(Boolean)) return result.join(" "); - } else if (param.isConstArray()) { - return /** @type {string[]} */ (param.array).join(" "); - } - } - - /** - * @param {BasicEvaluatedExpression} param param - * @returns {string | undefined} result - */ - processItemForRequestString(param) { - if (param.isConditional()) { - const result = - /** @type {BasicEvaluatedExpression[]} */ - (param.options).map(item => this.processItemForRequestString(item)); - if (result.every(Boolean)) return result.join("|"); - } else if (param.isString()) { - return param.string; - } - } - - /** - * @param {JavascriptParser} parser the parser - * @param {CallExpression} expr call expression - * @returns {boolean | undefined} result - */ - processCallRequire(parser, expr) { - /** @type {BasicEvaluatedExpression | undefined} */ - let param; - /** @type {AMDRequireDependenciesBlock | undefined | null} */ - let depBlock; - /** @type {AMDRequireDependency | undefined} */ - let dep; - /** @type {boolean | undefined} */ - let result; - - const old = parser.state.current; - - if (expr.arguments.length >= 1) { - param = parser.evaluateExpression( - /** @type {Expression} */ (expr.arguments[0]) - ); - depBlock = this.newRequireDependenciesBlock( - /** @type {DependencyLocation} */ (expr.loc), - this.processArrayForRequestString(param) - ); - dep = this.newRequireDependency( - /** @type {Range} */ (expr.range), - /** @type {Range} */ (param.range), - expr.arguments.length > 1 - ? /** @type {Range} */ (expr.arguments[1].range) - : null, - expr.arguments.length > 2 - ? /** @type {Range} */ (expr.arguments[2].range) - : null - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - depBlock.addDependency(dep); - - parser.state.current = /** @type {TODO} */ (depBlock); - } - - if (expr.arguments.length === 1) { - parser.inScope([], () => { - result = this.processArray( - parser, - expr, - /** @type {BasicEvaluatedExpression} */ (param) - ); - }); - parser.state.current = old; - if (!result) return; - parser.state.current.addBlock( - /** @type {AMDRequireDependenciesBlock} */ (depBlock) - ); - return true; - } - - if (expr.arguments.length === 2 || expr.arguments.length === 3) { - try { - parser.inScope([], () => { - result = this.processArray( - parser, - expr, - /** @type {BasicEvaluatedExpression} */ (param) - ); - }); - if (!result) { - const dep = new UnsupportedDependency( - "unsupported", - /** @type {Range} */ (expr.range) - ); - old.addPresentationalDependency(dep); - if (parser.state.module) { - parser.state.module.addError( - new UnsupportedFeatureWarning( - `Cannot statically analyse 'require(…, …)' in line ${ - /** @type {SourceLocation} */ (expr.loc).start.line - }`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - depBlock = null; - return true; - } - /** @type {AMDRequireDependency} */ - (dep).functionBindThis = this.processFunctionArgument( - parser, - expr.arguments[1] - ); - if (expr.arguments.length === 3) { - /** @type {AMDRequireDependency} */ - (dep).errorCallbackBindThis = this.processFunctionArgument( - parser, - expr.arguments[2] - ); - } - } finally { - parser.state.current = old; - if (depBlock) parser.state.current.addBlock(depBlock); - } - return true; - } - } - - /** - * @param {DependencyLocation} loc location - * @param {string=} request request - * @returns {AMDRequireDependenciesBlock} AMDRequireDependenciesBlock - */ - newRequireDependenciesBlock(loc, request) { - return new AMDRequireDependenciesBlock(loc, request); - } - - /** - * @param {Range} outerRange outer range - * @param {Range} arrayRange array range - * @param {Range | null} functionRange function range - * @param {Range | null} errorCallbackRange error callback range - * @returns {AMDRequireDependency} dependency - */ - newRequireDependency( - outerRange, - arrayRange, - functionRange, - errorCallbackRange - ) { - return new AMDRequireDependency( - outerRange, - arrayRange, - functionRange, - errorCallbackRange - ); - } - - /** - * @param {string} request request - * @param {Range=} range range - * @returns {AMDRequireItemDependency} AMDRequireItemDependency - */ - newRequireItemDependency(request, range) { - return new AMDRequireItemDependency(request, range); - } - - /** - * @param {(string | LocalModuleDependency | AMDRequireItemDependency)[]} depsArray deps array - * @param {Range} range range - * @returns {AMDRequireArrayDependency} AMDRequireArrayDependency - */ - newRequireArrayDependency(depsArray, range) { - return new AMDRequireArrayDependency(depsArray, range); - } -} -module.exports = AMDRequireDependenciesBlockParserPlugin; diff --git a/webpack-lib/lib/dependencies/AMDRequireDependency.js b/webpack-lib/lib/dependencies/AMDRequireDependency.js deleted file mode 100644 index 930348fc948..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireDependency.js +++ /dev/null @@ -1,189 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class AMDRequireDependency extends NullDependency { - /** - * @param {Range} outerRange outer range - * @param {Range} arrayRange array range - * @param {Range | null} functionRange function range - * @param {Range | null} errorCallbackRange error callback range - */ - constructor(outerRange, arrayRange, functionRange, errorCallbackRange) { - super(); - - this.outerRange = outerRange; - this.arrayRange = arrayRange; - this.functionRange = functionRange; - this.errorCallbackRange = errorCallbackRange; - this.functionBindThis = false; - this.errorCallbackBindThis = false; - } - - get category() { - return "amd"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.outerRange); - write(this.arrayRange); - write(this.functionRange); - write(this.errorCallbackRange); - write(this.functionBindThis); - write(this.errorCallbackBindThis); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.outerRange = read(); - this.arrayRange = read(); - this.functionRange = read(); - this.errorCallbackRange = read(); - this.functionBindThis = read(); - this.errorCallbackBindThis = read(); - - super.deserialize(context); - } -} - -makeSerializable( - AMDRequireDependency, - "webpack/lib/dependencies/AMDRequireDependency" -); - -AMDRequireDependency.Template = class AMDRequireDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {AMDRequireDependency} */ (dependency); - const depBlock = /** @type {AsyncDependenciesBlock} */ ( - moduleGraph.getParentBlock(dep) - ); - const promise = runtimeTemplate.blockPromise({ - chunkGraph, - block: depBlock, - message: "AMD require", - runtimeRequirements - }); - - // has array range but no function range - if (dep.arrayRange && !dep.functionRange) { - const startBlock = `${promise}.then(function() {`; - const endBlock = `;})['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; - runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); - - source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); - - source.replace(dep.arrayRange[1], dep.outerRange[1] - 1, endBlock); - - return; - } - - // has function range but no array range - if (dep.functionRange && !dep.arrayRange) { - const startBlock = `${promise}.then((`; - const endBlock = `).bind(exports, ${RuntimeGlobals.require}, exports, module))['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; - runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); - - source.replace(dep.outerRange[0], dep.functionRange[0] - 1, startBlock); - - source.replace(dep.functionRange[1], dep.outerRange[1] - 1, endBlock); - - return; - } - - // has array range, function range, and errorCallbackRange - if (dep.arrayRange && dep.functionRange && dep.errorCallbackRange) { - const startBlock = `${promise}.then(function() { `; - const errorRangeBlock = `}${ - dep.functionBindThis ? ".bind(this)" : "" - })['catch'](`; - const endBlock = `${dep.errorCallbackBindThis ? ".bind(this)" : ""})`; - - source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); - - source.insert(dep.arrayRange[0], "var __WEBPACK_AMD_REQUIRE_ARRAY__ = "); - - source.replace(dep.arrayRange[1], dep.functionRange[0] - 1, "; ("); - - source.insert( - dep.functionRange[1], - ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);" - ); - - source.replace( - dep.functionRange[1], - dep.errorCallbackRange[0] - 1, - errorRangeBlock - ); - - source.replace( - dep.errorCallbackRange[1], - dep.outerRange[1] - 1, - endBlock - ); - - return; - } - - // has array range, function range, but no errorCallbackRange - if (dep.arrayRange && dep.functionRange) { - const startBlock = `${promise}.then(function() { `; - const endBlock = `}${ - dep.functionBindThis ? ".bind(this)" : "" - })['catch'](${RuntimeGlobals.uncaughtErrorHandler})`; - runtimeRequirements.add(RuntimeGlobals.uncaughtErrorHandler); - - source.replace(dep.outerRange[0], dep.arrayRange[0] - 1, startBlock); - - source.insert(dep.arrayRange[0], "var __WEBPACK_AMD_REQUIRE_ARRAY__ = "); - - source.replace(dep.arrayRange[1], dep.functionRange[0] - 1, "; ("); - - source.insert( - dep.functionRange[1], - ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);" - ); - - source.replace(dep.functionRange[1], dep.outerRange[1] - 1, endBlock); - } - } -}; - -module.exports = AMDRequireDependency; diff --git a/webpack-lib/lib/dependencies/AMDRequireItemDependency.js b/webpack-lib/lib/dependencies/AMDRequireItemDependency.js deleted file mode 100644 index 614633ad324..00000000000 --- a/webpack-lib/lib/dependencies/AMDRequireItemDependency.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class AMDRequireItemDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range=} range location in source code - */ - constructor(request, range) { - super(request); - - this.range = range; - } - - get type() { - return "amd require"; - } - - get category() { - return "amd"; - } -} - -makeSerializable( - AMDRequireItemDependency, - "webpack/lib/dependencies/AMDRequireItemDependency" -); - -AMDRequireItemDependency.Template = ModuleDependencyTemplateAsRequireId; - -module.exports = AMDRequireItemDependency; diff --git a/webpack-lib/lib/dependencies/AMDRuntimeModules.js b/webpack-lib/lib/dependencies/AMDRuntimeModules.js deleted file mode 100644 index cec00bb8412..00000000000 --- a/webpack-lib/lib/dependencies/AMDRuntimeModules.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -class AMDDefineRuntimeModule extends RuntimeModule { - constructor() { - super("amd define"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return Template.asString([ - `${RuntimeGlobals.amdDefine} = function () {`, - Template.indent("throw new Error('define cannot be used indirect');"), - "};" - ]); - } -} - -class AMDOptionsRuntimeModule extends RuntimeModule { - /** - * @param {Record} options the AMD options - */ - constructor(options) { - super("amd options"); - this.options = options; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return Template.asString([ - `${RuntimeGlobals.amdOptions} = ${JSON.stringify(this.options)};` - ]); - } -} - -module.exports.AMDDefineRuntimeModule = AMDDefineRuntimeModule; -module.exports.AMDOptionsRuntimeModule = AMDOptionsRuntimeModule; diff --git a/webpack-lib/lib/dependencies/CachedConstDependency.js b/webpack-lib/lib/dependencies/CachedConstDependency.js deleted file mode 100644 index 913904abc94..00000000000 --- a/webpack-lib/lib/dependencies/CachedConstDependency.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const DependencyTemplate = require("../DependencyTemplate"); -const InitFragment = require("../InitFragment"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class CachedConstDependency extends NullDependency { - /** - * @param {string} expression expression - * @param {Range} range range - * @param {string} identifier identifier - */ - constructor(expression, range, identifier) { - super(); - - this.expression = expression; - this.range = range; - this.identifier = identifier; - this._hashUpdate = undefined; - } - - /** - * @returns {string} hash update - */ - _createHashUpdate() { - return `${this.identifier}${this.range}${this.expression}`; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = this._createHashUpdate(); - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.expression); - write(this.range); - write(this.identifier); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.expression = read(); - this.range = read(); - this.identifier = read(); - - super.deserialize(context); - } -} - -makeSerializable( - CachedConstDependency, - "webpack/lib/dependencies/CachedConstDependency" -); - -CachedConstDependency.Template = class CachedConstDependencyTemplate extends ( - DependencyTemplate -) { - /** - * @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, - { runtimeTemplate, dependencyTemplates, initFragments } - ) { - const dep = /** @type {CachedConstDependency} */ (dependency); - - initFragments.push( - new InitFragment( - `var ${dep.identifier} = ${dep.expression};\n`, - InitFragment.STAGE_CONSTANTS, - 0, - `const ${dep.identifier}` - ) - ); - - if (typeof dep.range === "number") { - source.insert(dep.range, dep.identifier); - - return; - } - - source.replace(dep.range[0], dep.range[1] - 1, dep.identifier); - } -}; - -module.exports = CachedConstDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js b/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js deleted file mode 100644 index 0cd457ee73a..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsDependencyHelpers.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); - -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ -/** @typedef {"exports" | "module.exports" | "this" | "Object.defineProperty(exports)" | "Object.defineProperty(module.exports)" | "Object.defineProperty(this)"} CommonJSDependencyBaseKeywords */ - -/** - * @param {CommonJSDependencyBaseKeywords} depBase commonjs dependency base - * @param {Module} module module - * @param {RuntimeRequirements} runtimeRequirements runtime requirements - * @returns {[string, string]} type and base - */ -module.exports.handleDependencyBase = ( - depBase, - module, - runtimeRequirements -) => { - let base; - let type; - switch (depBase) { - case "exports": - runtimeRequirements.add(RuntimeGlobals.exports); - base = module.exportsArgument; - type = "expression"; - break; - case "module.exports": - runtimeRequirements.add(RuntimeGlobals.module); - base = `${module.moduleArgument}.exports`; - type = "expression"; - break; - case "this": - runtimeRequirements.add(RuntimeGlobals.thisAsExports); - base = "this"; - type = "expression"; - break; - case "Object.defineProperty(exports)": - runtimeRequirements.add(RuntimeGlobals.exports); - base = module.exportsArgument; - type = "Object.defineProperty"; - break; - case "Object.defineProperty(module.exports)": - runtimeRequirements.add(RuntimeGlobals.module); - base = `${module.moduleArgument}.exports`; - type = "Object.defineProperty"; - break; - case "Object.defineProperty(this)": - runtimeRequirements.add(RuntimeGlobals.thisAsExports); - base = "this"; - type = "Object.defineProperty"; - break; - default: - throw new Error(`Unsupported base ${depBase}`); - } - - return [type, base]; -}; diff --git a/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js deleted file mode 100644 index d4f7a73c8fe..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsExportRequireDependency.js +++ /dev/null @@ -1,404 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const { UsageState } = require("../ExportsInfo"); -const Template = require("../Template"); -const { equals } = require("../util/ArrayHelpers"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const { handleDependencyBase } = require("./CommonJsDependencyHelpers"); -const ModuleDependency = require("./ModuleDependency"); -const processExportInfo = require("./processExportInfo"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ExportsInfo")} ExportsInfo */ -/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ - -const idsSymbol = Symbol("CommonJsExportRequireDependency.ids"); - -const EMPTY_OBJECT = {}; - -class CommonJsExportRequireDependency extends ModuleDependency { - /** - * @param {Range} range range - * @param {Range | null} valueRange value range - * @param {CommonJSDependencyBaseKeywords} base base - * @param {string[]} names names - * @param {string} request request - * @param {string[]} ids ids - * @param {boolean} resultUsed true, when the result is used - */ - constructor(range, valueRange, base, names, request, ids, resultUsed) { - super(request); - this.range = range; - this.valueRange = valueRange; - this.base = base; - this.names = names; - this.ids = ids; - this.resultUsed = resultUsed; - this.asiSafe = undefined; - } - - get type() { - return "cjs export require"; - } - - /** - * @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 Dependency.TRANSITIVE; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {string[]} the imported id - */ - getIds(moduleGraph) { - return ( - /** @type {TODO} */ (moduleGraph.getMeta(this))[idsSymbol] || this.ids - ); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {string[]} ids the imported ids - * @returns {void} - */ - setIds(moduleGraph, ids) { - /** @type {TODO} */ (moduleGraph.getMeta(this))[idsSymbol] = ids; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - const ids = this.getIds(moduleGraph); - const getFullResult = () => { - if (ids.length === 0) { - return Dependency.EXPORTS_OBJECT_REFERENCED; - } - return [ - { - name: ids, - canMangle: false - } - ]; - }; - if (this.resultUsed) return getFullResult(); - /** @type {ExportsInfo | undefined} */ - let exportsInfo = moduleGraph.getExportsInfo( - /** @type {Module} */ (moduleGraph.getParentModule(this)) - ); - for (const name of this.names) { - const exportInfo = /** @type {ExportInfo} */ ( - exportsInfo.getReadOnlyExportInfo(name) - ); - const used = exportInfo.getUsed(runtime); - if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED; - if (used !== UsageState.OnlyPropertiesUsed) return getFullResult(); - exportsInfo = exportInfo.exportsInfo; - if (!exportsInfo) return getFullResult(); - } - if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) { - return getFullResult(); - } - /** @type {string[][]} */ - const referencedExports = []; - for (const exportInfo of exportsInfo.orderedExports) { - processExportInfo( - runtime, - referencedExports, - ids.concat(exportInfo.name), - exportInfo, - false - ); - } - return referencedExports.map(name => ({ - name, - canMangle: false - })); - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - if (this.names.length === 1) { - const ids = this.getIds(moduleGraph); - const name = this.names[0]; - const from = moduleGraph.getConnection(this); - if (!from) return; - return { - exports: [ - { - name, - from, - export: ids.length === 0 ? null : ids, - // we can't mangle names that are in an empty object - // because one could access the prototype property - // when export isn't set yet - canMangle: !(name in EMPTY_OBJECT) && false - } - ], - dependencies: [from.module] - }; - } else if (this.names.length > 0) { - const name = this.names[0]; - return { - exports: [ - { - name, - // we can't mangle names that are in an empty object - // because one could access the prototype property - // when export isn't set yet - canMangle: !(name in EMPTY_OBJECT) && false - } - ], - dependencies: undefined - }; - } - const from = moduleGraph.getConnection(this); - if (!from) return; - const reexportInfo = this.getStarReexports( - moduleGraph, - undefined, - from.module - ); - const ids = this.getIds(moduleGraph); - if (reexportInfo) { - return { - exports: Array.from( - /** @type {TODO} */ (reexportInfo).exports, - name => ({ - name, - from, - export: ids.concat(name), - canMangle: !(name in EMPTY_OBJECT) && false - }) - ), - // TODO handle deep reexports - dependencies: [from.module] - }; - } - return { - exports: true, - from: ids.length === 0 ? from : undefined, - canMangle: false, - dependencies: [from.module] - }; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {RuntimeSpec} runtime the runtime - * @param {Module} importedModule the imported module (optional) - * @returns {{exports?: Set, checked?: Set} | undefined} information - */ - getStarReexports( - moduleGraph, - runtime, - importedModule = /** @type {Module} */ (moduleGraph.getModule(this)) - ) { - /** @type {ExportsInfo | undefined} */ - let importedExportsInfo = moduleGraph.getExportsInfo(importedModule); - const ids = this.getIds(moduleGraph); - if (ids.length > 0) - importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids); - /** @type {ExportsInfo | undefined} */ - let exportsInfo = moduleGraph.getExportsInfo( - /** @type {Module} */ (moduleGraph.getParentModule(this)) - ); - if (this.names.length > 0) - exportsInfo = exportsInfo.getNestedExportsInfo(this.names); - - const noExtraExports = - importedExportsInfo && - importedExportsInfo.otherExportsInfo.provided === false; - const noExtraImports = - exportsInfo && - exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused; - - if (!noExtraExports && !noExtraImports) { - return; - } - - const isNamespaceImport = - importedModule.getExportsType(moduleGraph, false) === "namespace"; - - /** @type {Set} */ - const exports = new Set(); - /** @type {Set} */ - const checked = new Set(); - - if (noExtraImports) { - for (const exportInfo of /** @type {ExportsInfo} */ (exportsInfo) - .orderedExports) { - const name = exportInfo.name; - if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; - if (name === "__esModule" && isNamespaceImport) { - exports.add(name); - } else if (importedExportsInfo) { - const importedExportInfo = - importedExportsInfo.getReadOnlyExportInfo(name); - if (importedExportInfo.provided === false) continue; - exports.add(name); - if (importedExportInfo.provided === true) continue; - checked.add(name); - } else { - exports.add(name); - checked.add(name); - } - } - } else if (noExtraExports) { - for (const importedExportInfo of /** @type {ExportsInfo} */ ( - importedExportsInfo - ).orderedExports) { - const name = importedExportInfo.name; - if (importedExportInfo.provided === false) continue; - if (exportsInfo) { - const exportInfo = exportsInfo.getReadOnlyExportInfo(name); - if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; - } - exports.add(name); - if (importedExportInfo.provided === true) continue; - checked.add(name); - } - if (isNamespaceImport) { - exports.add("__esModule"); - checked.delete("__esModule"); - } - } - - return { exports, checked }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.asiSafe); - write(this.range); - write(this.valueRange); - write(this.base); - write(this.names); - write(this.ids); - write(this.resultUsed); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.asiSafe = read(); - this.range = read(); - this.valueRange = read(); - this.base = read(); - this.names = read(); - this.ids = read(); - this.resultUsed = read(); - super.deserialize(context); - } -} - -makeSerializable( - CommonJsExportRequireDependency, - "webpack/lib/dependencies/CommonJsExportRequireDependency" -); - -CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, - { - module, - runtimeTemplate, - chunkGraph, - moduleGraph, - runtimeRequirements, - runtime - } - ) { - const dep = /** @type {CommonJsExportRequireDependency} */ (dependency); - const used = moduleGraph - .getExportsInfo(module) - .getUsedName(dep.names, runtime); - - const [type, base] = handleDependencyBase( - dep.base, - module, - runtimeRequirements - ); - - const importedModule = moduleGraph.getModule(dep); - let requireExpr = runtimeTemplate.moduleExports({ - module: importedModule, - chunkGraph, - request: dep.request, - weak: dep.weak, - runtimeRequirements - }); - if (importedModule) { - const ids = dep.getIds(moduleGraph); - const usedImported = moduleGraph - .getExportsInfo(importedModule) - .getUsedName(ids, runtime); - if (usedImported) { - const comment = equals(usedImported, ids) - ? "" - : `${Template.toNormalComment(propertyAccess(ids))} `; - requireExpr += `${comment}${propertyAccess(usedImported)}`; - } - } - - switch (type) { - case "expression": - source.replace( - dep.range[0], - dep.range[1] - 1, - used - ? `${base}${propertyAccess(used)} = ${requireExpr}` - : `/* unused reexport */ ${requireExpr}` - ); - return; - case "Object.defineProperty": - throw new Error("TODO"); - default: - throw new Error("Unexpected type"); - } - } -}; - -module.exports = CommonJsExportRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsExportsDependency.js b/webpack-lib/lib/dependencies/CommonJsExportsDependency.js deleted file mode 100644 index 93c831b5dfd..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsExportsDependency.js +++ /dev/null @@ -1,183 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const InitFragment = require("../InitFragment"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const { handleDependencyBase } = require("./CommonJsDependencyHelpers"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ - -const EMPTY_OBJECT = {}; - -class CommonJsExportsDependency extends NullDependency { - /** - * @param {Range} range range - * @param {Range | null} valueRange value range - * @param {CommonJSDependencyBaseKeywords} base base - * @param {string[]} names names - */ - constructor(range, valueRange, base, names) { - super(); - this.range = range; - this.valueRange = valueRange; - this.base = base; - this.names = names; - } - - get type() { - return "cjs exports"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - const name = this.names[0]; - return { - exports: [ - { - name, - // we can't mangle names that are in an empty object - // because one could access the prototype property - // when export isn't set yet - canMangle: !(name in EMPTY_OBJECT) - } - ], - dependencies: undefined - }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.valueRange); - write(this.base); - write(this.names); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.valueRange = read(); - this.base = read(); - this.names = read(); - super.deserialize(context); - } -} - -makeSerializable( - CommonJsExportsDependency, - "webpack/lib/dependencies/CommonJsExportsDependency" -); - -CommonJsExportsDependency.Template = class CommonJsExportsDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { module, moduleGraph, initFragments, runtimeRequirements, runtime } - ) { - const dep = /** @type {CommonJsExportsDependency} */ (dependency); - const used = moduleGraph - .getExportsInfo(module) - .getUsedName(dep.names, runtime); - - const [type, base] = handleDependencyBase( - dep.base, - module, - runtimeRequirements - ); - - switch (type) { - case "expression": - if (!used) { - initFragments.push( - new InitFragment( - "var __webpack_unused_export__;\n", - InitFragment.STAGE_CONSTANTS, - 0, - "__webpack_unused_export__" - ) - ); - source.replace( - dep.range[0], - dep.range[1] - 1, - "__webpack_unused_export__" - ); - return; - } - source.replace( - dep.range[0], - dep.range[1] - 1, - `${base}${propertyAccess(used)}` - ); - return; - case "Object.defineProperty": - if (!used) { - initFragments.push( - new InitFragment( - "var __webpack_unused_export__;\n", - InitFragment.STAGE_CONSTANTS, - 0, - "__webpack_unused_export__" - ) - ); - source.replace( - dep.range[0], - /** @type {Range} */ (dep.valueRange)[0] - 1, - "__webpack_unused_export__ = (" - ); - source.replace( - /** @type {Range} */ (dep.valueRange)[1], - dep.range[1] - 1, - ")" - ); - return; - } - source.replace( - dep.range[0], - /** @type {Range} */ (dep.valueRange)[0] - 1, - `Object.defineProperty(${base}${propertyAccess( - used.slice(0, -1) - )}, ${JSON.stringify(used[used.length - 1])}, (` - ); - source.replace( - /** @type {Range} */ (dep.valueRange)[1], - dep.range[1] - 1, - "))" - ); - } - } -}; - -module.exports = CommonJsExportsDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js b/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js deleted file mode 100644 index a37e0521288..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsExportsParserPlugin.js +++ /dev/null @@ -1,411 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const formatLocation = require("../formatLocation"); -const { evaluateToString } = require("../javascript/JavascriptParserHelpers"); -const propertyAccess = require("../util/propertyAccess"); -const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency"); -const CommonJsExportsDependency = require("./CommonJsExportsDependency"); -const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency"); -const DynamicExports = require("./DynamicExports"); -const HarmonyExports = require("./HarmonyExports"); -const ModuleDecoratorDependency = require("./ModuleDecoratorDependency"); - -/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").Super} Super */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../javascript/JavascriptParser").StatementPath} StatementPath */ -/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ - -/** - * This function takes a generic expression and detects whether it is an ObjectExpression. - * This is used in the context of parsing CommonJS exports to get the value of the property descriptor - * when the `exports` object is assigned to `Object.defineProperty`. - * - * In CommonJS modules, the `exports` object can be assigned to `Object.defineProperty` and therefore - * webpack has to detect this case and get the value key of the property descriptor. See the following example - * for more information: https://astexplorer.net/#/gist/83ce51a4e96e59d777df315a6d111da6/8058ead48a1bb53c097738225db0967ef7f70e57 - * - * This would be an example of a CommonJS module that exports an object with a property descriptor: - * ```js - * Object.defineProperty(exports, "__esModule", { value: true }); - * exports.foo = void 0; - * exports.foo = "bar"; - * ``` - * @param {TODO} expr expression - * @returns {Expression | undefined} returns the value of property descriptor - */ -const getValueOfPropertyDescription = expr => { - if (expr.type !== "ObjectExpression") return; - for (const property of expr.properties) { - if (property.computed) continue; - const key = property.key; - if (key.type !== "Identifier" || key.name !== "value") continue; - return property.value; - } -}; - -/** - * The purpose of this function is to check whether an expression is a truthy literal or not. This is - * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy - * values like `null` and `false`. However, exports should only be created if the exported value is truthy. - * @param {Expression} expr expression being checked - * @returns {boolean} true, when the expression is a truthy literal - */ -const isTruthyLiteral = expr => { - switch (expr.type) { - case "Literal": - return Boolean(expr.value); - case "UnaryExpression": - if (expr.operator === "!") return isFalsyLiteral(expr.argument); - } - return false; -}; - -/** - * The purpose of this function is to check whether an expression is a falsy literal or not. This is - * useful when parsing CommonJS exports, because CommonJS modules can export any value, including falsy - * values like `null` and `false`. However, exports should only be created if the exported value is truthy. - * @param {Expression} expr expression being checked - * @returns {boolean} true, when the expression is a falsy literal - */ -const isFalsyLiteral = expr => { - switch (expr.type) { - case "Literal": - return !expr.value; - case "UnaryExpression": - if (expr.operator === "!") return isTruthyLiteral(expr.argument); - } - return false; -}; - -/** - * @param {JavascriptParser} parser the parser - * @param {Expression} expr expression - * @returns {{ argument: BasicEvaluatedExpression, ids: string[] } | undefined} parsed call - */ -const parseRequireCall = (parser, expr) => { - const ids = []; - while (expr.type === "MemberExpression") { - if (expr.object.type === "Super") return; - if (!expr.property) return; - const prop = expr.property; - if (expr.computed) { - if (prop.type !== "Literal") return; - ids.push(`${prop.value}`); - } else { - if (prop.type !== "Identifier") return; - ids.push(prop.name); - } - expr = expr.object; - } - if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return; - const callee = expr.callee; - if ( - callee.type !== "Identifier" || - parser.getVariableInfo(callee.name) !== "require" - ) { - return; - } - const arg = expr.arguments[0]; - if (arg.type === "SpreadElement") return; - const argValue = parser.evaluateExpression(arg); - return { argument: argValue, ids: ids.reverse() }; -}; - -class CommonJsExportsParserPlugin { - /** - * @param {ModuleGraph} moduleGraph module graph - */ - constructor(moduleGraph) { - this.moduleGraph = moduleGraph; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - const enableStructuredExports = () => { - DynamicExports.enable(parser.state); - }; - - /** - * @param {boolean} topLevel true, when the export is on top level - * @param {string[]} members members of the export - * @param {Expression | undefined} valueExpr expression for the value - * @returns {void} - */ - const checkNamespace = (topLevel, members, valueExpr) => { - if (!DynamicExports.isEnabled(parser.state)) return; - if (members.length > 0 && members[0] === "__esModule") { - if (valueExpr && isTruthyLiteral(valueExpr) && topLevel) { - DynamicExports.setFlagged(parser.state); - } else { - DynamicExports.setDynamic(parser.state); - } - } - }; - /** - * @param {string=} reason reason - */ - const bailout = reason => { - DynamicExports.bailout(parser.state); - if (reason) bailoutHint(reason); - }; - /** - * @param {string} reason reason - */ - const bailoutHint = reason => { - this.moduleGraph - .getOptimizationBailout(parser.state.module) - .push(`CommonJS bailout: ${reason}`); - }; - - // metadata // - parser.hooks.evaluateTypeof - .for("module") - .tap("CommonJsExportsParserPlugin", evaluateToString("object")); - parser.hooks.evaluateTypeof - .for("exports") - .tap("CommonJsPlugin", evaluateToString("object")); - - // exporting // - - /** - * @param {AssignmentExpression} expr expression - * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords - * @param {string[]} members members of the export - * @returns {boolean | undefined} true, when the expression was handled - */ - const handleAssignExport = (expr, base, members) => { - if (HarmonyExports.isEnabled(parser.state)) return; - // Handle reexporting - const requireCall = parseRequireCall(parser, expr.right); - if ( - requireCall && - requireCall.argument.isString() && - (members.length === 0 || members[0] !== "__esModule") - ) { - enableStructuredExports(); - // It's possible to reexport __esModule, so we must convert to a dynamic module - if (members.length === 0) DynamicExports.setDynamic(parser.state); - const dep = new CommonJsExportRequireDependency( - /** @type {Range} */ (expr.range), - null, - base, - members, - /** @type {string} */ (requireCall.argument.string), - requireCall.ids, - !parser.isStatementLevelExpression(expr) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.module.addDependency(dep); - return true; - } - if (members.length === 0) return; - enableStructuredExports(); - const remainingMembers = members; - checkNamespace( - /** @type {StatementPath} */ - (parser.statementPath).length === 1 && - parser.isStatementLevelExpression(expr), - remainingMembers, - expr.right - ); - const dep = new CommonJsExportsDependency( - /** @type {Range} */ (expr.left.range), - null, - base, - remainingMembers - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - parser.walkExpression(expr.right); - return true; - }; - parser.hooks.assignMemberChain - .for("exports") - .tap("CommonJsExportsParserPlugin", (expr, members) => - handleAssignExport(expr, "exports", members) - ); - parser.hooks.assignMemberChain - .for("this") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (!parser.scope.topLevelScope) return; - return handleAssignExport(expr, "this", members); - }); - parser.hooks.assignMemberChain - .for("module") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (members[0] !== "exports") return; - return handleAssignExport(expr, "module.exports", members.slice(1)); - }); - parser.hooks.call - .for("Object.defineProperty") - .tap("CommonJsExportsParserPlugin", expression => { - const expr = /** @type {CallExpression} */ (expression); - if (!parser.isStatementLevelExpression(expr)) return; - if (expr.arguments.length !== 3) return; - if (expr.arguments[0].type === "SpreadElement") return; - if (expr.arguments[1].type === "SpreadElement") return; - if (expr.arguments[2].type === "SpreadElement") return; - const exportsArg = parser.evaluateExpression(expr.arguments[0]); - if (!exportsArg.isIdentifier()) return; - if ( - exportsArg.identifier !== "exports" && - exportsArg.identifier !== "module.exports" && - (exportsArg.identifier !== "this" || !parser.scope.topLevelScope) - ) { - return; - } - const propertyArg = parser.evaluateExpression(expr.arguments[1]); - const property = propertyArg.asString(); - if (typeof property !== "string") return; - enableStructuredExports(); - const descArg = expr.arguments[2]; - checkNamespace( - /** @type {StatementPath} */ - (parser.statementPath).length === 1, - [property], - getValueOfPropertyDescription(descArg) - ); - const dep = new CommonJsExportsDependency( - /** @type {Range} */ (expr.range), - /** @type {Range} */ (expr.arguments[2].range), - `Object.defineProperty(${exportsArg.identifier})`, - [property] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - - parser.walkExpression(expr.arguments[2]); - return true; - }); - - // Self reference // - - /** - * @param {Expression | Super} expr expression - * @param {CommonJSDependencyBaseKeywords} base commonjs base keywords - * @param {string[]} members members of the export - * @param {CallExpression=} call call expression - * @returns {boolean | void} true, when the expression was handled - */ - const handleAccessExport = (expr, base, members, call) => { - if (HarmonyExports.isEnabled(parser.state)) return; - if (members.length === 0) { - bailout( - `${base} is used directly at ${formatLocation( - /** @type {DependencyLocation} */ (expr.loc) - )}` - ); - } - if (call && members.length === 1) { - bailoutHint( - `${base}${propertyAccess( - members - )}(...) prevents optimization as ${base} is passed as call context at ${formatLocation( - /** @type {DependencyLocation} */ (expr.loc) - )}` - ); - } - const dep = new CommonJsSelfReferenceDependency( - /** @type {Range} */ (expr.range), - base, - members, - Boolean(call) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - if (call) { - parser.walkExpressions(call.arguments); - } - return true; - }; - parser.hooks.callMemberChain - .for("exports") - .tap("CommonJsExportsParserPlugin", (expr, members) => - handleAccessExport(expr.callee, "exports", members, expr) - ); - parser.hooks.expressionMemberChain - .for("exports") - .tap("CommonJsExportsParserPlugin", (expr, members) => - handleAccessExport(expr, "exports", members) - ); - parser.hooks.expression - .for("exports") - .tap("CommonJsExportsParserPlugin", expr => - handleAccessExport(expr, "exports", []) - ); - parser.hooks.callMemberChain - .for("module") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (members[0] !== "exports") return; - return handleAccessExport( - expr.callee, - "module.exports", - members.slice(1), - expr - ); - }); - parser.hooks.expressionMemberChain - .for("module") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (members[0] !== "exports") return; - return handleAccessExport(expr, "module.exports", members.slice(1)); - }); - parser.hooks.expression - .for("module.exports") - .tap("CommonJsExportsParserPlugin", expr => - handleAccessExport(expr, "module.exports", []) - ); - parser.hooks.callMemberChain - .for("this") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (!parser.scope.topLevelScope) return; - return handleAccessExport(expr.callee, "this", members, expr); - }); - parser.hooks.expressionMemberChain - .for("this") - .tap("CommonJsExportsParserPlugin", (expr, members) => { - if (!parser.scope.topLevelScope) return; - return handleAccessExport(expr, "this", members); - }); - parser.hooks.expression - .for("this") - .tap("CommonJsExportsParserPlugin", expr => { - if (!parser.scope.topLevelScope) return; - return handleAccessExport(expr, "this", []); - }); - - // Bailouts // - parser.hooks.expression.for("module").tap("CommonJsPlugin", expr => { - bailout(); - const isHarmony = HarmonyExports.isEnabled(parser.state); - const dep = new ModuleDecoratorDependency( - isHarmony - ? RuntimeGlobals.harmonyModuleDecorator - : RuntimeGlobals.nodeModuleDecorator, - !isHarmony - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - return true; - }); - } -} -module.exports = CommonJsExportsParserPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js deleted file mode 100644 index 1164eee150e..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsFullRequireDependency.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Template = require("../Template"); -const { equals } = require("../util/ArrayHelpers"); -const { getTrimmedIdsAndRange } = require("../util/chainedImports"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class CommonJsFullRequireDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - * @param {string[]} names accessed properties on module - * @param {Range[]=} idRanges ranges for members of ids; the two arrays are right-aligned - */ - constructor( - request, - range, - names, - idRanges /* TODO webpack 6 make this non-optional. It must always be set to properly trim ids. */ - ) { - super(request); - this.range = range; - this.names = names; - this.idRanges = idRanges; - this.call = false; - this.asiSafe = undefined; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - if (this.call) { - const importedModule = moduleGraph.getModule(this); - if ( - !importedModule || - importedModule.getExportsType(moduleGraph, false) !== "namespace" - ) { - return [this.names.slice(0, -1)]; - } - } - return [this.names]; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.names); - write(this.idRanges); - write(this.call); - write(this.asiSafe); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.names = read(); - this.idRanges = read(); - this.call = read(); - this.asiSafe = read(); - super.deserialize(context); - } - - get type() { - return "cjs full require"; - } - - get category() { - return "commonjs"; - } -} - -CommonJsFullRequireDependency.Template = class CommonJsFullRequireDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, - { - module, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtimeRequirements, - runtime, - initFragments - } - ) { - const dep = /** @type {CommonJsFullRequireDependency} */ (dependency); - if (!dep.range) return; - const importedModule = moduleGraph.getModule(dep); - let requireExpr = runtimeTemplate.moduleExports({ - module: importedModule, - chunkGraph, - request: dep.request, - weak: dep.weak, - runtimeRequirements - }); - - const { - trimmedRange: [trimmedRangeStart, trimmedRangeEnd], - trimmedIds - } = getTrimmedIdsAndRange( - dep.names, - dep.range, - dep.idRanges, - moduleGraph, - dep - ); - - if (importedModule) { - const usedImported = moduleGraph - .getExportsInfo(importedModule) - .getUsedName(trimmedIds, runtime); - if (usedImported) { - const comment = equals(usedImported, trimmedIds) - ? "" - : `${Template.toNormalComment(propertyAccess(trimmedIds))} `; - const access = `${comment}${propertyAccess(usedImported)}`; - requireExpr = - dep.asiSafe === true - ? `(${requireExpr}${access})` - : `${requireExpr}${access}`; - } - } - source.replace(trimmedRangeStart, trimmedRangeEnd - 1, requireExpr); - } -}; - -makeSerializable( - CommonJsFullRequireDependency, - "webpack/lib/dependencies/CommonJsFullRequireDependency" -); - -module.exports = CommonJsFullRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js b/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js deleted file mode 100644 index 15e87b90817..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsImportsParserPlugin.js +++ /dev/null @@ -1,783 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { fileURLToPath } = require("url"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const WebpackError = require("../WebpackError"); -const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); -const { - evaluateToIdentifier, - evaluateToString, - expressionIsUnsupported, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); -const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); -const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); -const CommonJsRequireDependency = require("./CommonJsRequireDependency"); -const ConstDependency = require("./ConstDependency"); -const ContextDependencyHelpers = require("./ContextDependencyHelpers"); -const LocalModuleDependency = require("./LocalModuleDependency"); -const { getLocalModule } = require("./LocalModulesHelpers"); -const RequireHeaderDependency = require("./RequireHeaderDependency"); -const RequireResolveContextDependency = require("./RequireResolveContextDependency"); -const RequireResolveDependency = require("./RequireResolveDependency"); -const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency"); - -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").NewExpression} NewExpression */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").ImportSource} ImportSource */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const createRequireSpecifierTag = Symbol("createRequire"); -const createdRequireIdentifierTag = Symbol("createRequire()"); - -class CommonJsImportsParserPlugin { - /** - * @param {JavascriptParserOptions} options parser options - */ - constructor(options) { - this.options = options; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - const options = this.options; - - const getContext = () => { - if (parser.currentTagData) { - const { context } = parser.currentTagData; - return context; - } - }; - - // #region metadata - /** - * @param {string} expression expression - * @param {() => string[]} getMembers get members - */ - const tapRequireExpression = (expression, getMembers) => { - parser.hooks.typeof - .for(expression) - .tap( - "CommonJsImportsParserPlugin", - toConstantDependency(parser, JSON.stringify("function")) - ); - parser.hooks.evaluateTypeof - .for(expression) - .tap("CommonJsImportsParserPlugin", evaluateToString("function")); - parser.hooks.evaluateIdentifier - .for(expression) - .tap( - "CommonJsImportsParserPlugin", - evaluateToIdentifier(expression, "require", getMembers, true) - ); - }; - /** - * @param {string | symbol} tag tag - */ - const tapRequireExpressionTag = tag => { - parser.hooks.typeof - .for(tag) - .tap( - "CommonJsImportsParserPlugin", - toConstantDependency(parser, JSON.stringify("function")) - ); - parser.hooks.evaluateTypeof - .for(tag) - .tap("CommonJsImportsParserPlugin", evaluateToString("function")); - }; - tapRequireExpression("require", () => []); - tapRequireExpression("require.resolve", () => ["resolve"]); - tapRequireExpression("require.resolveWeak", () => ["resolveWeak"]); - // #endregion - - // Weird stuff // - parser.hooks.assign - .for("require") - .tap("CommonJsImportsParserPlugin", expr => { - // to not leak to global "require", we need to define a local require here. - const dep = new ConstDependency("var require;", 0); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - - // #region Unsupported - parser.hooks.expression - .for("require.main") - .tap( - "CommonJsImportsParserPlugin", - expressionIsUnsupported( - parser, - "require.main is not supported by webpack." - ) - ); - parser.hooks.call - .for("require.main.require") - .tap( - "CommonJsImportsParserPlugin", - expressionIsUnsupported( - parser, - "require.main.require is not supported by webpack." - ) - ); - parser.hooks.expression - .for("module.parent.require") - .tap( - "CommonJsImportsParserPlugin", - expressionIsUnsupported( - parser, - "module.parent.require is not supported by webpack." - ) - ); - parser.hooks.call - .for("module.parent.require") - .tap( - "CommonJsImportsParserPlugin", - expressionIsUnsupported( - parser, - "module.parent.require is not supported by webpack." - ) - ); - // #endregion - - // #region Renaming - /** - * @param {Expression} expr expression - * @returns {boolean} true when set undefined - */ - const defineUndefined = expr => { - // To avoid "not defined" error, replace the value with undefined - const dep = new ConstDependency( - "undefined", - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return false; - }; - parser.hooks.canRename - .for("require") - .tap("CommonJsImportsParserPlugin", () => true); - parser.hooks.rename - .for("require") - .tap("CommonJsImportsParserPlugin", defineUndefined); - // #endregion - - // #region Inspection - const requireCache = toConstantDependency( - parser, - RuntimeGlobals.moduleCache, - [ - RuntimeGlobals.moduleCache, - RuntimeGlobals.moduleId, - RuntimeGlobals.moduleLoaded - ] - ); - - parser.hooks.expression - .for("require.cache") - .tap("CommonJsImportsParserPlugin", requireCache); - // #endregion - - // #region Require as expression - /** - * @param {Expression} expr expression - * @returns {boolean} true when handled - */ - const requireAsExpressionHandler = expr => { - const dep = new CommonJsRequireContextDependency( - { - request: options.unknownContextRequest, - recursive: options.unknownContextRecursive, - regExp: options.unknownContextRegExp, - mode: "sync" - }, - /** @type {Range} */ (expr.range), - undefined, - parser.scope.inShorthand, - getContext() - ); - dep.critical = - options.unknownContextCritical && - "require function is used in a way in which dependencies cannot be statically extracted"; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - }; - parser.hooks.expression - .for("require") - .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); - // #endregion - - // #region Require - /** - * @param {CallExpression | NewExpression} expr expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | void} true when handled - */ - const processRequireItem = (expr, param) => { - if (param.isString()) { - const dep = new CommonJsRequireDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (param.range), - getContext() - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - }; - /** - * @param {CallExpression | NewExpression} expr expression - * @param {BasicEvaluatedExpression} param param - * @returns {boolean | void} true when handled - */ - const processRequireContext = (expr, param) => { - const dep = ContextDependencyHelpers.create( - CommonJsRequireContextDependency, - /** @type {Range} */ (expr.range), - param, - expr, - options, - { - category: "commonjs" - }, - parser, - undefined, - getContext() - ); - if (!dep) return; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - }; - /** - * @param {boolean} callNew true, when require is called with new - * @returns {(expr: CallExpression | NewExpression) => (boolean | void)} handler - */ - const createRequireHandler = callNew => expr => { - if (options.commonjsMagicComments) { - const { options: requireOptions, errors: commentErrors } = - parser.parseCommentOptions(/** @type {Range} */ (expr.range)); - - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - parser.state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - /** @type {DependencyLocation} */ (comment.loc) - ) - ); - } - } - if (requireOptions && requireOptions.webpackIgnore !== undefined) { - if (typeof requireOptions.webpackIgnore !== "boolean") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${requireOptions.webpackIgnore}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else if (requireOptions.webpackIgnore) { - // Do not instrument `require()` if `webpackIgnore` is `true` - return true; - } - } - } - - if (expr.arguments.length !== 1) return; - let localModule; - const param = parser.evaluateExpression(expr.arguments[0]); - if (param.isConditional()) { - let isExpression = false; - for (const p of /** @type {BasicEvaluatedExpression[]} */ ( - param.options - )) { - const result = processRequireItem(expr, p); - if (result === undefined) { - isExpression = true; - } - } - if (!isExpression) { - const dep = new RequireHeaderDependency( - /** @type {Range} */ (expr.callee.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - } - if ( - param.isString() && - (localModule = getLocalModule( - parser.state, - /** @type {string} */ (param.string) - )) - ) { - localModule.flagUsed(); - const dep = new LocalModuleDependency( - localModule, - /** @type {Range} */ (expr.range), - callNew - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } else { - const result = processRequireItem(expr, param); - if (result === undefined) { - processRequireContext(expr, param); - } else { - const dep = new RequireHeaderDependency( - /** @type {Range} */ (expr.callee.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } - } - return true; - }; - parser.hooks.call - .for("require") - .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); - parser.hooks.new - .for("require") - .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); - parser.hooks.call - .for("module.require") - .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); - parser.hooks.new - .for("module.require") - .tap("CommonJsImportsParserPlugin", createRequireHandler(true)); - // #endregion - - // #region Require with property access - /** - * @param {Expression} expr expression - * @param {string[]} calleeMembers callee members - * @param {CallExpression} callExpr call expression - * @param {string[]} members members - * @param {Range[]} memberRanges member ranges - * @returns {boolean | void} true when handled - */ - const chainHandler = ( - expr, - calleeMembers, - callExpr, - members, - memberRanges - ) => { - if (callExpr.arguments.length !== 1) return; - const param = parser.evaluateExpression(callExpr.arguments[0]); - if ( - param.isString() && - !getLocalModule(parser.state, /** @type {string} */ (param.string)) - ) { - const dep = new CommonJsFullRequireDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.range), - members, - /** @type {Range[]} */ memberRanges - ); - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expr.range)[0] - ); - dep.optional = Boolean(parser.scope.inTry); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.current.addDependency(dep); - return true; - } - }; - /** - * @param {CallExpression} expr expression - * @param {string[]} calleeMembers callee members - * @param {CallExpression} callExpr call expression - * @param {string[]} members members - * @param {Range[]} memberRanges member ranges - * @returns {boolean | void} true when handled - */ - const callChainHandler = ( - expr, - calleeMembers, - callExpr, - members, - memberRanges - ) => { - if (callExpr.arguments.length !== 1) return; - const param = parser.evaluateExpression(callExpr.arguments[0]); - if ( - param.isString() && - !getLocalModule(parser.state, /** @type {string} */ (param.string)) - ) { - const dep = new CommonJsFullRequireDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.callee.range), - members, - /** @type {Range[]} */ memberRanges - ); - dep.call = true; - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expr.range)[0] - ); - dep.optional = Boolean(parser.scope.inTry); - dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc); - parser.state.current.addDependency(dep); - parser.walkExpressions(expr.arguments); - return true; - } - }; - parser.hooks.memberChainOfCallMemberChain - .for("require") - .tap("CommonJsImportsParserPlugin", chainHandler); - parser.hooks.memberChainOfCallMemberChain - .for("module.require") - .tap("CommonJsImportsParserPlugin", chainHandler); - parser.hooks.callMemberChainOfCallMemberChain - .for("require") - .tap("CommonJsImportsParserPlugin", callChainHandler); - parser.hooks.callMemberChainOfCallMemberChain - .for("module.require") - .tap("CommonJsImportsParserPlugin", callChainHandler); - // #endregion - - // #region Require.resolve - /** - * @param {CallExpression} expr call expression - * @param {boolean} weak weak - * @returns {boolean | void} true when handled - */ - const processResolve = (expr, weak) => { - if (expr.arguments.length !== 1) return; - const param = parser.evaluateExpression(expr.arguments[0]); - if (param.isConditional()) { - for (const option of /** @type {BasicEvaluatedExpression[]} */ ( - param.options - )) { - const result = processResolveItem(expr, option, weak); - if (result === undefined) { - processResolveContext(expr, option, weak); - } - } - const dep = new RequireResolveHeaderDependency( - /** @type {Range} */ (expr.callee.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - const result = processResolveItem(expr, param, weak); - if (result === undefined) { - processResolveContext(expr, param, weak); - } - const dep = new RequireResolveHeaderDependency( - /** @type {Range} */ (expr.callee.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }; - /** - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @param {boolean} weak weak - * @returns {boolean | void} true when handled - */ - const processResolveItem = (expr, param, weak) => { - if (param.isString()) { - const dep = new RequireResolveDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (param.range), - getContext() - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - dep.weak = weak; - parser.state.current.addDependency(dep); - return true; - } - }; - /** - * @param {CallExpression} expr call expression - * @param {BasicEvaluatedExpression} param param - * @param {boolean} weak weak - * @returns {boolean | void} true when handled - */ - const processResolveContext = (expr, param, weak) => { - const dep = ContextDependencyHelpers.create( - RequireResolveContextDependency, - /** @type {Range} */ (param.range), - param, - expr, - options, - { - category: "commonjs", - mode: weak ? "weak" : "sync" - }, - parser, - getContext() - ); - if (!dep) return; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - }; - - parser.hooks.call - .for("require.resolve") - .tap("CommonJsImportsParserPlugin", expr => processResolve(expr, false)); - parser.hooks.call - .for("require.resolveWeak") - .tap("CommonJsImportsParserPlugin", expr => processResolve(expr, true)); - // #endregion - - // #region Create require - - if (!options.createRequire) return; - - /** @type {ImportSource[]} */ - let moduleName = []; - /** @type {string | undefined} */ - let specifierName; - - if (options.createRequire === true) { - moduleName = ["module", "node:module"]; - specifierName = "createRequire"; - } else { - let moduleName; - const match = /^(.*) from (.*)$/.exec(options.createRequire); - if (match) { - [, specifierName, moduleName] = match; - } - if (!specifierName || !moduleName) { - const err = new WebpackError( - `Parsing javascript parser option "createRequire" failed, got ${JSON.stringify( - options.createRequire - )}` - ); - err.details = - 'Expected string in format "createRequire from module", where "createRequire" is specifier name and "module" name of the module'; - throw err; - } - } - - tapRequireExpressionTag(createdRequireIdentifierTag); - tapRequireExpressionTag(createRequireSpecifierTag); - parser.hooks.evaluateCallExpression - .for(createRequireSpecifierTag) - .tap("CommonJsImportsParserPlugin", expr => { - const context = parseCreateRequireArguments(expr); - if (context === undefined) return; - const ident = parser.evaluatedVariable({ - tag: createdRequireIdentifierTag, - data: { context }, - next: undefined - }); - - return new BasicEvaluatedExpression() - .setIdentifier(ident, ident, () => []) - .setSideEffects(false) - .setRange(/** @type {Range} */ (expr.range)); - }); - parser.hooks.unhandledExpressionMemberChain - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", (expr, members) => - expressionIsUnsupported( - parser, - `createRequire().${members.join(".")} is not supported by webpack.` - )(expr) - ); - parser.hooks.canRename - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", () => true); - parser.hooks.canRename - .for(createRequireSpecifierTag) - .tap("CommonJsImportsParserPlugin", () => true); - parser.hooks.rename - .for(createRequireSpecifierTag) - .tap("CommonJsImportsParserPlugin", defineUndefined); - parser.hooks.expression - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", requireAsExpressionHandler); - parser.hooks.call - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); - /** - * @param {CallExpression} expr call expression - * @returns {string | void} context - */ - const parseCreateRequireArguments = expr => { - const args = expr.arguments; - if (args.length !== 1) { - const err = new WebpackError( - "module.createRequire supports only one argument." - ); - err.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addWarning(err); - return; - } - const arg = args[0]; - const evaluated = parser.evaluateExpression(arg); - if (!evaluated.isString()) { - const err = new WebpackError( - "module.createRequire failed parsing argument." - ); - err.loc = /** @type {DependencyLocation} */ (arg.loc); - parser.state.module.addWarning(err); - return; - } - const ctx = /** @type {string} */ (evaluated.string).startsWith("file://") - ? fileURLToPath(/** @type {string} */ (evaluated.string)) - : /** @type {string} */ (evaluated.string); - // argument always should be a filename - return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\")); - }; - - parser.hooks.import.tap( - { - name: "CommonJsImportsParserPlugin", - stage: -10 - }, - (statement, source) => { - if ( - !moduleName.includes(source) || - statement.specifiers.length !== 1 || - statement.specifiers[0].type !== "ImportSpecifier" || - statement.specifiers[0].imported.type !== "Identifier" || - statement.specifiers[0].imported.name !== specifierName - ) - return; - // clear for 'import { createRequire as x } from "module"' - // if any other specifier was used import module - const clearDep = new ConstDependency( - parser.isAsiPosition(/** @type {Range} */ (statement.range)[0]) - ? ";" - : "", - /** @type {Range} */ (statement.range) - ); - clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); - parser.state.module.addPresentationalDependency(clearDep); - parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]); - return true; - } - ); - parser.hooks.importSpecifier.tap( - { - name: "CommonJsImportsParserPlugin", - stage: -10 - }, - (statement, source, id, name) => { - if (!moduleName.includes(source) || id !== specifierName) return; - parser.tagVariable(name, createRequireSpecifierTag); - return true; - } - ); - parser.hooks.preDeclarator.tap( - "CommonJsImportsParserPlugin", - declarator => { - if ( - declarator.id.type !== "Identifier" || - !declarator.init || - declarator.init.type !== "CallExpression" || - declarator.init.callee.type !== "Identifier" - ) - return; - const variableInfo = - /** @type {TODO} */ - (parser.getVariableInfo(declarator.init.callee.name)); - if ( - variableInfo && - variableInfo.tagInfo && - variableInfo.tagInfo.tag === createRequireSpecifierTag - ) { - const context = parseCreateRequireArguments(declarator.init); - if (context === undefined) return; - parser.tagVariable(declarator.id.name, createdRequireIdentifierTag, { - name: declarator.id.name, - context - }); - return true; - } - } - ); - - parser.hooks.memberChainOfCallMemberChain - .for(createRequireSpecifierTag) - .tap( - "CommonJsImportsParserPlugin", - (expr, calleeMembers, callExpr, members) => { - if ( - calleeMembers.length !== 0 || - members.length !== 1 || - members[0] !== "cache" - ) - return; - // createRequire().cache - const context = parseCreateRequireArguments(callExpr); - if (context === undefined) return; - return requireCache(expr); - } - ); - parser.hooks.callMemberChainOfCallMemberChain - .for(createRequireSpecifierTag) - .tap( - "CommonJsImportsParserPlugin", - (expr, calleeMembers, innerCallExpression, members) => { - if ( - calleeMembers.length !== 0 || - members.length !== 1 || - members[0] !== "resolve" - ) - return; - // createRequire().resolve() - return processResolve(expr, false); - } - ); - parser.hooks.expressionMemberChain - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", (expr, members) => { - // require.cache - if (members.length === 1 && members[0] === "cache") { - return requireCache(expr); - } - }); - parser.hooks.callMemberChain - .for(createdRequireIdentifierTag) - .tap("CommonJsImportsParserPlugin", (expr, members) => { - // require.resolve() - if (members.length === 1 && members[0] === "resolve") { - return processResolve(expr, false); - } - }); - parser.hooks.call - .for(createRequireSpecifierTag) - .tap("CommonJsImportsParserPlugin", expr => { - const clearDep = new ConstDependency( - "/* createRequire() */ undefined", - /** @type {Range} */ (expr.range) - ); - clearDep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(clearDep); - return true; - }); - // #endregion - } -} -module.exports = CommonJsImportsParserPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsPlugin.js b/webpack-lib/lib/dependencies/CommonJsPlugin.js deleted file mode 100644 index b148b17b0b9..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsPlugin.js +++ /dev/null @@ -1,305 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const SelfModuleFactory = require("../SelfModuleFactory"); -const Template = require("../Template"); -const CommonJsExportsDependency = require("./CommonJsExportsDependency"); -const CommonJsFullRequireDependency = require("./CommonJsFullRequireDependency"); -const CommonJsRequireContextDependency = require("./CommonJsRequireContextDependency"); -const CommonJsRequireDependency = require("./CommonJsRequireDependency"); -const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency"); -const ModuleDecoratorDependency = require("./ModuleDecoratorDependency"); -const RequireHeaderDependency = require("./RequireHeaderDependency"); -const RequireResolveContextDependency = require("./RequireResolveContextDependency"); -const RequireResolveDependency = require("./RequireResolveDependency"); -const RequireResolveHeaderDependency = require("./RequireResolveHeaderDependency"); -const RuntimeRequirementsDependency = require("./RuntimeRequirementsDependency"); - -const CommonJsExportsParserPlugin = require("./CommonJsExportsParserPlugin"); -const CommonJsImportsParserPlugin = require("./CommonJsImportsParserPlugin"); - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC -} = require("../ModuleTypeConstants"); -const { - evaluateToIdentifier, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); -const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "CommonJsPlugin"; - -class CommonJsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { contextModuleFactory, normalModuleFactory }) => { - compilation.dependencyFactories.set( - CommonJsRequireDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CommonJsRequireDependency, - new CommonJsRequireDependency.Template() - ); - - compilation.dependencyFactories.set( - CommonJsFullRequireDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CommonJsFullRequireDependency, - new CommonJsFullRequireDependency.Template() - ); - - compilation.dependencyFactories.set( - CommonJsRequireContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - CommonJsRequireContextDependency, - new CommonJsRequireContextDependency.Template() - ); - - compilation.dependencyFactories.set( - RequireResolveDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - RequireResolveDependency, - new RequireResolveDependency.Template() - ); - - compilation.dependencyFactories.set( - RequireResolveContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - RequireResolveContextDependency, - new RequireResolveContextDependency.Template() - ); - - compilation.dependencyTemplates.set( - RequireResolveHeaderDependency, - new RequireResolveHeaderDependency.Template() - ); - - compilation.dependencyTemplates.set( - RequireHeaderDependency, - new RequireHeaderDependency.Template() - ); - - compilation.dependencyTemplates.set( - CommonJsExportsDependency, - new CommonJsExportsDependency.Template() - ); - - compilation.dependencyFactories.set( - CommonJsExportRequireDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - CommonJsExportRequireDependency, - new CommonJsExportRequireDependency.Template() - ); - - const selfFactory = new SelfModuleFactory(compilation.moduleGraph); - - compilation.dependencyFactories.set( - CommonJsSelfReferenceDependency, - selfFactory - ); - compilation.dependencyTemplates.set( - CommonJsSelfReferenceDependency, - new CommonJsSelfReferenceDependency.Template() - ); - - compilation.dependencyFactories.set( - ModuleDecoratorDependency, - selfFactory - ); - compilation.dependencyTemplates.set( - ModuleDecoratorDependency, - new ModuleDecoratorDependency.Template() - ); - - compilation.hooks.runtimeRequirementInModule - .for(RuntimeGlobals.harmonyModuleDecorator) - .tap(PLUGIN_NAME, (module, set) => { - set.add(RuntimeGlobals.module); - set.add(RuntimeGlobals.requireScope); - }); - - compilation.hooks.runtimeRequirementInModule - .for(RuntimeGlobals.nodeModuleDecorator) - .tap(PLUGIN_NAME, (module, set) => { - set.add(RuntimeGlobals.module); - set.add(RuntimeGlobals.requireScope); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.harmonyModuleDecorator) - .tap(PLUGIN_NAME, (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new HarmonyModuleDecoratorRuntimeModule() - ); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.nodeModuleDecorator) - .tap(PLUGIN_NAME, (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new NodeModuleDecoratorRuntimeModule() - ); - }); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.commonjs !== undefined && !parserOptions.commonjs) - return; - parser.hooks.typeof - .for("module") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("object")) - ); - - parser.hooks.expression - .for("require.main") - .tap( - PLUGIN_NAME, - toConstantDependency( - parser, - `${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}]`, - [RuntimeGlobals.moduleCache, RuntimeGlobals.entryModuleId] - ) - ); - parser.hooks.expression - .for(RuntimeGlobals.moduleLoaded) - .tap(PLUGIN_NAME, expr => { - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).moduleConcatenationBailout = - RuntimeGlobals.moduleLoaded; - const dep = new RuntimeRequirementsDependency([ - RuntimeGlobals.moduleLoaded - ]); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - - parser.hooks.expression - .for(RuntimeGlobals.moduleId) - .tap(PLUGIN_NAME, expr => { - /** @type {BuildInfo} */ - (parser.state.module.buildInfo).moduleConcatenationBailout = - RuntimeGlobals.moduleId; - const dep = new RuntimeRequirementsDependency([ - RuntimeGlobals.moduleId - ]); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - - parser.hooks.evaluateIdentifier.for("module.hot").tap( - PLUGIN_NAME, - evaluateToIdentifier("module.hot", "module", () => ["hot"], null) - ); - - new CommonJsImportsParserPlugin(parserOptions).apply(parser); - new CommonJsExportsParserPlugin(compilation.moduleGraph).apply( - parser - ); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - } - ); - } -} - -class HarmonyModuleDecoratorRuntimeModule extends RuntimeModule { - constructor() { - super("harmony module decorator"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { runtimeTemplate } = /** @type {Compilation} */ (this.compilation); - return Template.asString([ - `${ - RuntimeGlobals.harmonyModuleDecorator - } = ${runtimeTemplate.basicFunction("module", [ - "module = Object.create(module);", - "if (!module.children) module.children = [];", - "Object.defineProperty(module, 'exports', {", - Template.indent([ - "enumerable: true,", - `set: ${runtimeTemplate.basicFunction("", [ - "throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id);" - ])}` - ]), - "});", - "return module;" - ])};` - ]); - } -} - -class NodeModuleDecoratorRuntimeModule extends RuntimeModule { - constructor() { - super("node module decorator"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { runtimeTemplate } = /** @type {Compilation} */ (this.compilation); - return Template.asString([ - `${RuntimeGlobals.nodeModuleDecorator} = ${runtimeTemplate.basicFunction( - "module", - [ - "module.paths = [];", - "if (!module.children) module.children = [];", - "return module;" - ] - )};` - ]); - } -} - -module.exports = CommonJsPlugin; diff --git a/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js b/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js deleted file mode 100644 index 14e64285d0d..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsRequireContextDependency.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); -const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class CommonJsRequireContextDependency extends ContextDependency { - /** - * @param {TODO} options options for the context module - * @param {Range} range location in source code - * @param {Range | undefined} valueRange location of the require call - * @param {boolean | string } inShorthand true or name - * @param {string} context context - */ - constructor(options, range, valueRange, inShorthand, context) { - super(options, context); - - this.range = range; - this.valueRange = valueRange; - // inShorthand must be serialized by subclasses that use it - this.inShorthand = inShorthand; - } - - get type() { - return "cjs require context"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.range); - write(this.valueRange); - write(this.inShorthand); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.range = read(); - this.valueRange = read(); - this.inShorthand = read(); - - super.deserialize(context); - } -} - -makeSerializable( - CommonJsRequireContextDependency, - "webpack/lib/dependencies/CommonJsRequireContextDependency" -); - -CommonJsRequireContextDependency.Template = - ContextDependencyTemplateAsRequireCall; - -module.exports = CommonJsRequireContextDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsRequireDependency.js b/webpack-lib/lib/dependencies/CommonJsRequireDependency.js deleted file mode 100644 index 09545a86e5e..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsRequireDependency.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class CommonJsRequireDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {Range=} range location in source code - * @param {string=} context request context - */ - constructor(request, range, context) { - super(request); - this.range = range; - this._context = context; - } - - get type() { - return "cjs require"; - } - - get category() { - return "commonjs"; - } -} - -CommonJsRequireDependency.Template = ModuleDependencyTemplateAsId; - -makeSerializable( - CommonJsRequireDependency, - "webpack/lib/dependencies/CommonJsRequireDependency" -); - -module.exports = CommonJsRequireDependency; diff --git a/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js b/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js deleted file mode 100644 index b1b368ead67..00000000000 --- a/webpack-lib/lib/dependencies/CommonJsSelfReferenceDependency.js +++ /dev/null @@ -1,155 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const { equals } = require("../util/ArrayHelpers"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./CommonJsDependencyHelpers").CommonJSDependencyBaseKeywords} CommonJSDependencyBaseKeywords */ - -class CommonJsSelfReferenceDependency extends NullDependency { - /** - * @param {Range} range range - * @param {CommonJSDependencyBaseKeywords} base base - * @param {string[]} names names - * @param {boolean} call is a call - */ - constructor(range, base, names, call) { - super(); - this.range = range; - this.base = base; - this.names = names; - this.call = call; - } - - get type() { - return "cjs self exports reference"; - } - - get category() { - return "self"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return "self"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return [this.call ? this.names.slice(0, -1) : this.names]; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.base); - write(this.names); - write(this.call); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.base = read(); - this.names = read(); - this.call = read(); - super.deserialize(context); - } -} - -makeSerializable( - CommonJsSelfReferenceDependency, - "webpack/lib/dependencies/CommonJsSelfReferenceDependency" -); - -CommonJsSelfReferenceDependency.Template = class CommonJsSelfReferenceDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { module, moduleGraph, runtime, runtimeRequirements } - ) { - const dep = /** @type {CommonJsSelfReferenceDependency} */ (dependency); - const used = - dep.names.length === 0 - ? dep.names - : moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime); - if (!used) { - throw new Error( - "Self-reference dependency has unused export name: This should not happen" - ); - } - - let base; - switch (dep.base) { - case "exports": - runtimeRequirements.add(RuntimeGlobals.exports); - base = module.exportsArgument; - break; - case "module.exports": - runtimeRequirements.add(RuntimeGlobals.module); - base = `${module.moduleArgument}.exports`; - break; - case "this": - runtimeRequirements.add(RuntimeGlobals.thisAsExports); - base = "this"; - break; - default: - throw new Error(`Unsupported base ${dep.base}`); - } - - if (base === dep.base && equals(used, dep.names)) { - // Nothing has to be changed - // We don't use a replacement for compat reasons - // for plugins that update `module._source` which they - // shouldn't do! - return; - } - - source.replace( - dep.range[0], - dep.range[1] - 1, - `${base}${propertyAccess(used)}` - ); - } -}; - -module.exports = CommonJsSelfReferenceDependency; diff --git a/webpack-lib/lib/dependencies/ConstDependency.js b/webpack-lib/lib/dependencies/ConstDependency.js deleted file mode 100644 index e41acef3acc..00000000000 --- a/webpack-lib/lib/dependencies/ConstDependency.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class ConstDependency extends NullDependency { - /** - * @param {string} expression the expression - * @param {number | Range} range the source range - * @param {(string[] | null)=} runtimeRequirements runtime requirements - */ - constructor(expression, range, runtimeRequirements) { - super(); - this.expression = expression; - this.range = range; - this.runtimeRequirements = runtimeRequirements - ? new Set(runtimeRequirements) - : null; - this._hashUpdate = undefined; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - let hashUpdate = `${this.range}|${this.expression}`; - if (this.runtimeRequirements) { - for (const item of this.runtimeRequirements) { - hashUpdate += "|"; - hashUpdate += item; - } - } - this._hashUpdate = hashUpdate; - } - hash.update(this._hashUpdate); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return false; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.expression); - write(this.range); - write(this.runtimeRequirements); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.expression = read(); - this.range = read(); - this.runtimeRequirements = read(); - super.deserialize(context); - } -} - -makeSerializable(ConstDependency, "webpack/lib/dependencies/ConstDependency"); - -ConstDependency.Template = class ConstDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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 dep = /** @type {ConstDependency} */ (dependency); - if (dep.runtimeRequirements) { - for (const req of dep.runtimeRequirements) { - templateContext.runtimeRequirements.add(req); - } - } - if (typeof dep.range === "number") { - source.insert(dep.range, dep.expression); - return; - } - - source.replace(dep.range[0], dep.range[1] - 1, dep.expression); - } -}; - -module.exports = ConstDependency; diff --git a/webpack-lib/lib/dependencies/ContextDependency.js b/webpack-lib/lib/dependencies/ContextDependency.js deleted file mode 100644 index e1d94b5ece7..00000000000 --- a/webpack-lib/lib/dependencies/ContextDependency.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const DependencyTemplate = require("../DependencyTemplate"); -const makeSerializable = require("../util/makeSerializable"); -const memoize = require("../util/memoize"); - -/** @typedef {import("../ContextModule").ContextOptions} ContextOptions */ -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -const getCriticalDependencyWarning = memoize(() => - require("./CriticalDependencyWarning") -); - -/** @typedef {ContextOptions & { request: string }} ContextDependencyOptions */ - -/** - * @param {RegExp | null | undefined} r regexp - * @returns {string} stringified regexp - */ -const regExpToString = r => (r ? String(r) : ""); - -class ContextDependency extends Dependency { - /** - * @param {ContextDependencyOptions} options options for the context module - * @param {string=} context request context - */ - constructor(options, context) { - super(); - - this.options = options; - this.userRequest = this.options && this.options.request; - /** @type {false | undefined | string} */ - this.critical = false; - this.hadGlobalOrStickyRegExp = false; - - if ( - this.options && - (this.options.regExp.global || this.options.regExp.sticky) - ) { - this.options = { ...this.options, regExp: null }; - this.hadGlobalOrStickyRegExp = true; - } - - this.request = undefined; - this.range = undefined; - this.valueRange = undefined; - /** @type {boolean | string | undefined} */ - this.inShorthand = undefined; - // TODO refactor this - this.replaces = undefined; - this._requestContext = context; - } - - /** - * @returns {string | undefined} a request context - */ - getContext() { - return this._requestContext; - } - - get category() { - return "commonjs"; - } - - /** - * @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 true; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return ( - `context${this._requestContext || ""}|ctx request${ - this.options.request - } ${this.options.recursive} ` + - `${regExpToString(this.options.regExp)} ${regExpToString( - this.options.include - )} ${regExpToString(this.options.exclude)} ` + - `${this.options.mode} ${this.options.chunkName} ` + - `${JSON.stringify(this.options.groupOptions)}` + - `${ - this.options.referencedExports - ? ` ${JSON.stringify(this.options.referencedExports)}` - : "" - }` - ); - } - - /** - * Returns warnings - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} warnings - */ - getWarnings(moduleGraph) { - let warnings = super.getWarnings(moduleGraph); - - if (this.critical) { - if (!warnings) warnings = []; - const CriticalDependencyWarning = getCriticalDependencyWarning(); - warnings.push(new CriticalDependencyWarning(this.critical)); - } - - if (this.hadGlobalOrStickyRegExp) { - if (!warnings) warnings = []; - const CriticalDependencyWarning = getCriticalDependencyWarning(); - warnings.push( - new CriticalDependencyWarning( - "Contexts can't use RegExps with the 'g' or 'y' flags." - ) - ); - } - - return warnings; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.options); - write(this.userRequest); - write(this.critical); - write(this.hadGlobalOrStickyRegExp); - write(this.request); - write(this._requestContext); - write(this.range); - write(this.valueRange); - write(this.prepend); - write(this.replaces); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.options = read(); - this.userRequest = read(); - this.critical = read(); - this.hadGlobalOrStickyRegExp = read(); - this.request = read(); - this._requestContext = read(); - this.range = read(); - this.valueRange = read(); - this.prepend = read(); - this.replaces = read(); - - super.deserialize(context); - } -} - -makeSerializable( - ContextDependency, - "webpack/lib/dependencies/ContextDependency" -); - -ContextDependency.Template = DependencyTemplate; - -module.exports = ContextDependency; diff --git a/webpack-lib/lib/dependencies/ContextDependencyHelpers.js b/webpack-lib/lib/dependencies/ContextDependencyHelpers.js deleted file mode 100644 index ed635328202..00000000000 --- a/webpack-lib/lib/dependencies/ContextDependencyHelpers.js +++ /dev/null @@ -1,262 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { parseResource } = require("../util/identifier"); - -/** @typedef {import("estree").Node} EsTreeNode */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").ModuleOptionsNormalized} ModuleOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("./ContextDependency")} ContextDependency */ -/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ - -/** - * Escapes regular expression metacharacters - * @param {string} str String to quote - * @returns {string} Escaped string - */ -const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); - -/** - * @param {string} prefix prefix - * @returns {{prefix: string, context: string}} result - */ -const splitContextFromPrefix = prefix => { - const idx = prefix.lastIndexOf("/"); - let context = "."; - if (idx >= 0) { - context = prefix.slice(0, idx); - prefix = `.${prefix.slice(idx)}`; - } - return { - context, - prefix - }; -}; - -/** @typedef {Partial>} PartialContextDependencyOptions */ -/** @typedef {{ new(options: ContextDependencyOptions, range: Range, valueRange: [number, number], ...args: any[]): ContextDependency }} ContextDependencyConstructor */ - -/** - * @param {ContextDependencyConstructor} Dep the Dependency class - * @param {Range} range source range - * @param {BasicEvaluatedExpression} param context param - * @param {EsTreeNode} expr expr - * @param {Pick} options options for context creation - * @param {PartialContextDependencyOptions} contextOptions options for the ContextModule - * @param {JavascriptParser} parser the parser - * @param {...any} depArgs depArgs - * @returns {ContextDependency} the created Dependency - */ -module.exports.create = ( - Dep, - range, - param, - expr, - options, - contextOptions, - parser, - ...depArgs -) => { - if (param.isTemplateString()) { - const quasis = /** @type {BasicEvaluatedExpression[]} */ (param.quasis); - const prefixRaw = /** @type {string} */ (quasis[0].string); - const postfixRaw = - /** @type {string} */ - (quasis.length > 1 ? quasis[quasis.length - 1].string : ""); - - const valueRange = /** @type {Range} */ (param.range); - const { context, prefix } = splitContextFromPrefix(prefixRaw); - const { - path: postfix, - query, - fragment - } = parseResource(postfixRaw, parser); - - // When there are more than two quasis, the generated RegExp can be more precise - // We join the quasis with the expression regexp - const innerQuasis = quasis.slice(1, -1); - const innerRegExp = - /** @type {RegExp} */ (options.wrappedContextRegExp).source + - innerQuasis - .map( - q => - quoteMeta(/** @type {string} */ (q.string)) + - /** @type {RegExp} */ (options.wrappedContextRegExp).source - ) - .join(""); - - // Example: `./context/pre${e}inner${e}inner2${e}post?query#frag` - // context: "./context" - // prefix: "./pre" - // innerQuasis: [BEE("inner"), BEE("inner2")] - // (BEE = BasicEvaluatedExpression) - // postfix: "post" - // query: "?query" - // fragment: "#frag" - // regExp: /^\.\/pre.*inner.*inner2.*post$/ - const regExp = new RegExp( - `^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$` - ); - const dep = new Dep( - { - request: context + query + fragment, - recursive: /** @type {boolean} */ (options.wrappedContextRecursive), - regExp, - mode: "sync", - ...contextOptions - }, - range, - valueRange, - ...depArgs - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - - /** @type {{ value: string, range: Range }[]} */ - const replaces = []; - const parts = /** @type {BasicEvaluatedExpression[]} */ (param.parts); - - for (const [i, part] of parts.entries()) { - if (i % 2 === 0) { - // Quasis or merged quasi - let range = /** @type {Range} */ (part.range); - let value = /** @type {string} */ (part.string); - if (param.templateStringKind === "cooked") { - value = JSON.stringify(value); - value = value.slice(1, -1); - } - if (i === 0) { - // prefix - value = prefix; - range = [ - /** @type {Range} */ (param.range)[0], - /** @type {Range} */ (part.range)[1] - ]; - value = - (param.templateStringKind === "cooked" ? "`" : "String.raw`") + - value; - } else if (i === parts.length - 1) { - // postfix - value = postfix; - range = [ - /** @type {Range} */ (part.range)[0], - /** @type {Range} */ (param.range)[1] - ]; - value = `${value}\``; - } else if ( - part.expression && - part.expression.type === "TemplateElement" && - part.expression.value.raw === value - ) { - // Shortcut when it's a single quasi and doesn't need to be replaced - continue; - } - replaces.push({ - range, - value - }); - } else { - // Expression - parser.walkExpression(part.expression); - } - } - - dep.replaces = replaces; - dep.critical = - options.wrappedContextCritical && - "a part of the request of a dependency is an expression"; - return dep; - } else if ( - param.isWrapped() && - ((param.prefix && param.prefix.isString()) || - (param.postfix && param.postfix.isString())) - ) { - const prefixRaw = - /** @type {string} */ - (param.prefix && param.prefix.isString() ? param.prefix.string : ""); - const postfixRaw = - /** @type {string} */ - (param.postfix && param.postfix.isString() ? param.postfix.string : ""); - const prefixRange = - param.prefix && param.prefix.isString() ? param.prefix.range : null; - const postfixRange = - param.postfix && param.postfix.isString() ? param.postfix.range : null; - const valueRange = /** @type {Range} */ (param.range); - const { context, prefix } = splitContextFromPrefix(prefixRaw); - const { - path: postfix, - query, - fragment - } = parseResource(postfixRaw, parser); - const regExp = new RegExp( - `^${quoteMeta(prefix)}${ - /** @type {RegExp} */ (options.wrappedContextRegExp).source - }${quoteMeta(postfix)}$` - ); - const dep = new Dep( - { - request: context + query + fragment, - recursive: /** @type {boolean} */ (options.wrappedContextRecursive), - regExp, - mode: "sync", - ...contextOptions - }, - range, - valueRange, - ...depArgs - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - const replaces = []; - if (prefixRange) { - replaces.push({ - range: prefixRange, - value: JSON.stringify(prefix) - }); - } - if (postfixRange) { - replaces.push({ - range: postfixRange, - value: JSON.stringify(postfix) - }); - } - dep.replaces = replaces; - dep.critical = - options.wrappedContextCritical && - "a part of the request of a dependency is an expression"; - - if (parser && param.wrappedInnerExpressions) { - for (const part of param.wrappedInnerExpressions) { - if (part.expression) parser.walkExpression(part.expression); - } - } - - return dep; - } - const dep = new Dep( - { - request: /** @type {string} */ (options.exprContextRequest), - recursive: /** @type {boolean} */ (options.exprContextRecursive), - regExp: /** @type {RegExp} */ (options.exprContextRegExp), - mode: "sync", - ...contextOptions - }, - range, - /** @type {Range} */ (param.range), - ...depArgs - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.critical = - options.exprContextCritical && - "the request of a dependency is an expression"; - - parser.walkExpression(param.expression); - - return dep; -}; diff --git a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js deleted file mode 100644 index b0eb8b2c318..00000000000 --- a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsId.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ContextDependency = require("./ContextDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ - -class ContextDependencyTemplateAsId extends ContextDependency.Template { - /** - * @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, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ContextDependency} */ (dependency); - const module = moduleGraph.getModule(dep); - const moduleExports = runtimeTemplate.moduleExports({ - module, - chunkGraph, - request: dep.request, - weak: dep.weak, - runtimeRequirements - }); - - if (module) { - if (dep.valueRange) { - if (Array.isArray(dep.replaces)) { - for (let i = 0; i < dep.replaces.length; i++) { - const rep = dep.replaces[i]; - source.replace(rep.range[0], rep.range[1] - 1, rep.value); - } - } - source.replace(dep.valueRange[1], dep.range[1] - 1, ")"); - source.replace( - dep.range[0], - dep.valueRange[0] - 1, - `${moduleExports}.resolve(` - ); - } else { - source.replace( - dep.range[0], - dep.range[1] - 1, - `${moduleExports}.resolve` - ); - } - } else { - source.replace(dep.range[0], dep.range[1] - 1, moduleExports); - } - } -} -module.exports = ContextDependencyTemplateAsId; diff --git a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js b/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js deleted file mode 100644 index 8907f9f55d8..00000000000 --- a/webpack-lib/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ContextDependency = require("./ContextDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ - -class ContextDependencyTemplateAsRequireCall extends ContextDependency.Template { - /** - * @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, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ContextDependency} */ (dependency); - let moduleExports = runtimeTemplate.moduleExports({ - module: moduleGraph.getModule(dep), - chunkGraph, - request: dep.request, - runtimeRequirements - }); - - if (dep.inShorthand) { - moduleExports = `${dep.inShorthand}: ${moduleExports}`; - } - if (moduleGraph.getModule(dep)) { - if (dep.valueRange) { - if (Array.isArray(dep.replaces)) { - for (let i = 0; i < dep.replaces.length; i++) { - const rep = dep.replaces[i]; - source.replace(rep.range[0], rep.range[1] - 1, rep.value); - } - } - source.replace(dep.valueRange[1], dep.range[1] - 1, ")"); - source.replace( - dep.range[0], - dep.valueRange[0] - 1, - `${moduleExports}(` - ); - } else { - source.replace(dep.range[0], dep.range[1] - 1, moduleExports); - } - } else { - source.replace(dep.range[0], dep.range[1] - 1, moduleExports); - } - } -} -module.exports = ContextDependencyTemplateAsRequireCall; diff --git a/webpack-lib/lib/dependencies/ContextElementDependency.js b/webpack-lib/lib/dependencies/ContextElementDependency.js deleted file mode 100644 index 43d06edded6..00000000000 --- a/webpack-lib/lib/dependencies/ContextElementDependency.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("../ContextModule")} ContextModule */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class ContextElementDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {string|undefined} userRequest user request - * @param {string | undefined} typePrefix type prefix - * @param {string} category category - * @param {(string[][] | null)=} referencedExports referenced exports - * @param {string=} context context - * @param {ImportAttributes=} attributes import assertions - */ - constructor( - request, - userRequest, - typePrefix, - category, - referencedExports, - context, - attributes - ) { - super(request); - this.referencedExports = referencedExports; - this._typePrefix = typePrefix; - this._category = category; - this._context = context || undefined; - - if (userRequest) { - this.userRequest = userRequest; - } - - this.assertions = attributes; - } - - get type() { - if (this._typePrefix) { - return `${this._typePrefix} context element`; - } - - return "context element"; - } - - get category() { - return this._category; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - if (!this.referencedExports) return Dependency.EXPORTS_OBJECT_REFERENCED; - const refs = []; - for (const referencedExport of this.referencedExports) { - if ( - this._typePrefix === "import()" && - referencedExport[0] === "default" - ) { - const selfModule = - /** @type {ContextModule} */ - (moduleGraph.getParentModule(this)); - const importedModule = - /** @type {Module} */ - (moduleGraph.getModule(this)); - const exportsType = importedModule.getExportsType( - moduleGraph, - selfModule.options.namespaceObject === "strict" - ); - if ( - exportsType === "default-only" || - exportsType === "default-with-named" - ) { - return Dependency.EXPORTS_OBJECT_REFERENCED; - } - } - refs.push({ - name: referencedExport, - canMangle: false - }); - } - return refs; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this._typePrefix); - write(this._category); - write(this.referencedExports); - write(this.assertions); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this._typePrefix = read(); - this._category = read(); - this.referencedExports = read(); - this.assertions = read(); - super.deserialize(context); - } -} - -makeSerializable( - ContextElementDependency, - "webpack/lib/dependencies/ContextElementDependency" -); - -module.exports = ContextElementDependency; diff --git a/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js b/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js deleted file mode 100644 index 9abb5c50cf4..00000000000 --- a/webpack-lib/lib/dependencies/CreateScriptUrlDependency.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class CreateScriptUrlDependency extends NullDependency { - /** - * @param {Range} range range - */ - constructor(range) { - super(); - this.range = range; - } - - get type() { - return "create script url"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - super.deserialize(context); - } -} - -CreateScriptUrlDependency.Template = class CreateScriptUrlDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { runtimeRequirements }) { - const dep = /** @type {CreateScriptUrlDependency} */ (dependency); - - runtimeRequirements.add(RuntimeGlobals.createScriptUrl); - - source.insert(dep.range[0], `${RuntimeGlobals.createScriptUrl}(`); - source.insert(dep.range[1], ")"); - } -}; - -makeSerializable( - CreateScriptUrlDependency, - "webpack/lib/dependencies/CreateScriptUrlDependency" -); - -module.exports = CreateScriptUrlDependency; diff --git a/webpack-lib/lib/dependencies/CriticalDependencyWarning.js b/webpack-lib/lib/dependencies/CriticalDependencyWarning.js deleted file mode 100644 index 3299150bd97..00000000000 --- a/webpack-lib/lib/dependencies/CriticalDependencyWarning.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); -const makeSerializable = require("../util/makeSerializable"); - -class CriticalDependencyWarning extends WebpackError { - /** - * @param {string} message message - */ - constructor(message) { - super(); - - this.name = "CriticalDependencyWarning"; - this.message = `Critical dependency: ${message}`; - } -} - -makeSerializable( - CriticalDependencyWarning, - "webpack/lib/dependencies/CriticalDependencyWarning" -); - -module.exports = CriticalDependencyWarning; diff --git a/webpack-lib/lib/dependencies/CssIcssExportDependency.js b/webpack-lib/lib/dependencies/CssIcssExportDependency.js deleted file mode 100644 index 1b43d897577..00000000000 --- a/webpack-lib/lib/dependencies/CssIcssExportDependency.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const { cssExportConvention } = require("../util/conventions"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ -/** @typedef {import("../CssModule")} CssModule */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../css/CssGenerator")} CssGenerator */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class CssIcssExportDependency extends NullDependency { - /** - * @param {string} name name - * @param {string} value value - */ - constructor(name, value) { - super(); - this.name = name; - this.value = value; - this._hashUpdate = undefined; - } - - get type() { - return "css :export"; - } - - /** - * @param {string} name export name - * @param {CssGeneratorExportsConvention} convention convention of the export name - * @returns {string[]} convention results - */ - getExportsConventionNames(name, convention) { - if (this._conventionNames) { - return this._conventionNames; - } - this._conventionNames = cssExportConvention(name, convention); - return this._conventionNames; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this)); - const convention = - /** @type {CssGenerator} */ - (module.generator).convention; - const names = this.getExportsConventionNames(this.name, convention); - return { - exports: names.map(name => ({ - name, - canMangle: true - })), - dependencies: undefined - }; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, { chunkGraph }) { - if (this._hashUpdate === undefined) { - const module = - /** @type {CssModule} */ - (chunkGraph.moduleGraph.getParentModule(this)); - const generator = - /** @type {CssGenerator} */ - (module.generator); - const names = this.getExportsConventionNames( - this.name, - generator.convention - ); - this._hashUpdate = JSON.stringify(names); - } - hash.update("exportsConvention"); - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.name); - write(this.value); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.name = read(); - this.value = read(); - super.deserialize(context); - } -} - -CssIcssExportDependency.Template = class CssIcssExportDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { cssData, module: m, runtime, moduleGraph }) { - const dep = /** @type {CssIcssExportDependency} */ (dependency); - const module = /** @type {CssModule} */ (m); - const convention = - /** @type {CssGenerator} */ - (module.generator).convention; - const names = dep.getExportsConventionNames(dep.name, convention); - const usedNames = - /** @type {string[]} */ - ( - names - .map(name => - moduleGraph.getExportInfo(module, name).getUsedName(name, runtime) - ) - .filter(Boolean) - ); - - for (const used of usedNames.concat(names)) { - cssData.exports.set(used, dep.value); - } - } -}; - -makeSerializable( - CssIcssExportDependency, - "webpack/lib/dependencies/CssIcssExportDependency" -); - -module.exports = CssIcssExportDependency; diff --git a/webpack-lib/lib/dependencies/CssIcssImportDependency.js b/webpack-lib/lib/dependencies/CssIcssImportDependency.js deleted file mode 100644 index 4206b6484c7..00000000000 --- a/webpack-lib/lib/dependencies/CssIcssImportDependency.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const CssIcssExportDependency = require("./CssIcssExportDependency"); -const CssLocalIdentifierDependency = require("./CssLocalIdentifierDependency"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class CssIcssImportDependency extends ModuleDependency { - /** - * Example of dependency: - * - *:import('./style.css') { IMPORTED_NAME: v-primary } - * @param {string} request request request path which needs resolving - * @param {string} exportName export name - * @param {[number, number]} range the range of dependency - */ - constructor(request, exportName, range) { - super(request); - this.range = range; - this.exportName = exportName; - } - - get type() { - return "css :import"; - } - - get category() { - return "css-import"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.exportName); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.exportName = read(); - super.deserialize(context); - } -} - -CssIcssImportDependency.Template = class CssIcssImportDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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 dep = /** @type {CssIcssImportDependency} */ (dependency); - const { range } = dep; - const module = templateContext.moduleGraph.getModule(dep); - let value; - - for (const item of module.dependencies) { - if ( - item instanceof CssLocalIdentifierDependency && - dep.exportName === item.name - ) { - value = CssLocalIdentifierDependency.Template.getIdentifier( - item, - dep.exportName, - { - ...templateContext, - module - } - ); - break; - } else if ( - item instanceof CssIcssExportDependency && - dep.exportName === item.name - ) { - value = item.value; - break; - } - } - - if (!value) { - throw new Error( - `Imported '${dep.exportName}' name from '${dep.request}' not found` - ); - } - - source.replace(range[0], range[1], value); - } -}; - -makeSerializable( - CssIcssImportDependency, - "webpack/lib/dependencies/CssIcssImportDependency" -); - -module.exports = CssIcssImportDependency; diff --git a/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js b/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js deleted file mode 100644 index 298e5d1cdc5..00000000000 --- a/webpack-lib/lib/dependencies/CssIcssSymbolDependency.js +++ /dev/null @@ -1,132 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../css/CssParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class CssIcssSymbolDependency extends NullDependency { - /** - * @param {string} name name - * @param {string} value value - * @param {Range} range range - */ - constructor(name, value, range) { - super(); - this.name = name; - this.value = value; - this.range = range; - this._hashUpdate = undefined; - } - - get type() { - return "css @value identifier"; - } - - get category() { - return "self"; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = `${this.range}${this.name}${this.value}`; - } - hash.update(this._hashUpdate); - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - return { - exports: [ - { - name: this.name, - canMangle: true - } - ], - dependencies: undefined - }; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return [[this.name]]; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.name); - write(this.value); - write(this.range); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.name = read(); - this.value = read(); - this.range = read(); - super.deserialize(context); - } -} - -CssIcssSymbolDependency.Template = class CssValueAtRuleDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { cssData }) { - const dep = /** @type {CssIcssSymbolDependency} */ (dependency); - - source.replace(dep.range[0], dep.range[1] - 1, dep.value); - - cssData.exports.set(dep.name, dep.value); - } -}; - -makeSerializable( - CssIcssSymbolDependency, - "webpack/lib/dependencies/CssIcssSymbolDependency" -); - -module.exports = CssIcssSymbolDependency; diff --git a/webpack-lib/lib/dependencies/CssImportDependency.js b/webpack-lib/lib/dependencies/CssImportDependency.js deleted file mode 100644 index b6a0772d2ba..00000000000 --- a/webpack-lib/lib/dependencies/CssImportDependency.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../css/CssParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class CssImportDependency extends ModuleDependency { - /** - * Example of dependency: - * \@import url("landscape.css") layer(forms) screen and (orientation: landscape) screen and (orientation: landscape); - * @param {string} request request - * @param {Range} range range of the argument - * @param {string | undefined} layer layer - * @param {string | undefined} supports list of supports conditions - * @param {string | undefined} media list of media conditions - */ - constructor(request, range, layer, supports, media) { - super(request); - this.range = range; - this.layer = layer; - this.supports = supports; - this.media = media; - } - - get type() { - return "css @import"; - } - - get category() { - return "css-import"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - let str = `context${this._context || ""}|module${this.request}`; - - if (this.layer) { - str += `|layer${this.layer}`; - } - - if (this.supports) { - str += `|supports${this.supports}`; - } - - if (this.media) { - str += `|media${this.media}`; - } - - return str; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.layer); - write(this.supports); - write(this.media); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.layer = read(); - this.supports = read(); - this.media = read(); - super.deserialize(context); - } -} - -CssImportDependency.Template = class CssImportDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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 dep = /** @type {CssImportDependency} */ (dependency); - - source.replace(dep.range[0], dep.range[1] - 1, ""); - } -}; - -makeSerializable( - CssImportDependency, - "webpack/lib/dependencies/CssImportDependency" -); - -module.exports = CssImportDependency; diff --git a/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js b/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js deleted file mode 100644 index 7f8ddbf3bef..00000000000 --- a/webpack-lib/lib/dependencies/CssLocalIdentifierDependency.js +++ /dev/null @@ -1,250 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const { cssExportConvention } = require("../util/conventions"); -const createHash = require("../util/createHash"); -const { makePathsRelative } = require("../util/identifier"); -const makeSerializable = require("../util/makeSerializable"); -const memoize = require("../util/memoize"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ -/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorLocalIdentName} CssGeneratorLocalIdentName */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CssModule")} CssModule */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../css/CssGenerator")} CssGenerator */ -/** @typedef {import("../css/CssParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/createHash").Algorithm} Algorithm */ - -const getCssParser = memoize(() => require("../css/CssParser")); - -/** - * @param {string} local css local - * @param {CssModule} module module - * @param {ChunkGraph} chunkGraph chunk graph - * @param {RuntimeTemplate} runtimeTemplate runtime template - * @returns {string} local ident - */ -const getLocalIdent = (local, module, chunkGraph, runtimeTemplate) => { - const localIdentName = - /** @type {CssGenerator} */ - (module.generator).localIdentName; - const relativeResourcePath = makePathsRelative( - /** @type {string} */ - (module.context), - module.matchResource || module.resource, - runtimeTemplate.compilation.compiler.root - ); - const { hashFunction, hashDigest, hashDigestLength, hashSalt, uniqueName } = - runtimeTemplate.outputOptions; - const hash = createHash(/** @type {Algorithm} */ (hashFunction)); - - if (hashSalt) { - hash.update(hashSalt); - } - - hash.update(relativeResourcePath); - - if (!/\[local\]/.test(localIdentName)) { - hash.update(local); - } - - const localIdentHash = - /** @type {string} */ - (hash.digest(hashDigest)).slice(0, hashDigestLength); - - return runtimeTemplate.compilation - .getPath(localIdentName, { - filename: relativeResourcePath, - hash: localIdentHash, - contentHash: localIdentHash, - chunkGraph, - module - }) - .replace(/\[local\]/g, local) - .replace(/\[uniqueName\]/g, /** @type {string} */ (uniqueName)) - .replace(/^((-?[0-9])|--)/, "_$1"); -}; - -class CssLocalIdentifierDependency extends NullDependency { - /** - * @param {string} name name - * @param {Range} range range - * @param {string=} prefix prefix - */ - constructor(name, range, prefix = "") { - super(); - this.name = name; - this.range = range; - this.prefix = prefix; - this._conventionNames = undefined; - this._hashUpdate = undefined; - } - - get type() { - return "css local identifier"; - } - - /** - * @param {string} name export name - * @param {CssGeneratorExportsConvention} convention convention of the export name - * @returns {string[]} convention results - */ - getExportsConventionNames(name, convention) { - if (this._conventionNames) { - return this._conventionNames; - } - this._conventionNames = cssExportConvention(this.name, convention); - return this._conventionNames; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - const module = /** @type {CssModule} */ (moduleGraph.getParentModule(this)); - const convention = - /** @type {CssGenerator} */ - (module.generator).convention; - const names = this.getExportsConventionNames(this.name, convention); - return { - exports: names.map(name => ({ - name, - canMangle: true - })), - dependencies: undefined - }; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, { chunkGraph }) { - if (this._hashUpdate === undefined) { - const module = - /** @type {CssModule} */ - (chunkGraph.moduleGraph.getParentModule(this)); - const generator = - /** @type {CssGenerator} */ - (module.generator); - const names = this.getExportsConventionNames( - this.name, - generator.convention - ); - this._hashUpdate = `exportsConvention|${JSON.stringify(names)}|localIdentName|${JSON.stringify(generator.localIdentName)}`; - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.name); - write(this.range); - write(this.prefix); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.name = read(); - this.range = read(); - this.prefix = read(); - super.deserialize(context); - } -} - -CssLocalIdentifierDependency.Template = class CssLocalIdentifierDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @param {Dependency} dependency the dependency for which the template should be applied - * @param {string} local local name - * @param {DependencyTemplateContext} templateContext the context object - * @returns {string} identifier - */ - static getIdentifier( - dependency, - local, - { module: m, chunkGraph, runtimeTemplate } - ) { - const dep = /** @type {CssLocalIdentifierDependency} */ (dependency); - const module = /** @type {CssModule} */ (m); - - return ( - dep.prefix + - getCssParser().escapeIdentifier( - getLocalIdent(local, module, chunkGraph, runtimeTemplate) - ) - ); - } - - /** - * @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 { module: m, moduleGraph, runtime, cssData } = templateContext; - const dep = /** @type {CssLocalIdentifierDependency} */ (dependency); - const module = /** @type {CssModule} */ (m); - const convention = - /** @type {CssGenerator} */ - (module.generator).convention; - const names = dep.getExportsConventionNames(dep.name, convention); - const usedNames = - /** @type {(string)[]} */ - ( - names - .map(name => - moduleGraph.getExportInfo(module, name).getUsedName(name, runtime) - ) - .filter(Boolean) - ); - const local = usedNames.length === 0 ? names[0] : usedNames[0]; - const identifier = CssLocalIdentifierDependencyTemplate.getIdentifier( - dep, - local, - templateContext - ); - - source.replace(dep.range[0], dep.range[1] - 1, identifier); - - for (const used of usedNames.concat(names)) { - cssData.exports.set(used, getCssParser().unescapeIdentifier(identifier)); - } - } -}; - -makeSerializable( - CssLocalIdentifierDependency, - "webpack/lib/dependencies/CssLocalIdentifierDependency" -); - -module.exports = CssLocalIdentifierDependency; diff --git a/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js b/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js deleted file mode 100644 index b63ff53a74e..00000000000 --- a/webpack-lib/lib/dependencies/CssSelfLocalIdentifierDependency.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); -const CssLocalIdentifierDependency = require("./CssLocalIdentifierDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../css/CssParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class CssSelfLocalIdentifierDependency extends CssLocalIdentifierDependency { - /** - * @param {string} name name - * @param {Range} range range - * @param {string=} prefix prefix - * @param {Set=} declaredSet set of declared names (will only be active when in declared set) - */ - constructor(name, range, prefix = "", declaredSet = undefined) { - super(name, range, prefix); - this.declaredSet = declaredSet; - } - - get type() { - return "css self local identifier"; - } - - get category() { - return "self"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return "self"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - if (this.declaredSet && !this.declaredSet.has(this.name)) return; - return super.getExports(moduleGraph); - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - if (this.declaredSet && !this.declaredSet.has(this.name)) - return Dependency.NO_EXPORTS_REFERENCED; - return [[this.name]]; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.declaredSet); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.declaredSet = read(); - super.deserialize(context); - } -} - -CssSelfLocalIdentifierDependency.Template = class CssSelfLocalIdentifierDependencyTemplate extends ( - CssLocalIdentifierDependency.Template -) { - /** - * @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 dep = /** @type {CssSelfLocalIdentifierDependency} */ (dependency); - if (dep.declaredSet && !dep.declaredSet.has(dep.name)) return; - super.apply(dependency, source, templateContext); - } -}; - -makeSerializable( - CssSelfLocalIdentifierDependency, - "webpack/lib/dependencies/CssSelfLocalIdentifierDependency" -); - -module.exports = CssSelfLocalIdentifierDependency; diff --git a/webpack-lib/lib/dependencies/CssUrlDependency.js b/webpack-lib/lib/dependencies/CssUrlDependency.js deleted file mode 100644 index a177a89df5d..00000000000 --- a/webpack-lib/lib/dependencies/CssUrlDependency.js +++ /dev/null @@ -1,195 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const RawDataUrlModule = require("../asset/RawDataUrlModule"); -const makeSerializable = require("../util/makeSerializable"); -const memoize = require("../util/memoize"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -const getIgnoredRawDataUrlModule = memoize( - () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)") -); - -class CssUrlDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {Range} range range of the argument - * @param {"string" | "url" | "src"} urlType dependency type e.g. url() or string - */ - constructor(request, range, urlType) { - super(request); - this.range = range; - this.urlType = urlType; - } - - get type() { - return "css url()"; - } - - get category() { - return "url"; - } - - /** - * @param {string} context context directory - * @returns {Module | null} a module - */ - createIgnoredModule(context) { - return getIgnoredRawDataUrlModule(); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.urlType); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.urlType = read(); - super.deserialize(context); - } -} - -/** - * @param {string} str string - * @returns {string} string in quotes if needed - */ -const cssEscapeString = str => { - let countWhiteOrBracket = 0; - let countQuotation = 0; - let countApostrophe = 0; - for (let i = 0; i < str.length; i++) { - const cc = str.charCodeAt(i); - switch (cc) { - case 9: // tab - case 10: // nl - case 32: // space - case 40: // ( - case 41: // ) - countWhiteOrBracket++; - break; - case 34: - countQuotation++; - break; - case 39: - countApostrophe++; - break; - } - } - if (countWhiteOrBracket < 2) { - return str.replace(/[\n\t ()'"\\]/g, m => `\\${m}`); - } else if (countQuotation <= countApostrophe) { - return `"${str.replace(/[\n"\\]/g, m => `\\${m}`)}"`; - } - return `'${str.replace(/[\n'\\]/g, m => `\\${m}`)}'`; -}; - -CssUrlDependency.Template = class CssUrlDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, - { moduleGraph, runtimeTemplate, codeGenerationResults } - ) { - const dep = /** @type {CssUrlDependency} */ (dependency); - const module = /** @type {Module} */ (moduleGraph.getModule(dep)); - - /** @type {string | undefined} */ - let newValue; - - switch (dep.urlType) { - case "string": - newValue = cssEscapeString( - this.assetUrl({ - module, - codeGenerationResults - }) - ); - break; - case "url": - newValue = `url(${cssEscapeString( - this.assetUrl({ - module, - codeGenerationResults - }) - )})`; - break; - case "src": - newValue = `src(${cssEscapeString( - this.assetUrl({ - module, - codeGenerationResults - }) - )})`; - break; - } - - source.replace( - dep.range[0], - dep.range[1] - 1, - /** @type {string} */ (newValue) - ); - } - - /** - * @param {object} options options object - * @param {Module} options.module the module - * @param {RuntimeSpec=} options.runtime runtime - * @param {CodeGenerationResults} options.codeGenerationResults the code generation results - * @returns {string} the url of the asset - */ - assetUrl({ runtime, module, codeGenerationResults }) { - if (!module) { - return "data:,"; - } - const codeGen = codeGenerationResults.get(module, runtime); - const data = - /** @type {NonNullable} */ - (codeGen.data); - if (!data) return "data:,"; - const url = data.get("url"); - if (!url || !url["css-url"]) return "data:,"; - return url["css-url"]; - } -}; - -makeSerializable(CssUrlDependency, "webpack/lib/dependencies/CssUrlDependency"); - -CssUrlDependency.PUBLIC_PATH_AUTO = "__WEBPACK_CSS_PUBLIC_PATH_AUTO__"; - -module.exports = CssUrlDependency; diff --git a/webpack-lib/lib/dependencies/DelegatedSourceDependency.js b/webpack-lib/lib/dependencies/DelegatedSourceDependency.js deleted file mode 100644 index 737f60e7727..00000000000 --- a/webpack-lib/lib/dependencies/DelegatedSourceDependency.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -class DelegatedSourceDependency extends ModuleDependency { - /** - * @param {string} request the request string - */ - constructor(request) { - super(request); - } - - get type() { - return "delegated source"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - DelegatedSourceDependency, - "webpack/lib/dependencies/DelegatedSourceDependency" -); - -module.exports = DelegatedSourceDependency; diff --git a/webpack-lib/lib/dependencies/DllEntryDependency.js b/webpack-lib/lib/dependencies/DllEntryDependency.js deleted file mode 100644 index 74697042150..00000000000 --- a/webpack-lib/lib/dependencies/DllEntryDependency.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./EntryDependency")} EntryDependency */ - -class DllEntryDependency extends Dependency { - /** - * @param {EntryDependency[]} dependencies dependencies - * @param {string} name name - */ - constructor(dependencies, name) { - super(); - - this.dependencies = dependencies; - this.name = name; - } - - get type() { - return "dll entry"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.dependencies); - write(this.name); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.dependencies = read(); - this.name = read(); - - super.deserialize(context); - } -} - -makeSerializable( - DllEntryDependency, - "webpack/lib/dependencies/DllEntryDependency" -); - -module.exports = DllEntryDependency; diff --git a/webpack-lib/lib/dependencies/DynamicExports.js b/webpack-lib/lib/dependencies/DynamicExports.js deleted file mode 100644 index cdc5e9c9820..00000000000 --- a/webpack-lib/lib/dependencies/DynamicExports.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ - -/** @type {WeakMap} */ -const parserStateExportsState = new WeakMap(); - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.bailout = parserState => { - const value = parserStateExportsState.get(parserState); - parserStateExportsState.set(parserState, false); - if (value === true) { - const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); - buildMeta.exportsType = undefined; - buildMeta.defaultObject = false; - } -}; - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.enable = parserState => { - const value = parserStateExportsState.get(parserState); - if (value === false) return; - parserStateExportsState.set(parserState, true); - if (value !== true) { - const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); - buildMeta.exportsType = "default"; - buildMeta.defaultObject = "redirect"; - } -}; - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.setFlagged = parserState => { - const value = parserStateExportsState.get(parserState); - if (value !== true) return; - const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); - if (buildMeta.exportsType === "dynamic") return; - buildMeta.exportsType = "flagged"; -}; - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.setDynamic = parserState => { - const value = parserStateExportsState.get(parserState); - if (value !== true) return; - /** @type {BuildMeta} */ - (parserState.module.buildMeta).exportsType = "dynamic"; -}; - -/** - * @param {ParserState} parserState parser state - * @returns {boolean} true, when enabled - */ -module.exports.isEnabled = parserState => { - const value = parserStateExportsState.get(parserState); - return value === true; -}; diff --git a/webpack-lib/lib/dependencies/EntryDependency.js b/webpack-lib/lib/dependencies/EntryDependency.js deleted file mode 100644 index f46444945b7..00000000000 --- a/webpack-lib/lib/dependencies/EntryDependency.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -class EntryDependency extends ModuleDependency { - /** - * @param {string} request request path for entry - */ - constructor(request) { - super(request); - } - - get type() { - return "entry"; - } - - get category() { - return "esm"; - } -} - -makeSerializable(EntryDependency, "webpack/lib/dependencies/EntryDependency"); - -module.exports = EntryDependency; diff --git a/webpack-lib/lib/dependencies/ExportsInfoDependency.js b/webpack-lib/lib/dependencies/ExportsInfoDependency.js deleted file mode 100644 index 70383e25d3a..00000000000 --- a/webpack-lib/lib/dependencies/ExportsInfoDependency.js +++ /dev/null @@ -1,158 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {Module} module the module - * @param {string[] | null} _exportName name of the export if any - * @param {string | null} property name of the requested property - * @param {RuntimeSpec} runtime for which runtime - * @returns {any} value of the property - */ -const getProperty = (moduleGraph, module, _exportName, property, runtime) => { - if (!_exportName) { - switch (property) { - case "usedExports": { - const usedExports = moduleGraph - .getExportsInfo(module) - .getUsedExports(runtime); - if ( - typeof usedExports === "boolean" || - usedExports === undefined || - usedExports === null - ) { - return usedExports; - } - return Array.from(usedExports).sort(); - } - } - } - const exportName = /** @type {string[]} */ (_exportName); - switch (property) { - case "canMangle": { - const exportsInfo = moduleGraph.getExportsInfo(module); - const exportInfo = exportsInfo.getReadOnlyExportInfoRecursive(exportName); - if (exportInfo) return exportInfo.canMangle; - return exportsInfo.otherExportsInfo.canMangle; - } - case "used": - return ( - moduleGraph.getExportsInfo(module).getUsed(exportName, runtime) !== - UsageState.Unused - ); - case "useInfo": { - const state = moduleGraph - .getExportsInfo(module) - .getUsed(exportName, runtime); - switch (state) { - case UsageState.Used: - case UsageState.OnlyPropertiesUsed: - return true; - case UsageState.Unused: - return false; - case UsageState.NoInfo: - return; - case UsageState.Unknown: - return null; - default: - throw new Error(`Unexpected UsageState ${state}`); - } - } - case "provideInfo": - return moduleGraph.getExportsInfo(module).isExportProvided(exportName); - } -}; - -class ExportsInfoDependency extends NullDependency { - /** - * @param {Range} range range - * @param {string[] | null} exportName export name - * @param {string | null} property property - */ - constructor(range, exportName, property) { - super(); - this.range = range; - this.exportName = exportName; - this.property = property; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.exportName); - write(this.property); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ExportsInfoDependency} ExportsInfoDependency - */ - static deserialize(context) { - const obj = new ExportsInfoDependency( - context.read(), - context.read(), - context.read() - ); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - ExportsInfoDependency, - "webpack/lib/dependencies/ExportsInfoDependency" -); - -ExportsInfoDependency.Template = class ExportsInfoDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { module, moduleGraph, runtime }) { - const dep = /** @type {ExportsInfoDependency} */ (dependency); - - const value = getProperty( - moduleGraph, - module, - dep.exportName, - dep.property, - runtime - ); - source.replace( - dep.range[0], - dep.range[1] - 1, - value === undefined ? "undefined" : JSON.stringify(value) - ); - } -}; - -module.exports = ExportsInfoDependency; diff --git a/webpack-lib/lib/dependencies/ExternalModuleDependency.js b/webpack-lib/lib/dependencies/ExternalModuleDependency.js deleted file mode 100644 index ce3e3785846..00000000000 --- a/webpack-lib/lib/dependencies/ExternalModuleDependency.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const CachedConstDependency = require("./CachedConstDependency"); -const ExternalModuleInitFragment = require("./ExternalModuleInitFragment"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class ExternalModuleDependency extends CachedConstDependency { - /** - * @param {string} module module - * @param {{ name: string, value: string }[]} importSpecifiers import specifiers - * @param {string | undefined} defaultImport default import - * @param {string} expression expression - * @param {Range} range range - * @param {string} identifier identifier - */ - constructor( - module, - importSpecifiers, - defaultImport, - expression, - range, - identifier - ) { - super(expression, range, identifier); - - this.importedModule = module; - this.specifiers = importSpecifiers; - this.default = defaultImport; - } - - /** - * @returns {string} hash update - */ - _createHashUpdate() { - return `${this.importedModule}${JSON.stringify(this.specifiers)}${ - this.default || "null" - }${super._createHashUpdate()}`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - super.serialize(context); - const { write } = context; - write(this.importedModule); - write(this.specifiers); - write(this.default); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - super.deserialize(context); - const { read } = context; - this.importedModule = read(); - this.specifiers = read(); - this.default = read(); - } -} - -makeSerializable( - ExternalModuleDependency, - "webpack/lib/dependencies/ExternalModuleDependency" -); - -ExternalModuleDependency.Template = class ExternalModuleDependencyTemplate extends ( - CachedConstDependency.Template -) { - /** - * @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) { - super.apply(dependency, source, templateContext); - const dep = /** @type {ExternalModuleDependency} */ (dependency); - const { chunkInitFragments, runtimeTemplate } = templateContext; - - chunkInitFragments.push( - new ExternalModuleInitFragment( - `${runtimeTemplate.supportNodePrefixForCoreModules() ? "node:" : ""}${ - dep.importedModule - }`, - dep.specifiers, - dep.default - ) - ); - } -}; - -module.exports = ExternalModuleDependency; diff --git a/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js b/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js deleted file mode 100644 index 41bbe538e14..00000000000 --- a/webpack-lib/lib/dependencies/ExternalModuleInitFragment.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const InitFragment = require("../InitFragment"); -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 {Map>} ImportSpecifiers */ - -/** - * @extends {InitFragment} - */ -class ExternalModuleInitFragment extends InitFragment { - /** - * @param {string} importedModule imported module - * @param {Array<{ name: string, value?: string }> | ImportSpecifiers} specifiers import specifiers - * @param {string=} defaultImport default import - */ - constructor(importedModule, specifiers, defaultImport) { - super( - undefined, - InitFragment.STAGE_CONSTANTS, - 0, - `external module imports|${importedModule}|${defaultImport || "null"}` - ); - this.importedModule = importedModule; - if (Array.isArray(specifiers)) { - /** @type {ImportSpecifiers} */ - this.specifiers = new Map(); - for (const { name, value } of specifiers) { - let specifiers = this.specifiers.get(name); - if (!specifiers) { - specifiers = new Set(); - this.specifiers.set(name, specifiers); - } - specifiers.add(value || name); - } - } else { - this.specifiers = specifiers; - } - this.defaultImport = defaultImport; - } - - /** - * @param {ExternalModuleInitFragment} other other - * @returns {ExternalModuleInitFragment} ExternalModuleInitFragment - */ - merge(other) { - const newSpecifiersMap = new Map(this.specifiers); - for (const [name, specifiers] of other.specifiers) { - if (newSpecifiersMap.has(name)) { - const currentSpecifiers = - /** @type {Set} */ - (newSpecifiersMap.get(name)); - for (const spec of specifiers) currentSpecifiers.add(spec); - } else { - newSpecifiersMap.set(name, specifiers); - } - } - return new ExternalModuleInitFragment( - this.importedModule, - newSpecifiersMap, - this.defaultImport - ); - } - - /** - * @param {GenerateContext} context context - * @returns {string | Source | undefined} the source code that will be included as initialization code - */ - getContent({ runtimeRequirements }) { - const namedImports = []; - - for (const [name, specifiers] of this.specifiers) { - for (const spec of specifiers) { - if (spec === name) { - namedImports.push(name); - } else { - namedImports.push(`${name} as ${spec}`); - } - } - } - - let importsString = - namedImports.length > 0 ? `{${namedImports.join(",")}}` : ""; - - if (this.defaultImport) { - importsString = `${this.defaultImport}${ - importsString ? `, ${importsString}` : "" - }`; - } - - return `import ${importsString} from ${JSON.stringify( - this.importedModule - )};`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - super.serialize(context); - const { write } = context; - write(this.importedModule); - write(this.specifiers); - write(this.defaultImport); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - super.deserialize(context); - const { read } = context; - this.importedModule = read(); - this.specifiers = read(); - this.defaultImport = read(); - } -} - -makeSerializable( - ExternalModuleInitFragment, - "webpack/lib/dependencies/ExternalModuleInitFragment" -); - -module.exports = ExternalModuleInitFragment; diff --git a/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js b/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js deleted file mode 100644 index 4817b722d7e..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyAcceptDependency.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Template = require("../Template"); -const makeSerializable = require("../util/makeSerializable"); -const HarmonyImportDependency = require("./HarmonyImportDependency"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./HarmonyAcceptImportDependency")} HarmonyAcceptImportDependency */ - -class HarmonyAcceptDependency extends NullDependency { - /** - * @param {Range} range expression range - * @param {HarmonyAcceptImportDependency[]} dependencies import dependencies - * @param {boolean} hasCallback true, if the range wraps an existing callback - */ - constructor(range, dependencies, hasCallback) { - super(); - this.range = range; - this.dependencies = dependencies; - this.hasCallback = hasCallback; - } - - get type() { - return "accepted harmony modules"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.dependencies); - write(this.hasCallback); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.dependencies = read(); - this.hasCallback = read(); - super.deserialize(context); - } -} - -makeSerializable( - HarmonyAcceptDependency, - "webpack/lib/dependencies/HarmonyAcceptDependency" -); - -HarmonyAcceptDependency.Template = class HarmonyAcceptDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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 dep = /** @type {HarmonyAcceptDependency} */ (dependency); - const { - module, - runtime, - runtimeRequirements, - runtimeTemplate, - moduleGraph, - chunkGraph - } = templateContext; - const content = dep.dependencies - .map(dependency => { - const referencedModule = moduleGraph.getModule(dependency); - return { - dependency, - runtimeCondition: referencedModule - ? HarmonyImportDependency.Template.getImportEmittedRuntime( - module, - referencedModule - ) - : false - }; - }) - .filter(({ runtimeCondition }) => runtimeCondition !== false) - .map(({ dependency, runtimeCondition }) => { - const condition = runtimeTemplate.runtimeConditionExpression({ - chunkGraph, - runtime, - runtimeCondition, - runtimeRequirements - }); - const s = dependency.getImportStatement(true, templateContext); - const code = s[0] + s[1]; - if (condition !== "true") { - return `if (${condition}) {\n${Template.indent(code)}\n}\n`; - } - return code; - }) - .join(""); - - if (dep.hasCallback) { - if (runtimeTemplate.supportsArrowFunction()) { - source.insert( - dep.range[0], - `__WEBPACK_OUTDATED_DEPENDENCIES__ => { ${content}(` - ); - source.insert(dep.range[1], ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }"); - } else { - source.insert( - dep.range[0], - `function(__WEBPACK_OUTDATED_DEPENDENCIES__) { ${content}(` - ); - source.insert( - dep.range[1], - ")(__WEBPACK_OUTDATED_DEPENDENCIES__); }.bind(this)" - ); - } - return; - } - - const arrow = runtimeTemplate.supportsArrowFunction(); - source.insert( - dep.range[1] - 0.5, - `, ${arrow ? "() =>" : "function()"} { ${content} }` - ); - } -}; - -module.exports = HarmonyAcceptDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js b/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js deleted file mode 100644 index 03cb0002ddc..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyAcceptImportDependency.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const HarmonyImportDependency = require("./HarmonyImportDependency"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ - -class HarmonyAcceptImportDependency extends HarmonyImportDependency { - /** - * @param {string} request the request string - */ - constructor(request) { - super(request, Number.NaN); - this.weak = true; - } - - get type() { - return "harmony accept"; - } -} - -makeSerializable( - HarmonyAcceptImportDependency, - "webpack/lib/dependencies/HarmonyAcceptImportDependency" -); - -HarmonyAcceptImportDependency.Template = - /** @type {typeof HarmonyImportDependency.Template} */ ( - NullDependency.Template - ); - -module.exports = HarmonyAcceptImportDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js b/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js deleted file mode 100644 index 5464f91b1f0..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyCompatibilityDependency.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); -const InitFragment = require("../InitFragment"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ - -class HarmonyCompatibilityDependency extends NullDependency { - get type() { - return "harmony export header"; - } -} - -makeSerializable( - HarmonyCompatibilityDependency, - "webpack/lib/dependencies/HarmonyCompatibilityDependency" -); - -HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { - module, - runtimeTemplate, - moduleGraph, - initFragments, - runtimeRequirements, - runtime, - concatenationScope - } - ) { - if (concatenationScope) return; - const exportsInfo = moduleGraph.getExportsInfo(module); - if ( - exportsInfo.getReadOnlyExportInfo("__esModule").getUsed(runtime) !== - UsageState.Unused - ) { - const content = runtimeTemplate.defineEsModuleFlagStatement({ - exportsArgument: module.exportsArgument, - runtimeRequirements - }); - initFragments.push( - new InitFragment( - content, - InitFragment.STAGE_HARMONY_EXPORTS, - 0, - "harmony compatibility" - ) - ); - } - if (moduleGraph.isAsync(module)) { - runtimeRequirements.add(RuntimeGlobals.module); - runtimeRequirements.add(RuntimeGlobals.asyncModule); - initFragments.push( - new InitFragment( - runtimeTemplate.supportsArrowFunction() - ? `${RuntimeGlobals.asyncModule}(${module.moduleArgument}, async (__webpack_handle_async_dependencies__, __webpack_async_result__) => { try {\n` - : `${RuntimeGlobals.asyncModule}(${module.moduleArgument}, async function (__webpack_handle_async_dependencies__, __webpack_async_result__) { try {\n`, - InitFragment.STAGE_ASYNC_BOUNDARY, - 0, - undefined, - `\n__webpack_async_result__();\n} catch(e) { __webpack_async_result__(e); } }${ - /** @type {BuildMeta} */ (module.buildMeta).async ? ", 1" : "" - });` - ) - ); - } - } -}; - -module.exports = HarmonyCompatibilityDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js deleted file mode 100644 index 4cf84fc1ec5..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyDetectionParserPlugin.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const EnvironmentNotSupportAsyncWarning = require("../EnvironmentNotSupportAsyncWarning"); -const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants"); -const DynamicExports = require("./DynamicExports"); -const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency"); -const HarmonyExports = require("./HarmonyExports"); - -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("./HarmonyModulesPlugin").HarmonyModulesPluginOptions} HarmonyModulesPluginOptions */ - -module.exports = class HarmonyDetectionParserPlugin { - /** - * @param {HarmonyModulesPluginOptions} options options - */ - constructor(options) { - const { topLevelAwait = false } = options || {}; - this.topLevelAwait = topLevelAwait; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.program.tap("HarmonyDetectionParserPlugin", ast => { - const isStrictHarmony = - parser.state.module.type === JAVASCRIPT_MODULE_TYPE_ESM; - const isHarmony = - isStrictHarmony || - ast.body.some( - statement => - statement.type === "ImportDeclaration" || - statement.type === "ExportDefaultDeclaration" || - statement.type === "ExportNamedDeclaration" || - statement.type === "ExportAllDeclaration" - ); - if (isHarmony) { - const module = parser.state.module; - const compatDep = new HarmonyCompatibilityDependency(); - compatDep.loc = { - start: { - line: -1, - column: 0 - }, - end: { - line: -1, - column: 0 - }, - index: -3 - }; - module.addPresentationalDependency(compatDep); - DynamicExports.bailout(parser.state); - HarmonyExports.enable(parser.state, isStrictHarmony); - parser.scope.isStrict = true; - } - }); - - parser.hooks.topLevelAwait.tap("HarmonyDetectionParserPlugin", () => { - const module = parser.state.module; - if (!this.topLevelAwait) { - throw new Error( - "The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enable it)" - ); - } - if (!HarmonyExports.isEnabled(parser.state)) { - throw new Error( - "Top-level-await is only supported in EcmaScript Modules" - ); - } - /** @type {BuildMeta} */ - (module.buildMeta).async = true; - EnvironmentNotSupportAsyncWarning.check( - module, - parser.state.compilation.runtimeTemplate, - "topLevelAwait" - ); - }); - - /** - * @returns {boolean | undefined} true if in harmony - */ - const skipInHarmony = () => { - if (HarmonyExports.isEnabled(parser.state)) { - return true; - } - }; - - /** - * @returns {null | undefined} null if in harmony - */ - const nullInHarmony = () => { - if (HarmonyExports.isEnabled(parser.state)) { - return null; - } - }; - - const nonHarmonyIdentifiers = ["define", "exports"]; - for (const identifier of nonHarmonyIdentifiers) { - parser.hooks.evaluateTypeof - .for(identifier) - .tap("HarmonyDetectionParserPlugin", nullInHarmony); - parser.hooks.typeof - .for(identifier) - .tap("HarmonyDetectionParserPlugin", skipInHarmony); - parser.hooks.evaluate - .for(identifier) - .tap("HarmonyDetectionParserPlugin", nullInHarmony); - parser.hooks.expression - .for(identifier) - .tap("HarmonyDetectionParserPlugin", skipInHarmony); - parser.hooks.call - .for(identifier) - .tap("HarmonyDetectionParserPlugin", skipInHarmony); - } - } -}; diff --git a/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js deleted file mode 100644 index 09ceb8e4aa0..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +++ /dev/null @@ -1,152 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** - * Dependency for static evaluating import specifier. e.g. - * @example - * import a from "a"; - * "x" in a; - * a.x !== undefined; // if x value statically analyzable - */ -class HarmonyEvaluatedImportSpecifierDependency extends HarmonyImportSpecifierDependency { - /** - * @param {string} request the request string - * @param {number} sourceOrder source order - * @param {TODO} ids ids - * @param {TODO} name name - * @param {Range} range location in source code - * @param {ImportAttributes} attributes import assertions - * @param {string} operator operator - */ - constructor(request, sourceOrder, ids, name, range, attributes, operator) { - super(request, sourceOrder, ids, name, range, false, attributes, []); - this.operator = operator; - } - - get type() { - return `evaluated X ${this.operator} harmony import specifier`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - super.serialize(context); - const { write } = context; - write(this.operator); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - super.deserialize(context); - const { read } = context; - this.operator = read(); - } -} - -makeSerializable( - HarmonyEvaluatedImportSpecifierDependency, - "webpack/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency" -); - -HarmonyEvaluatedImportSpecifierDependency.Template = class HarmonyEvaluatedImportSpecifierDependencyTemplate extends ( - HarmonyImportSpecifierDependency.Template -) { - /** - * @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 dep = /** @type {HarmonyEvaluatedImportSpecifierDependency} */ ( - dependency - ); - const { module, moduleGraph, runtime } = templateContext; - const connection = moduleGraph.getConnection(dep); - // Skip rendering depending when dependency is conditional - if (connection && !connection.isTargetActive(runtime)) return; - - const exportsInfo = moduleGraph.getExportsInfo( - /** @type {ModuleGraphConnection} */ (connection).module - ); - const ids = dep.getIds(moduleGraph); - - let value; - - const exportsType = - /** @type {ModuleGraphConnection} */ - (connection).module.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ - (module.buildMeta).strictHarmonyModule - ); - switch (exportsType) { - case "default-with-named": { - if (ids[0] === "default") { - value = - ids.length === 1 || exportsInfo.isExportProvided(ids.slice(1)); - } else { - value = exportsInfo.isExportProvided(ids); - } - break; - } - case "namespace": { - value = - ids[0] === "__esModule" - ? ids.length === 1 || undefined - : exportsInfo.isExportProvided(ids); - break; - } - case "dynamic": { - if (ids[0] !== "default") { - value = exportsInfo.isExportProvided(ids); - } - break; - } - // default-only could lead to runtime error, when default value is primitive - } - - if (typeof value === "boolean") { - source.replace(dep.range[0], dep.range[1] - 1, ` ${value}`); - } else { - const usedName = exportsInfo.getUsedName(ids, runtime); - - const code = this._getCodeForIds( - dep, - source, - templateContext, - ids.slice(0, -1) - ); - source.replace( - dep.range[0], - dep.range[1] - 1, - `${ - usedName ? JSON.stringify(usedName[usedName.length - 1]) : '""' - } in ${code}` - ); - } - } -}; - -module.exports = HarmonyEvaluatedImportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js deleted file mode 100644 index 247d0c155ac..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportDependencyParserPlugin.js +++ /dev/null @@ -1,221 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { getImportAttributes } = require("../javascript/JavascriptParser"); -const InnerGraph = require("../optimize/InnerGraph"); -const ConstDependency = require("./ConstDependency"); -const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency"); -const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency"); -const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency"); -const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency"); -const { ExportPresenceModes } = require("./HarmonyImportDependency"); -const { - harmonySpecifierTag -} = require("./HarmonyImportDependencyParserPlugin"); -const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); - -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").FunctionDeclaration} FunctionDeclaration */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const { HarmonyStarExportsList } = HarmonyExportImportedSpecifierDependency; - -module.exports = class HarmonyExportDependencyParserPlugin { - /** - * @param {import("../../declarations/WebpackOptions").JavascriptParserOptions} options options - */ - constructor(options) { - this.exportPresenceMode = - options.reexportExportsPresence !== undefined - ? ExportPresenceModes.fromUserOption(options.reexportExportsPresence) - : options.exportsPresence !== undefined - ? ExportPresenceModes.fromUserOption(options.exportsPresence) - : options.strictExportPresence - ? ExportPresenceModes.ERROR - : ExportPresenceModes.AUTO; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - const { exportPresenceMode } = this; - parser.hooks.export.tap( - "HarmonyExportDependencyParserPlugin", - statement => { - const dep = new HarmonyExportHeaderDependency( - /** @type {Range | false} */ ( - statement.declaration && statement.declaration.range - ), - /** @type {Range} */ (statement.range) - ); - dep.loc = Object.create( - /** @type {DependencyLocation} */ (statement.loc) - ); - dep.loc.index = -1; - parser.state.module.addPresentationalDependency(dep); - return true; - } - ); - parser.hooks.exportImport.tap( - "HarmonyExportDependencyParserPlugin", - (statement, source) => { - parser.state.lastHarmonyImportOrder = - (parser.state.lastHarmonyImportOrder || 0) + 1; - const clearDep = new ConstDependency( - "", - /** @type {Range} */ (statement.range) - ); - clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); - clearDep.loc.index = -1; - parser.state.module.addPresentationalDependency(clearDep); - const sideEffectDep = new HarmonyImportSideEffectDependency( - /** @type {string} */ (source), - parser.state.lastHarmonyImportOrder, - getImportAttributes(statement) - ); - sideEffectDep.loc = Object.create( - /** @type {DependencyLocation} */ (statement.loc) - ); - sideEffectDep.loc.index = -1; - parser.state.current.addDependency(sideEffectDep); - return true; - } - ); - parser.hooks.exportExpression.tap( - "HarmonyExportDependencyParserPlugin", - (statement, expr) => { - const isFunctionDeclaration = expr.type === "FunctionDeclaration"; - const exprRange = /** @type {Range} */ (expr.range); - const statementRange = /** @type {Range} */ (statement.range); - const comments = parser.getComments([statementRange[0], exprRange[0]]); - const dep = new HarmonyExportExpressionDependency( - exprRange, - statementRange, - comments - .map(c => { - switch (c.type) { - case "Block": - return `/*${c.value}*/`; - case "Line": - return `//${c.value}\n`; - } - return ""; - }) - .join(""), - expr.type.endsWith("Declaration") && expr.id - ? expr.id.name - : isFunctionDeclaration - ? { - range: [ - exprRange[0], - expr.params.length > 0 - ? /** @type {Range} */ (expr.params[0].range)[0] - : /** @type {Range} */ (expr.body.range)[0] - ], - prefix: `${expr.async ? "async " : ""}function${ - expr.generator ? "*" : "" - } `, - suffix: `(${expr.params.length > 0 ? "" : ") "}` - } - : undefined - ); - dep.loc = Object.create( - /** @type {DependencyLocation} */ (statement.loc) - ); - dep.loc.index = -1; - parser.state.current.addDependency(dep); - InnerGraph.addVariableUsage( - parser, - expr.type.endsWith("Declaration") && expr.id - ? expr.id.name - : "*default*", - "default" - ); - return true; - } - ); - parser.hooks.exportSpecifier.tap( - "HarmonyExportDependencyParserPlugin", - (statement, id, name, idx) => { - const settings = parser.getTagData(id, harmonySpecifierTag); - const harmonyNamedExports = (parser.state.harmonyNamedExports = - parser.state.harmonyNamedExports || new Set()); - harmonyNamedExports.add(name); - InnerGraph.addVariableUsage(parser, id, name); - const dep = settings - ? new HarmonyExportImportedSpecifierDependency( - settings.source, - settings.sourceOrder, - settings.ids, - name, - harmonyNamedExports, - null, - exportPresenceMode, - null, - settings.assertions - ) - : new HarmonyExportSpecifierDependency(id, name); - dep.loc = Object.create( - /** @type {DependencyLocation} */ (statement.loc) - ); - dep.loc.index = idx; - const isAsiSafe = !parser.isAsiPosition( - /** @type {Range} */ - (statement.range)[0] - ); - if (!isAsiSafe) { - parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]); - } - parser.state.current.addDependency(dep); - return true; - } - ); - parser.hooks.exportImportSpecifier.tap( - "HarmonyExportDependencyParserPlugin", - (statement, source, id, name, idx) => { - const harmonyNamedExports = (parser.state.harmonyNamedExports = - parser.state.harmonyNamedExports || new Set()); - let harmonyStarExports = null; - if (name) { - harmonyNamedExports.add(name); - } else { - harmonyStarExports = parser.state.harmonyStarExports = - parser.state.harmonyStarExports || new HarmonyStarExportsList(); - } - const dep = new HarmonyExportImportedSpecifierDependency( - /** @type {string} */ (source), - parser.state.lastHarmonyImportOrder, - id ? [id] : [], - name, - harmonyNamedExports, - harmonyStarExports && harmonyStarExports.slice(), - exportPresenceMode, - harmonyStarExports - ); - if (harmonyStarExports) { - harmonyStarExports.push(dep); - } - dep.loc = Object.create( - /** @type {DependencyLocation} */ (statement.loc) - ); - dep.loc.index = idx; - const isAsiSafe = !parser.isAsiPosition( - /** @type {Range} */ - (statement.range)[0] - ); - if (!isAsiSafe) { - parser.setAsiPosition(/** @type {Range} */ (statement.range)[1]); - } - parser.state.current.addDependency(dep); - return true; - } - ); - } -}; diff --git a/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js b/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js deleted file mode 100644 index 12481cf963c..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportExpressionDependency.js +++ /dev/null @@ -1,207 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ConcatenationScope = require("../ConcatenationScope"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class HarmonyExportExpressionDependency extends NullDependency { - /** - * @param {Range} range range - * @param {Range} rangeStatement range statement - * @param {string} prefix prefix - * @param {string | { range: Range, prefix: string, suffix: string }} [declarationId] declaration id - */ - constructor(range, rangeStatement, prefix, declarationId) { - super(); - this.range = range; - this.rangeStatement = rangeStatement; - this.prefix = prefix; - this.declarationId = declarationId; - } - - get type() { - return "harmony export expression"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - return { - exports: ["default"], - priority: 1, - terminalBinding: true, - dependencies: undefined - }; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - // The expression/declaration is already covered by SideEffectsFlagPlugin - return false; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.rangeStatement); - write(this.prefix); - write(this.declarationId); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.rangeStatement = read(); - this.prefix = read(); - this.declarationId = read(); - super.deserialize(context); - } -} - -makeSerializable( - HarmonyExportExpressionDependency, - "webpack/lib/dependencies/HarmonyExportExpressionDependency" -); - -HarmonyExportExpressionDependency.Template = class HarmonyExportDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { - module, - moduleGraph, - runtimeTemplate, - runtimeRequirements, - initFragments, - runtime, - concatenationScope - } - ) { - const dep = /** @type {HarmonyExportExpressionDependency} */ (dependency); - const { declarationId } = dep; - const exportsName = module.exportsArgument; - if (declarationId) { - let name; - if (typeof declarationId === "string") { - name = declarationId; - } else { - name = ConcatenationScope.DEFAULT_EXPORT; - source.replace( - declarationId.range[0], - declarationId.range[1] - 1, - `${declarationId.prefix}${name}${declarationId.suffix}` - ); - } - - if (concatenationScope) { - concatenationScope.registerExport("default", name); - } else { - const used = moduleGraph - .getExportsInfo(module) - .getUsedName("default", runtime); - if (used) { - const map = new Map(); - map.set(used, `/* export default binding */ ${name}`); - initFragments.push(new HarmonyExportInitFragment(exportsName, map)); - } - } - - source.replace( - dep.rangeStatement[0], - dep.range[0] - 1, - `/* harmony default export */ ${dep.prefix}` - ); - } else { - /** @type {string} */ - let content; - const name = ConcatenationScope.DEFAULT_EXPORT; - if (runtimeTemplate.supportsConst()) { - content = `/* harmony default export */ const ${name} = `; - if (concatenationScope) { - concatenationScope.registerExport("default", name); - } else { - const used = moduleGraph - .getExportsInfo(module) - .getUsedName("default", runtime); - if (used) { - runtimeRequirements.add(RuntimeGlobals.exports); - const map = new Map(); - map.set(used, name); - initFragments.push(new HarmonyExportInitFragment(exportsName, map)); - } else { - content = `/* unused harmony default export */ var ${name} = `; - } - } - } else if (concatenationScope) { - content = `/* harmony default export */ var ${name} = `; - concatenationScope.registerExport("default", name); - } else { - const used = moduleGraph - .getExportsInfo(module) - .getUsedName("default", runtime); - if (used) { - runtimeRequirements.add(RuntimeGlobals.exports); - // This is a little bit incorrect as TDZ is not correct, but we can't use const. - content = `/* harmony default export */ ${exportsName}${propertyAccess( - typeof used === "string" ? [used] : used - )} = `; - } else { - content = `/* unused harmony default export */ var ${name} = `; - } - } - - if (dep.range) { - source.replace( - dep.rangeStatement[0], - dep.range[0] - 1, - `${content}(${dep.prefix}` - ); - source.replace(dep.range[1], dep.rangeStatement[1] - 0.5, ");"); - return; - } - - source.replace(dep.rangeStatement[0], dep.rangeStatement[1] - 1, content); - } - } -}; - -module.exports = HarmonyExportExpressionDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js b/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js deleted file mode 100644 index ae649796686..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportHeaderDependency.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class HarmonyExportHeaderDependency extends NullDependency { - /** - * @param {Range | false} range range - * @param {Range} rangeStatement range statement - */ - constructor(range, rangeStatement) { - super(); - this.range = range; - this.rangeStatement = rangeStatement; - } - - get type() { - return "harmony export header"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.rangeStatement); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.rangeStatement = read(); - super.deserialize(context); - } -} - -makeSerializable( - HarmonyExportHeaderDependency, - "webpack/lib/dependencies/HarmonyExportHeaderDependency" -); - -HarmonyExportHeaderDependency.Template = class HarmonyExportDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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 dep = /** @type {HarmonyExportHeaderDependency} */ (dependency); - const content = ""; - const replaceUntil = dep.range - ? dep.range[0] - 1 - : dep.rangeStatement[1] - 1; - source.replace(dep.rangeStatement[0], replaceUntil, content); - } -}; - -module.exports = HarmonyExportHeaderDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js deleted file mode 100644 index 95d4507e273..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +++ /dev/null @@ -1,1396 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ConditionalInitFragment = require("../ConditionalInitFragment"); -const Dependency = require("../Dependency"); -const { UsageState } = require("../ExportsInfo"); -const HarmonyLinkingError = require("../HarmonyLinkingError"); -const InitFragment = require("../InitFragment"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const { countIterable } = require("../util/IterableHelpers"); -const { first, combine } = require("../util/SetHelpers"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const { propertyName } = require("../util/propertyName"); -const { - getRuntimeKey, - keyToRuntime, - filterRuntime -} = require("../util/runtime"); -const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); -const HarmonyImportDependency = require("./HarmonyImportDependency"); -const processExportInfo = require("./processExportInfo"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ExportsInfo")} ExportsInfo */ -/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ -/** @typedef {import("../ExportsInfo").UsedName} UsedName */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @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("./processExportInfo").ReferencedExports} ReferencedExports */ - -/** @typedef {"missing"|"unused"|"empty-star"|"reexport-dynamic-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-fake-namespace-object"|"reexport-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */ - -const { ExportPresenceModes } = HarmonyImportDependency; - -const idsSymbol = Symbol("HarmonyExportImportedSpecifierDependency.ids"); - -class NormalReexportItem { - /** - * @param {string} name export name - * @param {string[]} ids reexported ids from other module - * @param {ExportInfo} exportInfo export info from other module - * @param {boolean} checked true, if it should be checked at runtime if this export exists - * @param {boolean} hidden true, if it is hidden behind another active export in the same module - */ - constructor(name, ids, exportInfo, checked, hidden) { - this.name = name; - this.ids = ids; - this.exportInfo = exportInfo; - this.checked = checked; - this.hidden = hidden; - } -} - -/** @typedef {Set} ExportModeIgnored */ -/** @typedef {Set} ExportModeHidden */ - -class ExportMode { - /** - * @param {ExportModeType} type type of the mode - */ - constructor(type) { - /** @type {ExportModeType} */ - this.type = type; - - // for "normal-reexport": - /** @type {NormalReexportItem[] | null} */ - this.items = null; - - // for "reexport-named-default" | "reexport-fake-namespace-object" | "reexport-namespace-object" - /** @type {string | null} */ - this.name = null; - /** @type {ExportInfo | null} */ - this.partialNamespaceExportInfo = null; - - // for "dynamic-reexport": - /** @type {ExportModeIgnored | null} */ - this.ignored = null; - - // for "dynamic-reexport" | "empty-star": - /** @type {ExportModeHidden | undefined | null} */ - this.hidden = null; - - // for "missing": - /** @type {string | null} */ - this.userRequest = null; - - // for "reexport-fake-namespace-object": - /** @type {number} */ - this.fakeType = 0; - } -} - -/** - * @param {ModuleGraph} moduleGraph module graph - * @param {TODO} dependencies dependencies - * @param {TODO=} additionalDependency additional dependency - * @returns {TODO} result - */ -const determineExportAssignments = ( - moduleGraph, - dependencies, - additionalDependency -) => { - const names = new Set(); - /** @type {number[]} */ - const dependencyIndices = []; - - if (additionalDependency) { - dependencies = dependencies.concat(additionalDependency); - } - - for (const dep of dependencies) { - const i = dependencyIndices.length; - dependencyIndices[i] = names.size; - const otherImportedModule = moduleGraph.getModule(dep); - if (otherImportedModule) { - const exportsInfo = moduleGraph.getExportsInfo(otherImportedModule); - for (const exportInfo of exportsInfo.exports) { - if ( - exportInfo.provided === true && - exportInfo.name !== "default" && - !names.has(exportInfo.name) - ) { - names.add(exportInfo.name); - dependencyIndices[i] = names.size; - } - } - } - } - dependencyIndices.push(names.size); - - return { names: Array.from(names), dependencyIndices }; -}; - -const findDependencyForName = ( - { names, dependencyIndices }, - name, - dependencies -) => { - const dependenciesIt = dependencies[Symbol.iterator](); - const dependencyIndicesIt = dependencyIndices[Symbol.iterator](); - let dependenciesItResult = dependenciesIt.next(); - let dependencyIndicesItResult = dependencyIndicesIt.next(); - if (dependencyIndicesItResult.done) return; - for (let i = 0; i < names.length; i++) { - while (i >= dependencyIndicesItResult.value) { - dependenciesItResult = dependenciesIt.next(); - dependencyIndicesItResult = dependencyIndicesIt.next(); - if (dependencyIndicesItResult.done) return; - } - if (names[i] === name) return dependenciesItResult.value; - } - return undefined; -}; - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {HarmonyExportImportedSpecifierDependency} dep the dependency - * @param {string} runtimeKey the runtime key - * @returns {ExportMode} the export mode - */ -const getMode = (moduleGraph, dep, runtimeKey) => { - const importedModule = moduleGraph.getModule(dep); - - if (!importedModule) { - const mode = new ExportMode("missing"); - - mode.userRequest = dep.userRequest; - - return mode; - } - - const name = dep.name; - const runtime = keyToRuntime(runtimeKey); - const parentModule = /** @type {Module} */ (moduleGraph.getParentModule(dep)); - const exportsInfo = moduleGraph.getExportsInfo(parentModule); - - if ( - name - ? exportsInfo.getUsed(name, runtime) === UsageState.Unused - : exportsInfo.isUsed(runtime) === false - ) { - const mode = new ExportMode("unused"); - - mode.name = name || "*"; - - return mode; - } - - const importedExportsType = importedModule.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule - ); - - const ids = dep.getIds(moduleGraph); - - // Special handling for reexporting the default export - // from non-namespace modules - if (name && ids.length > 0 && ids[0] === "default") { - switch (importedExportsType) { - case "dynamic": { - const mode = new ExportMode("reexport-dynamic-default"); - - mode.name = name; - - return mode; - } - case "default-only": - case "default-with-named": { - const exportInfo = exportsInfo.getReadOnlyExportInfo(name); - const mode = new ExportMode("reexport-named-default"); - - mode.name = name; - mode.partialNamespaceExportInfo = exportInfo; - - return mode; - } - } - } - - // reexporting with a fixed name - if (name) { - let mode; - const exportInfo = exportsInfo.getReadOnlyExportInfo(name); - - if (ids.length > 0) { - // export { name as name } - switch (importedExportsType) { - case "default-only": - mode = new ExportMode("reexport-undefined"); - mode.name = name; - break; - default: - mode = new ExportMode("normal-reexport"); - mode.items = [ - new NormalReexportItem(name, ids, exportInfo, false, false) - ]; - break; - } - } else { - // export * as name - switch (importedExportsType) { - case "default-only": - mode = new ExportMode("reexport-fake-namespace-object"); - mode.name = name; - mode.partialNamespaceExportInfo = exportInfo; - mode.fakeType = 0; - break; - case "default-with-named": - mode = new ExportMode("reexport-fake-namespace-object"); - mode.name = name; - mode.partialNamespaceExportInfo = exportInfo; - mode.fakeType = 2; - break; - case "dynamic": - default: - mode = new ExportMode("reexport-namespace-object"); - mode.name = name; - mode.partialNamespaceExportInfo = exportInfo; - } - } - - return mode; - } - - // Star reexporting - - const { ignoredExports, exports, checked, hidden } = dep.getStarReexports( - moduleGraph, - runtime, - exportsInfo, - importedModule - ); - if (!exports) { - // We have too few info about the modules - // Delegate the logic to the runtime code - - const mode = new ExportMode("dynamic-reexport"); - mode.ignored = ignoredExports; - mode.hidden = hidden; - - return mode; - } - - if (exports.size === 0) { - const mode = new ExportMode("empty-star"); - mode.hidden = hidden; - - return mode; - } - - const mode = new ExportMode("normal-reexport"); - - mode.items = Array.from( - exports, - exportName => - new NormalReexportItem( - exportName, - [exportName], - exportsInfo.getReadOnlyExportInfo(exportName), - /** @type {Set} */ - (checked).has(exportName), - false - ) - ); - if (hidden !== undefined) { - for (const exportName of hidden) { - mode.items.push( - new NormalReexportItem( - exportName, - [exportName], - exportsInfo.getReadOnlyExportInfo(exportName), - false, - true - ) - ); - } - } - - return mode; -}; - -/** @typedef {string[]} Ids */ -/** @typedef {Set} Exports */ -/** @typedef {Set} Checked */ -/** @typedef {Set} Hidden */ -/** @typedef {Set} IgnoredExports */ - -class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { - /** - * @param {string} request the request string - * @param {number} sourceOrder the order in the original source file - * @param {Ids} ids the requested export name of the imported module - * @param {string | null} name the export name of for this module - * @param {Set} activeExports other named exports in the module - * @param {ReadonlyArray | Iterable | null} otherStarExports other star exports in the module before this import - * @param {number} exportPresenceMode mode of checking export names - * @param {HarmonyStarExportsList | null} allStarExports all star exports in the module - * @param {ImportAttributes=} attributes import attributes - */ - constructor( - request, - sourceOrder, - ids, - name, - activeExports, - otherStarExports, - exportPresenceMode, - allStarExports, - attributes - ) { - super(request, sourceOrder, attributes); - - this.ids = ids; - this.name = name; - this.activeExports = activeExports; - this.otherStarExports = otherStarExports; - this.exportPresenceMode = exportPresenceMode; - this.allStarExports = allStarExports; - } - - /** - * @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 Dependency.TRANSITIVE; - } - - // TODO webpack 6 remove - get id() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - // TODO webpack 6 remove - getId() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - // TODO webpack 6 remove - setId() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - get type() { - return "harmony export imported specifier"; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {Ids} the imported id - */ - getIds(moduleGraph) { - return moduleGraph.getMeta(this)[idsSymbol] || this.ids; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {Ids} ids the imported ids - * @returns {void} - */ - setIds(moduleGraph, ids) { - /** @type {TODO} */ - (moduleGraph.getMeta(this))[idsSymbol] = ids; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {RuntimeSpec} runtime the runtime - * @returns {ExportMode} the export mode - */ - getMode(moduleGraph, runtime) { - return moduleGraph.dependencyCacheProvide( - this, - getRuntimeKey(runtime), - getMode - ); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {RuntimeSpec} runtime the runtime - * @param {ExportsInfo} exportsInfo exports info about the current module (optional) - * @param {Module} importedModule the imported module (optional) - * @returns {{exports?: Exports, checked?: Checked, ignoredExports: IgnoredExports, hidden?: Hidden}} information - */ - getStarReexports( - moduleGraph, - runtime, - exportsInfo = moduleGraph.getExportsInfo( - /** @type {Module} */ (moduleGraph.getParentModule(this)) - ), - importedModule = /** @type {Module} */ (moduleGraph.getModule(this)) - ) { - const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); - const noExtraExports = - importedExportsInfo.otherExportsInfo.provided === false; - const noExtraImports = - exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused; - - const ignoredExports = new Set(["default", ...this.activeExports]); - - let hiddenExports; - const otherStarExports = - this._discoverActiveExportsFromOtherStarExports(moduleGraph); - if (otherStarExports !== undefined) { - hiddenExports = new Set(); - for (let i = 0; i < otherStarExports.namesSlice; i++) { - hiddenExports.add(otherStarExports.names[i]); - } - for (const e of ignoredExports) hiddenExports.delete(e); - } - - if (!noExtraExports && !noExtraImports) { - return { - ignoredExports, - hidden: hiddenExports - }; - } - - /** @type {Exports} */ - const exports = new Set(); - /** @type {Checked} */ - const checked = new Set(); - /** @type {Hidden | undefined} */ - const hidden = hiddenExports !== undefined ? new Set() : undefined; - - if (noExtraImports) { - for (const exportInfo of exportsInfo.orderedExports) { - const name = exportInfo.name; - if (ignoredExports.has(name)) continue; - if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; - const importedExportInfo = - importedExportsInfo.getReadOnlyExportInfo(name); - if (importedExportInfo.provided === false) continue; - if (hiddenExports !== undefined && hiddenExports.has(name)) { - /** @type {Set} */ - (hidden).add(name); - continue; - } - exports.add(name); - if (importedExportInfo.provided === true) continue; - checked.add(name); - } - } else if (noExtraExports) { - for (const importedExportInfo of importedExportsInfo.orderedExports) { - const name = importedExportInfo.name; - if (ignoredExports.has(name)) continue; - if (importedExportInfo.provided === false) continue; - const exportInfo = exportsInfo.getReadOnlyExportInfo(name); - if (exportInfo.getUsed(runtime) === UsageState.Unused) continue; - if (hiddenExports !== undefined && hiddenExports.has(name)) { - /** @type {ExportModeHidden} */ - (hidden).add(name); - continue; - } - exports.add(name); - if (importedExportInfo.provided === true) continue; - checked.add(name); - } - } - - return { ignoredExports, exports, checked, hidden }; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return (connection, runtime) => { - const mode = this.getMode(moduleGraph, runtime); - return mode.type !== "unused" && mode.type !== "empty-star"; - }; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return false; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - const mode = this.getMode(moduleGraph, runtime); - - switch (mode.type) { - case "missing": - case "unused": - case "empty-star": - case "reexport-undefined": - return Dependency.NO_EXPORTS_REFERENCED; - - case "reexport-dynamic-default": - return Dependency.EXPORTS_OBJECT_REFERENCED; - - case "reexport-named-default": { - if (!mode.partialNamespaceExportInfo) - return Dependency.EXPORTS_OBJECT_REFERENCED; - /** @type {ReferencedExports} */ - const referencedExports = []; - processExportInfo( - runtime, - referencedExports, - [], - /** @type {ExportInfo} */ (mode.partialNamespaceExportInfo) - ); - return referencedExports; - } - - case "reexport-namespace-object": - case "reexport-fake-namespace-object": { - if (!mode.partialNamespaceExportInfo) - return Dependency.EXPORTS_OBJECT_REFERENCED; - /** @type {ReferencedExports} */ - const referencedExports = []; - processExportInfo( - runtime, - referencedExports, - [], - /** @type {ExportInfo} */ (mode.partialNamespaceExportInfo), - mode.type === "reexport-fake-namespace-object" - ); - return referencedExports; - } - - case "dynamic-reexport": - return Dependency.EXPORTS_OBJECT_REFERENCED; - - case "normal-reexport": { - /** @type {ReferencedExports} */ - const referencedExports = []; - for (const { - ids, - exportInfo, - hidden - } of /** @type {NormalReexportItem[]} */ (mode.items)) { - if (hidden) continue; - processExportInfo(runtime, referencedExports, ids, exportInfo, false); - } - return referencedExports; - } - - default: - throw new Error(`Unknown mode ${mode.type}`); - } - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {{ names: string[], namesSlice: number, dependencyIndices: number[], dependencyIndex: number } | undefined} exported names and their origin dependency - */ - _discoverActiveExportsFromOtherStarExports(moduleGraph) { - if (!this.otherStarExports) return; - - const i = - "length" in this.otherStarExports - ? this.otherStarExports.length - : countIterable(this.otherStarExports); - if (i === 0) return; - - if (this.allStarExports) { - const { names, dependencyIndices } = moduleGraph.cached( - determineExportAssignments, - this.allStarExports.dependencies - ); - - return { - names, - namesSlice: dependencyIndices[i - 1], - dependencyIndices, - dependencyIndex: i - }; - } - - const { names, dependencyIndices } = moduleGraph.cached( - determineExportAssignments, - this.otherStarExports, - this - ); - - return { - names, - namesSlice: dependencyIndices[i - 1], - dependencyIndices, - dependencyIndex: i - }; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - const mode = this.getMode(moduleGraph, undefined); - - switch (mode.type) { - case "missing": - return; - case "dynamic-reexport": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: true, - from, - canMangle: false, - excludeExports: mode.hidden - ? combine( - /** @type {ExportModeIgnored} */ (mode.ignored), - mode.hidden - ) - : /** @type {ExportModeIgnored} */ (mode.ignored), - hideExports: mode.hidden, - dependencies: [from.module] - }; - } - case "empty-star": - return { - exports: [], - hideExports: mode.hidden, - dependencies: [/** @type {Module} */ (moduleGraph.getModule(this))] - }; - // falls through - case "normal-reexport": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: Array.from( - /** @type {NormalReexportItem[]} */ (mode.items), - item => ({ - name: item.name, - from, - export: item.ids, - hidden: item.hidden - }) - ), - priority: 1, - dependencies: [from.module] - }; - } - case "reexport-dynamic-default": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: [ - { - name: /** @type {string} */ (mode.name), - from, - export: ["default"] - } - ], - priority: 1, - dependencies: [from.module] - }; - } - case "reexport-undefined": - return { - exports: [/** @type {string} */ (mode.name)], - dependencies: [/** @type {Module} */ (moduleGraph.getModule(this))] - }; - case "reexport-fake-namespace-object": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: [ - { - name: /** @type {string} */ (mode.name), - from, - export: null, - exports: [ - { - name: "default", - canMangle: false, - from, - export: null - } - ] - } - ], - priority: 1, - dependencies: [from.module] - }; - } - case "reexport-namespace-object": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: [ - { - name: /** @type {string} */ (mode.name), - from, - export: null - } - ], - priority: 1, - dependencies: [from.module] - }; - } - case "reexport-named-default": { - const from = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(this)); - return { - exports: [ - { - name: /** @type {string} */ (mode.name), - from, - export: ["default"] - } - ], - priority: 1, - dependencies: [from.module] - }; - } - default: - throw new Error(`Unknown mode ${mode.type}`); - } - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {number} effective mode - */ - _getEffectiveExportPresenceLevel(moduleGraph) { - if (this.exportPresenceMode !== ExportPresenceModes.AUTO) - return this.exportPresenceMode; - const module = /** @type {Module} */ (moduleGraph.getParentModule(this)); - return /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule - ? ExportPresenceModes.ERROR - : ExportPresenceModes.WARN; - } - - /** - * Returns warnings - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} warnings - */ - getWarnings(moduleGraph) { - const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); - if (exportsPresence === ExportPresenceModes.WARN) { - return this._getErrors(moduleGraph); - } - return null; - } - - /** - * Returns errors - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} errors - */ - getErrors(moduleGraph) { - const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); - if (exportsPresence === ExportPresenceModes.ERROR) { - return this._getErrors(moduleGraph); - } - return null; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | undefined} errors - */ - _getErrors(moduleGraph) { - const ids = this.getIds(moduleGraph); - let errors = this.getLinkingErrors( - moduleGraph, - ids, - `(reexported as '${this.name}')` - ); - if (ids.length === 0 && this.name === null) { - const potentialConflicts = - this._discoverActiveExportsFromOtherStarExports(moduleGraph); - if (potentialConflicts && potentialConflicts.namesSlice > 0) { - const ownNames = new Set( - potentialConflicts.names.slice( - potentialConflicts.namesSlice, - potentialConflicts.dependencyIndices[ - potentialConflicts.dependencyIndex - ] - ) - ); - const importedModule = moduleGraph.getModule(this); - if (importedModule) { - const exportsInfo = moduleGraph.getExportsInfo(importedModule); - /** @type {Map} */ - const conflicts = new Map(); - for (const exportInfo of exportsInfo.orderedExports) { - if (exportInfo.provided !== true) continue; - if (exportInfo.name === "default") continue; - if (this.activeExports.has(exportInfo.name)) continue; - if (ownNames.has(exportInfo.name)) continue; - const conflictingDependency = findDependencyForName( - potentialConflicts, - exportInfo.name, - this.allStarExports - ? this.allStarExports.dependencies - : [...this.otherStarExports, this] - ); - if (!conflictingDependency) continue; - const target = exportInfo.getTerminalBinding(moduleGraph); - if (!target) continue; - const conflictingModule = - /** @type {Module} */ - (moduleGraph.getModule(conflictingDependency)); - if (conflictingModule === importedModule) continue; - const conflictingExportInfo = moduleGraph.getExportInfo( - conflictingModule, - exportInfo.name - ); - const conflictingTarget = - conflictingExportInfo.getTerminalBinding(moduleGraph); - if (!conflictingTarget) continue; - if (target === conflictingTarget) continue; - const list = conflicts.get(conflictingDependency.request); - if (list === undefined) { - conflicts.set(conflictingDependency.request, [exportInfo.name]); - } else { - list.push(exportInfo.name); - } - } - for (const [request, exports] of conflicts) { - if (!errors) errors = []; - errors.push( - new HarmonyLinkingError( - `The requested module '${ - this.request - }' contains conflicting star exports for the ${ - exports.length > 1 ? "names" : "name" - } ${exports - .map(e => `'${e}'`) - .join(", ")} with the previous requested module '${request}'` - ) - ); - } - } - } - } - return errors; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write, setCircularReference } = context; - - setCircularReference(this); - write(this.ids); - write(this.name); - write(this.activeExports); - write(this.otherStarExports); - write(this.exportPresenceMode); - write(this.allStarExports); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read, setCircularReference } = context; - - setCircularReference(this); - this.ids = read(); - this.name = read(); - this.activeExports = read(); - this.otherStarExports = read(); - this.exportPresenceMode = read(); - this.allStarExports = read(); - - super.deserialize(context); - } -} - -makeSerializable( - HarmonyExportImportedSpecifierDependency, - "webpack/lib/dependencies/HarmonyExportImportedSpecifierDependency" -); - -module.exports = HarmonyExportImportedSpecifierDependency; - -HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedSpecifierDependencyTemplate extends ( - HarmonyImportDependency.Template -) { - /** - * @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 { moduleGraph, runtime, concatenationScope } = templateContext; - - const dep = /** @type {HarmonyExportImportedSpecifierDependency} */ ( - dependency - ); - - const mode = dep.getMode(moduleGraph, runtime); - - if (concatenationScope) { - switch (mode.type) { - case "reexport-undefined": - concatenationScope.registerRawExport( - /** @type {NonNullable} */ (mode.name), - "/* reexport non-default export from non-harmony */ undefined" - ); - } - return; - } - - if (mode.type !== "unused" && mode.type !== "empty-star") { - super.apply(dependency, source, templateContext); - - this._addExportFragments( - templateContext.initFragments, - dep, - mode, - templateContext.module, - moduleGraph, - runtime, - templateContext.runtimeTemplate, - templateContext.runtimeRequirements - ); - } - } - - /** - * @param {InitFragment[]} initFragments target array for init fragments - * @param {HarmonyExportImportedSpecifierDependency} dep dependency - * @param {ExportMode} mode the export mode - * @param {Module} module the current module - * @param {ModuleGraph} moduleGraph the module graph - * @param {RuntimeSpec} runtime the runtime - * @param {RuntimeTemplate} runtimeTemplate the runtime template - * @param {RuntimeRequirements} runtimeRequirements runtime requirements - * @returns {void} - */ - _addExportFragments( - initFragments, - dep, - mode, - module, - moduleGraph, - runtime, - runtimeTemplate, - runtimeRequirements - ) { - const importedModule = /** @type {Module} */ (moduleGraph.getModule(dep)); - const importVar = dep.getImportVar(moduleGraph); - - switch (mode.type) { - case "missing": - case "empty-star": - initFragments.push( - new InitFragment( - "/* empty/unused harmony star reexport */\n", - InitFragment.STAGE_HARMONY_EXPORTS, - 1 - ) - ); - break; - - case "unused": - initFragments.push( - new InitFragment( - `${Template.toNormalComment( - `unused harmony reexport ${mode.name}` - )}\n`, - InitFragment.STAGE_HARMONY_EXPORTS, - 1 - ) - ); - break; - - case "reexport-dynamic-default": - initFragments.push( - this.getReexportFragment( - module, - "reexport default from dynamic", - moduleGraph - .getExportsInfo(module) - .getUsedName(/** @type {string} */ (mode.name), runtime), - importVar, - null, - runtimeRequirements - ) - ); - break; - - case "reexport-fake-namespace-object": - initFragments.push( - ...this.getReexportFakeNamespaceObjectFragments( - module, - moduleGraph - .getExportsInfo(module) - .getUsedName(/** @type {string} */ (mode.name), runtime), - importVar, - mode.fakeType, - runtimeRequirements - ) - ); - break; - - case "reexport-undefined": - initFragments.push( - this.getReexportFragment( - module, - "reexport non-default export from non-harmony", - moduleGraph - .getExportsInfo(module) - .getUsedName(/** @type {string} */ (mode.name), runtime), - "undefined", - "", - runtimeRequirements - ) - ); - break; - - case "reexport-named-default": - initFragments.push( - this.getReexportFragment( - module, - "reexport default export from named module", - moduleGraph - .getExportsInfo(module) - .getUsedName(/** @type {string} */ (mode.name), runtime), - importVar, - "", - runtimeRequirements - ) - ); - break; - - case "reexport-namespace-object": - initFragments.push( - this.getReexportFragment( - module, - "reexport module object", - moduleGraph - .getExportsInfo(module) - .getUsedName(/** @type {string} */ (mode.name), runtime), - importVar, - "", - runtimeRequirements - ) - ); - break; - - case "normal-reexport": - for (const { - name, - ids, - checked, - hidden - } of /** @type {NormalReexportItem[]} */ (mode.items)) { - if (hidden) continue; - if (checked) { - const connection = moduleGraph.getConnection(dep); - const key = `harmony reexport (checked) ${importVar} ${name}`; - const runtimeCondition = dep.weak - ? false - : connection - ? filterRuntime(runtime, r => connection.isTargetActive(r)) - : true; - initFragments.push( - new ConditionalInitFragment( - `/* harmony reexport (checked) */ ${this.getConditionalReexportStatement( - module, - name, - importVar, - ids, - runtimeRequirements - )}`, - moduleGraph.isAsync(importedModule) - ? InitFragment.STAGE_ASYNC_HARMONY_IMPORTS - : InitFragment.STAGE_HARMONY_IMPORTS, - dep.sourceOrder, - key, - runtimeCondition - ) - ); - } else { - initFragments.push( - this.getReexportFragment( - module, - "reexport safe", - moduleGraph.getExportsInfo(module).getUsedName(name, runtime), - importVar, - moduleGraph - .getExportsInfo(importedModule) - .getUsedName(ids, runtime), - runtimeRequirements - ) - ); - } - } - break; - - case "dynamic-reexport": { - const ignored = mode.hidden - ? combine( - /** @type {ExportModeIgnored} */ - (mode.ignored), - mode.hidden - ) - : /** @type {ExportModeIgnored} */ (mode.ignored); - const modern = - runtimeTemplate.supportsConst() && - runtimeTemplate.supportsArrowFunction(); - let content = - "/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};\n" + - `/* harmony reexport (unknown) */ for(${ - modern ? "const" : "var" - } __WEBPACK_IMPORT_KEY__ in ${importVar}) `; - - // Filter out exports which are defined by other exports - // and filter out default export because it cannot be reexported with * - if (ignored.size > 1) { - content += `if(${JSON.stringify( - Array.from(ignored) - )}.indexOf(__WEBPACK_IMPORT_KEY__) < 0) `; - } else if (ignored.size === 1) { - content += `if(__WEBPACK_IMPORT_KEY__ !== ${JSON.stringify( - first(ignored) - )}) `; - } - - content += "__WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = "; - content += modern - ? `() => ${importVar}[__WEBPACK_IMPORT_KEY__]` - : `function(key) { return ${importVar}[key]; }.bind(0, __WEBPACK_IMPORT_KEY__)`; - - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - - const exportsName = module.exportsArgument; - initFragments.push( - new InitFragment( - `${content}\n/* harmony reexport (unknown) */ ${RuntimeGlobals.definePropertyGetters}(${exportsName}, __WEBPACK_REEXPORT_OBJECT__);\n`, - moduleGraph.isAsync(importedModule) - ? InitFragment.STAGE_ASYNC_HARMONY_IMPORTS - : InitFragment.STAGE_HARMONY_IMPORTS, - dep.sourceOrder - ) - ); - break; - } - - default: - throw new Error(`Unknown mode ${mode.type}`); - } - } - - /** - * @param {Module} module the current module - * @param {string} comment comment - * @param {UsedName} key key - * @param {string} name name - * @param {string | string[] | null | false} valueKey value key - * @param {RuntimeRequirements} runtimeRequirements runtime requirements - * @returns {HarmonyExportInitFragment} harmony export init fragment - */ - getReexportFragment( - module, - comment, - key, - name, - valueKey, - runtimeRequirements - ) { - const returnValue = this.getReturnValue(name, valueKey); - - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - - const map = new Map(); - map.set(key, `/* ${comment} */ ${returnValue}`); - - return new HarmonyExportInitFragment(module.exportsArgument, map); - } - - /** - * @param {Module} module module - * @param {string | string[] | false} key key - * @param {string} name name - * @param {number} fakeType fake type - * @param {RuntimeRequirements} runtimeRequirements runtime requirements - * @returns {[InitFragment, HarmonyExportInitFragment]} init fragments - */ - getReexportFakeNamespaceObjectFragments( - module, - key, - name, - fakeType, - runtimeRequirements - ) { - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); - - const map = new Map(); - map.set( - key, - `/* reexport fake namespace object from non-harmony */ ${name}_namespace_cache || (${name}_namespace_cache = ${ - RuntimeGlobals.createFakeNamespaceObject - }(${name}${fakeType ? `, ${fakeType}` : ""}))` - ); - - return [ - new InitFragment( - `var ${name}_namespace_cache;\n`, - InitFragment.STAGE_CONSTANTS, - -1, - `${name}_namespace_cache` - ), - new HarmonyExportInitFragment(module.exportsArgument, map) - ]; - } - - /** - * @param {Module} module module - * @param {string} key key - * @param {string} name name - * @param {string | string[] | false} valueKey value key - * @param {RuntimeRequirements} runtimeRequirements runtime requirements - * @returns {string} result - */ - getConditionalReexportStatement( - module, - key, - name, - valueKey, - runtimeRequirements - ) { - if (valueKey === false) { - return "/* unused export */\n"; - } - - const exportsName = module.exportsArgument; - const returnValue = this.getReturnValue(name, valueKey); - - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - runtimeRequirements.add(RuntimeGlobals.hasOwnProperty); - - return `if(${RuntimeGlobals.hasOwnProperty}(${name}, ${JSON.stringify( - valueKey[0] - )})) ${ - RuntimeGlobals.definePropertyGetters - }(${exportsName}, { ${propertyName( - key - )}: function() { return ${returnValue}; } });\n`; - } - - /** - * @param {string} name name - * @param {null | false | string | string[]} valueKey value key - * @returns {string | undefined} value - */ - getReturnValue(name, valueKey) { - if (valueKey === null) { - return `${name}_default.a`; - } - - if (valueKey === "") { - return name; - } - - if (valueKey === false) { - return "/* unused export */ undefined"; - } - - return `${name}${propertyAccess(valueKey)}`; - } -}; - -class HarmonyStarExportsList { - constructor() { - /** @type {HarmonyExportImportedSpecifierDependency[]} */ - this.dependencies = []; - } - - /** - * @param {HarmonyExportImportedSpecifierDependency} dep dependency - * @returns {void} - */ - push(dep) { - this.dependencies.push(dep); - } - - slice() { - return this.dependencies.slice(); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize({ write, setCircularReference }) { - setCircularReference(this); - write(this.dependencies); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize({ read, setCircularReference }) { - setCircularReference(this); - this.dependencies = read(); - } -} - -makeSerializable( - HarmonyStarExportsList, - "webpack/lib/dependencies/HarmonyExportImportedSpecifierDependency", - "HarmonyStarExportsList" -); - -module.exports.HarmonyStarExportsList = HarmonyStarExportsList; diff --git a/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js b/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js deleted file mode 100644 index 8125cc2db8b..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportInitFragment.js +++ /dev/null @@ -1,177 +0,0 @@ -/* - 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 { first } = require("../util/SetHelpers"); -const { propertyName } = require("../util/propertyName"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ - -/** - * @param {Iterable} iterable iterable strings - * @returns {string} result - */ -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; -}; - -const EMPTY_MAP = new Map(); -const EMPTY_SET = new Set(); - -/** - * @extends {InitFragment} Context - */ -class HarmonyExportInitFragment extends InitFragment { - /** - * @param {string} exportsArgument the exports identifier - * @param {Map} exportMap mapping from used name to exposed variable name - * @param {Set} unusedExports list of unused export names - */ - constructor( - exportsArgument, - exportMap = EMPTY_MAP, - unusedExports = EMPTY_SET - ) { - super(undefined, InitFragment.STAGE_HARMONY_EXPORTS, 1, "harmony-exports"); - this.exportsArgument = exportsArgument; - this.exportMap = exportMap; - this.unusedExports = unusedExports; - } - - /** - * @param {HarmonyExportInitFragment[]} fragments all fragments to merge - * @returns {HarmonyExportInitFragment} merged fragment - */ - mergeAll(fragments) { - let exportMap; - let exportMapOwned = false; - let unusedExports; - let unusedExportsOwned = false; - - for (const fragment of fragments) { - if (fragment.exportMap.size !== 0) { - if (exportMap === undefined) { - exportMap = fragment.exportMap; - exportMapOwned = false; - } else { - if (!exportMapOwned) { - exportMap = new Map(exportMap); - exportMapOwned = true; - } - for (const [key, value] of fragment.exportMap) { - if (!exportMap.has(key)) exportMap.set(key, value); - } - } - } - if (fragment.unusedExports.size !== 0) { - if (unusedExports === undefined) { - unusedExports = fragment.unusedExports; - unusedExportsOwned = false; - } else { - if (!unusedExportsOwned) { - unusedExports = new Set(unusedExports); - unusedExportsOwned = true; - } - for (const value of fragment.unusedExports) { - unusedExports.add(value); - } - } - } - } - return new HarmonyExportInitFragment( - this.exportsArgument, - exportMap, - unusedExports - ); - } - - /** - * @param {HarmonyExportInitFragment} other other - * @returns {HarmonyExportInitFragment} merged result - */ - merge(other) { - let exportMap; - if (this.exportMap.size === 0) { - exportMap = other.exportMap; - } else if (other.exportMap.size === 0) { - exportMap = this.exportMap; - } else { - exportMap = new Map(other.exportMap); - for (const [key, value] of this.exportMap) { - if (!exportMap.has(key)) exportMap.set(key, value); - } - } - let unusedExports; - if (this.unusedExports.size === 0) { - unusedExports = other.unusedExports; - } else if (other.unusedExports.size === 0) { - unusedExports = this.unusedExports; - } else { - unusedExports = new Set(other.unusedExports); - for (const value of this.unusedExports) { - unusedExports.add(value); - } - } - return new HarmonyExportInitFragment( - this.exportsArgument, - exportMap, - unusedExports - ); - } - - /** - * @param {GenerateContext} context context - * @returns {string | Source | undefined} the source code that will be included as initialization code - */ - getContent({ runtimeTemplate, runtimeRequirements }) { - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - - const unusedPart = - this.unusedExports.size > 1 - ? `/* unused harmony exports ${joinIterableWithComma( - this.unusedExports - )} */\n` - : this.unusedExports.size > 0 - ? `/* unused harmony export ${first(this.unusedExports)} */\n` - : ""; - const definitions = []; - const orderedExportMap = Array.from(this.exportMap).sort(([a], [b]) => - a < b ? -1 : 1 - ); - for (const [key, value] of orderedExportMap) { - definitions.push( - `\n/* harmony export */ ${propertyName( - key - )}: ${runtimeTemplate.returningFunction(value)}` - ); - } - const definePart = - this.exportMap.size > 0 - ? `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${ - this.exportsArgument - }, {${definitions.join(",")}\n/* harmony export */ });\n` - : ""; - return `${definePart}${unusedPart}`; - } -} - -module.exports = HarmonyExportInitFragment; diff --git a/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js deleted file mode 100644 index 4e742935942..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExportSpecifierDependency.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const HarmonyExportInitFragment = require("./HarmonyExportInitFragment"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class HarmonyExportSpecifierDependency extends NullDependency { - /** - * @param {TODO} id id - * @param {TODO} name name - */ - constructor(id, name) { - super(); - this.id = id; - this.name = name; - } - - get type() { - return "harmony export specifier"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - return { - exports: [this.name], - priority: 1, - terminalBinding: true, - dependencies: undefined - }; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return false; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.id); - write(this.name); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.id = read(); - this.name = read(); - super.deserialize(context); - } -} - -makeSerializable( - HarmonyExportSpecifierDependency, - "webpack/lib/dependencies/HarmonyExportSpecifierDependency" -); - -HarmonyExportSpecifierDependency.Template = class HarmonyExportSpecifierDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { module, moduleGraph, initFragments, runtime, concatenationScope } - ) { - const dep = /** @type {HarmonyExportSpecifierDependency} */ (dependency); - if (concatenationScope) { - concatenationScope.registerExport(dep.name, dep.id); - return; - } - const used = moduleGraph - .getExportsInfo(module) - .getUsedName(dep.name, runtime); - if (!used) { - const set = new Set(); - set.add(dep.name || "namespace"); - initFragments.push( - new HarmonyExportInitFragment(module.exportsArgument, undefined, set) - ); - return; - } - - const map = new Map(); - map.set(used, `/* binding */ ${dep.id}`); - initFragments.push( - new HarmonyExportInitFragment(module.exportsArgument, map, undefined) - ); - } -}; - -module.exports = HarmonyExportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyExports.js b/webpack-lib/lib/dependencies/HarmonyExports.js deleted file mode 100644 index 1f6e6d4acb9..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyExports.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); - -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ - -/** @type {WeakMap} */ -const parserStateExportsState = new WeakMap(); - -/** - * @param {ParserState} parserState parser state - * @param {boolean} isStrictHarmony strict harmony mode should be enabled - * @returns {void} - */ -module.exports.enable = (parserState, isStrictHarmony) => { - const value = parserStateExportsState.get(parserState); - if (value === false) return; - parserStateExportsState.set(parserState, true); - if (value !== true) { - const buildMeta = /** @type {BuildMeta} */ (parserState.module.buildMeta); - buildMeta.exportsType = "namespace"; - const buildInfo = /** @type {BuildInfo} */ (parserState.module.buildInfo); - buildInfo.strict = true; - buildInfo.exportsArgument = RuntimeGlobals.exports; - if (isStrictHarmony) { - buildMeta.strictHarmonyModule = true; - buildInfo.moduleArgument = "__webpack_module__"; - } - } -}; - -/** - * @param {ParserState} parserState parser state - * @returns {boolean} true, when enabled - */ -module.exports.isEnabled = parserState => { - const value = parserStateExportsState.get(parserState); - return value === true; -}; diff --git a/webpack-lib/lib/dependencies/HarmonyImportDependency.js b/webpack-lib/lib/dependencies/HarmonyImportDependency.js deleted file mode 100644 index e9f26fc9459..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyImportDependency.js +++ /dev/null @@ -1,382 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ConditionalInitFragment = require("../ConditionalInitFragment"); -const Dependency = require("../Dependency"); -const HarmonyLinkingError = require("../HarmonyLinkingError"); -const InitFragment = require("../InitFragment"); -const Template = require("../Template"); -const AwaitDependenciesInitFragment = require("../async-modules/AwaitDependenciesInitFragment"); -const { filterRuntime, mergeRuntime } = require("../util/runtime"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ExportsInfo")} ExportsInfo */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -const ExportPresenceModes = { - NONE: /** @type {0} */ (0), - WARN: /** @type {1} */ (1), - AUTO: /** @type {2} */ (2), - ERROR: /** @type {3} */ (3), - /** - * @param {string | false} str param - * @returns {0 | 1 | 2 | 3} result - */ - fromUserOption(str) { - switch (str) { - case "error": - return ExportPresenceModes.ERROR; - case "warn": - return ExportPresenceModes.WARN; - case "auto": - return ExportPresenceModes.AUTO; - case false: - return ExportPresenceModes.NONE; - default: - throw new Error(`Invalid export presence value ${str}`); - } - } -}; - -class HarmonyImportDependency extends ModuleDependency { - /** - * @param {string} request request string - * @param {number} sourceOrder source order - * @param {ImportAttributes=} attributes import attributes - */ - constructor(request, sourceOrder, attributes) { - super(request); - this.sourceOrder = sourceOrder; - this.assertions = attributes; - } - - get category() { - return "esm"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return Dependency.NO_EXPORTS_REFERENCED; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {string} name of the variable for the import - */ - getImportVar(moduleGraph) { - const module = /** @type {Module} */ (moduleGraph.getParentModule(this)); - const meta = /** @type {TODO} */ (moduleGraph.getMeta(module)); - let importVarMap = meta.importVarMap; - if (!importVarMap) meta.importVarMap = importVarMap = new Map(); - let importVar = importVarMap.get( - /** @type {Module} */ (moduleGraph.getModule(this)) - ); - if (importVar) return importVar; - importVar = `${Template.toIdentifier( - `${this.userRequest}` - )}__WEBPACK_IMPORTED_MODULE_${importVarMap.size}__`; - importVarMap.set( - /** @type {Module} */ (moduleGraph.getModule(this)), - importVar - ); - return importVar; - } - - /** - * @param {boolean} update create new variables or update existing one - * @param {DependencyTemplateContext} templateContext the template context - * @returns {[string, string]} the import statement and the compat statement - */ - getImportStatement( - update, - { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } - ) { - return runtimeTemplate.importStatement({ - update, - module: /** @type {Module} */ (moduleGraph.getModule(this)), - chunkGraph, - importVar: this.getImportVar(moduleGraph), - request: this.request, - originModule: module, - runtimeRequirements - }); - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @param {string[]} ids imported ids - * @param {string} additionalMessage extra info included in the error message - * @returns {WebpackError[] | undefined} errors - */ - getLinkingErrors(moduleGraph, ids, additionalMessage) { - const importedModule = moduleGraph.getModule(this); - // ignore errors for missing or failed modules - if (!importedModule || importedModule.getNumberOfErrors() > 0) { - return; - } - - const parentModule = - /** @type {Module} */ - (moduleGraph.getParentModule(this)); - const exportsType = importedModule.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ (parentModule.buildMeta).strictHarmonyModule - ); - if (exportsType === "namespace" || exportsType === "default-with-named") { - if (ids.length === 0) { - return; - } - - if ( - (exportsType !== "default-with-named" || ids[0] !== "default") && - moduleGraph.isExportProvided(importedModule, ids) === false - ) { - // We are sure that it's not provided - - // Try to provide detailed info in the error message - let pos = 0; - let exportsInfo = moduleGraph.getExportsInfo(importedModule); - while (pos < ids.length && exportsInfo) { - const id = ids[pos++]; - const exportInfo = exportsInfo.getReadOnlyExportInfo(id); - if (exportInfo.provided === false) { - // We are sure that it's not provided - const providedExports = exportsInfo.getProvidedExports(); - const moreInfo = !Array.isArray(providedExports) - ? " (possible exports unknown)" - : providedExports.length === 0 - ? " (module has no exports)" - : ` (possible exports: ${providedExports.join(", ")})`; - return [ - new HarmonyLinkingError( - `export ${ids - .slice(0, pos) - .map(id => `'${id}'`) - .join(".")} ${additionalMessage} was not found in '${ - this.userRequest - }'${moreInfo}` - ) - ]; - } - exportsInfo = - /** @type {ExportsInfo} */ - (exportInfo.getNestedExportsInfo()); - } - - // General error message - return [ - new HarmonyLinkingError( - `export ${ids - .map(id => `'${id}'`) - .join(".")} ${additionalMessage} was not found in '${ - this.userRequest - }'` - ) - ]; - } - } - switch (exportsType) { - case "default-only": - // It's has only a default export - if (ids.length > 0 && ids[0] !== "default") { - // In strict harmony modules we only support the default export - return [ - new HarmonyLinkingError( - `Can't import the named export ${ids - .map(id => `'${id}'`) - .join( - "." - )} ${additionalMessage} from default-exporting module (only default export is available)` - ) - ]; - } - break; - case "default-with-named": - // It has a default export and named properties redirect - // In some cases we still want to warn here - if ( - ids.length > 0 && - ids[0] !== "default" && - /** @type {BuildMeta} */ - (importedModule.buildMeta).defaultObject === "redirect-warn" - ) { - // For these modules only the default export is supported - return [ - new HarmonyLinkingError( - `Should not import the named export ${ids - .map(id => `'${id}'`) - .join( - "." - )} ${additionalMessage} from default-exporting module (only default export is available soon)` - ) - ]; - } - break; - } - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.sourceOrder); - write(this.assertions); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.sourceOrder = read(); - this.assertions = read(); - super.deserialize(context); - } -} - -module.exports = HarmonyImportDependency; - -/** @type {WeakMap>} */ -const importEmittedMap = new WeakMap(); - -HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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 dep = /** @type {HarmonyImportDependency} */ (dependency); - const { module, chunkGraph, moduleGraph, runtime } = templateContext; - - const connection = moduleGraph.getConnection(dep); - if (connection && !connection.isTargetActive(runtime)) return; - - const referencedModule = connection && connection.module; - - if ( - connection && - connection.weak && - referencedModule && - chunkGraph.getModuleId(referencedModule) === null - ) { - // in weak references, module might not be in any chunk - // but that's ok, we don't need that logic in this case - return; - } - - const moduleKey = referencedModule - ? referencedModule.identifier() - : dep.request; - const key = `harmony import ${moduleKey}`; - - const runtimeCondition = dep.weak - ? false - : connection - ? filterRuntime(runtime, r => connection.isTargetActive(r)) - : true; - - if (module && referencedModule) { - let emittedModules = importEmittedMap.get(module); - if (emittedModules === undefined) { - emittedModules = new WeakMap(); - importEmittedMap.set(module, emittedModules); - } - let mergedRuntimeCondition = runtimeCondition; - const oldRuntimeCondition = emittedModules.get(referencedModule) || false; - if (oldRuntimeCondition !== false && mergedRuntimeCondition !== true) { - if (mergedRuntimeCondition === false || oldRuntimeCondition === true) { - mergedRuntimeCondition = oldRuntimeCondition; - } else { - mergedRuntimeCondition = mergeRuntime( - oldRuntimeCondition, - mergedRuntimeCondition - ); - } - } - emittedModules.set(referencedModule, mergedRuntimeCondition); - } - - const importStatement = dep.getImportStatement(false, templateContext); - if ( - referencedModule && - templateContext.moduleGraph.isAsync(referencedModule) - ) { - templateContext.initFragments.push( - new ConditionalInitFragment( - importStatement[0], - InitFragment.STAGE_HARMONY_IMPORTS, - dep.sourceOrder, - key, - runtimeCondition - ) - ); - templateContext.initFragments.push( - new AwaitDependenciesInitFragment( - new Set([dep.getImportVar(templateContext.moduleGraph)]) - ) - ); - templateContext.initFragments.push( - new ConditionalInitFragment( - importStatement[1], - InitFragment.STAGE_ASYNC_HARMONY_IMPORTS, - dep.sourceOrder, - `${key} compat`, - runtimeCondition - ) - ); - } else { - templateContext.initFragments.push( - new ConditionalInitFragment( - importStatement[0] + importStatement[1], - InitFragment.STAGE_HARMONY_IMPORTS, - dep.sourceOrder, - key, - runtimeCondition - ) - ); - } - } - - /** - * @param {Module} module the module - * @param {Module} referencedModule the referenced module - * @returns {RuntimeSpec | boolean} runtimeCondition in which this import has been emitted - */ - static getImportEmittedRuntime(module, referencedModule) { - const emittedModules = importEmittedMap.get(module); - if (emittedModules === undefined) return false; - return emittedModules.get(referencedModule) || false; - } -}; - -module.exports.ExportPresenceModes = ExportPresenceModes; diff --git a/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js deleted file mode 100644 index e7c556339e0..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyImportDependencyParserPlugin.js +++ /dev/null @@ -1,370 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const HotModuleReplacementPlugin = require("../HotModuleReplacementPlugin"); -const { getImportAttributes } = require("../javascript/JavascriptParser"); -const InnerGraph = require("../optimize/InnerGraph"); -const ConstDependency = require("./ConstDependency"); -const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); -const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); -const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency"); -const HarmonyExports = require("./HarmonyExports"); -const { ExportPresenceModes } = require("./HarmonyImportDependency"); -const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); -const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); - -/** @typedef {import("estree").Identifier} Identifier */ -/** @typedef {import("estree").Literal} Literal */ -/** @typedef {import("estree").MemberExpression} MemberExpression */ -/** @typedef {import("estree").ObjectExpression} ObjectExpression */ -/** @typedef {import("estree").Property} Property */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ -/** @typedef {import("../javascript/JavascriptParser").ExportAllDeclaration} ExportAllDeclaration */ -/** @typedef {import("../javascript/JavascriptParser").ExportNamedDeclaration} ExportNamedDeclaration */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").ImportDeclaration} ImportDeclaration */ -/** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../optimize/InnerGraph").InnerGraph} InnerGraph */ -/** @typedef {import("../optimize/InnerGraph").TopLevelSymbol} TopLevelSymbol */ -/** @typedef {import("./HarmonyImportDependency")} HarmonyImportDependency */ - -const harmonySpecifierTag = Symbol("harmony import"); - -/** - * @typedef {object} HarmonySettings - * @property {string[]} ids - * @property {string} source - * @property {number} sourceOrder - * @property {string} name - * @property {boolean} await - * @property {Record | undefined} assertions - */ - -module.exports = class HarmonyImportDependencyParserPlugin { - /** - * @param {JavascriptParserOptions} options options - */ - constructor(options) { - this.exportPresenceMode = - options.importExportsPresence !== undefined - ? ExportPresenceModes.fromUserOption(options.importExportsPresence) - : options.exportsPresence !== undefined - ? ExportPresenceModes.fromUserOption(options.exportsPresence) - : options.strictExportPresence - ? ExportPresenceModes.ERROR - : ExportPresenceModes.AUTO; - this.strictThisContextOnImports = options.strictThisContextOnImports; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - const { exportPresenceMode } = this; - - /** - * @param {string[]} members members - * @param {boolean[]} membersOptionals members Optionals - * @returns {string[]} a non optional part - */ - function getNonOptionalPart(members, membersOptionals) { - let i = 0; - while (i < members.length && membersOptionals[i] === false) i++; - return i !== members.length ? members.slice(0, i) : members; - } - - /** - * @param {TODO} node member expression - * @param {number} count count - * @returns {TODO} member expression - */ - function getNonOptionalMemberChain(node, count) { - while (count--) node = node.object; - return node; - } - - parser.hooks.isPure - .for("Identifier") - .tap("HarmonyImportDependencyParserPlugin", expression => { - const expr = /** @type {Identifier} */ (expression); - if ( - parser.isVariableDefined(expr.name) || - parser.getTagData(expr.name, harmonySpecifierTag) - ) { - return true; - } - }); - parser.hooks.import.tap( - "HarmonyImportDependencyParserPlugin", - (statement, source) => { - parser.state.lastHarmonyImportOrder = - (parser.state.lastHarmonyImportOrder || 0) + 1; - const clearDep = new ConstDependency( - parser.isAsiPosition(/** @type {Range} */ (statement.range)[0]) - ? ";" - : "", - /** @type {Range} */ (statement.range) - ); - clearDep.loc = /** @type {DependencyLocation} */ (statement.loc); - parser.state.module.addPresentationalDependency(clearDep); - parser.unsetAsiPosition(/** @type {Range} */ (statement.range)[1]); - const attributes = getImportAttributes(statement); - const sideEffectDep = new HarmonyImportSideEffectDependency( - /** @type {string} */ (source), - parser.state.lastHarmonyImportOrder, - attributes - ); - sideEffectDep.loc = /** @type {DependencyLocation} */ (statement.loc); - parser.state.module.addDependency(sideEffectDep); - return true; - } - ); - parser.hooks.importSpecifier.tap( - "HarmonyImportDependencyParserPlugin", - (statement, source, id, name) => { - const ids = id === null ? [] : [id]; - parser.tagVariable(name, harmonySpecifierTag, { - name, - source, - ids, - sourceOrder: parser.state.lastHarmonyImportOrder, - assertions: getImportAttributes(statement) - }); - return true; - } - ); - parser.hooks.binaryExpression.tap( - "HarmonyImportDependencyParserPlugin", - expression => { - if (expression.operator !== "in") return; - - const leftPartEvaluated = parser.evaluateExpression(expression.left); - if (leftPartEvaluated.couldHaveSideEffects()) return; - const leftPart = leftPartEvaluated.asString(); - if (!leftPart) return; - - const rightPart = parser.evaluateExpression(expression.right); - if (!rightPart.isIdentifier()) return; - - const rootInfo = rightPart.rootInfo; - if ( - typeof rootInfo === "string" || - !rootInfo || - !rootInfo.tagInfo || - rootInfo.tagInfo.tag !== harmonySpecifierTag - ) - return; - const settings = rootInfo.tagInfo.data; - const members = - /** @type {(() => string[])} */ - (rightPart.getMembers)(); - const dep = new HarmonyEvaluatedImportSpecifierDependency( - settings.source, - settings.sourceOrder, - settings.ids.concat(members).concat([leftPart]), - settings.name, - /** @type {Range} */ (expression.range), - settings.assertions, - "in" - ); - dep.directImport = members.length === 0; - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expression.range)[0] - ); - dep.loc = /** @type {DependencyLocation} */ (expression.loc); - parser.state.module.addDependency(dep); - InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); - return true; - } - ); - parser.hooks.expression - .for(harmonySpecifierTag) - .tap("HarmonyImportDependencyParserPlugin", expr => { - const settings = /** @type {HarmonySettings} */ (parser.currentTagData); - const dep = new HarmonyImportSpecifierDependency( - settings.source, - settings.sourceOrder, - settings.ids, - settings.name, - /** @type {Range} */ (expr.range), - exportPresenceMode, - settings.assertions, - [] - ); - dep.referencedPropertiesInDestructuring = - parser.destructuringAssignmentPropertiesFor(expr); - dep.shorthand = parser.scope.inShorthand; - dep.directImport = true; - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expr.range)[0] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.call = parser.scope.inTaggedTemplateTag; - parser.state.module.addDependency(dep); - InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); - return true; - }); - parser.hooks.expressionMemberChain - .for(harmonySpecifierTag) - .tap( - "HarmonyImportDependencyParserPlugin", - (expression, members, membersOptionals, memberRanges) => { - const settings = /** @type {HarmonySettings} */ ( - parser.currentTagData - ); - const nonOptionalMembers = getNonOptionalPart( - members, - membersOptionals - ); - /** @type {Range[]} */ - const ranges = memberRanges.slice( - 0, - memberRanges.length - (members.length - nonOptionalMembers.length) - ); - const expr = - nonOptionalMembers !== members - ? getNonOptionalMemberChain( - expression, - members.length - nonOptionalMembers.length - ) - : expression; - const ids = settings.ids.concat(nonOptionalMembers); - const dep = new HarmonyImportSpecifierDependency( - settings.source, - settings.sourceOrder, - ids, - settings.name, - /** @type {Range} */ (expr.range), - exportPresenceMode, - settings.assertions, - ranges - ); - dep.referencedPropertiesInDestructuring = - parser.destructuringAssignmentPropertiesFor(expr); - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expr.range)[0] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); - return true; - } - ); - parser.hooks.callMemberChain - .for(harmonySpecifierTag) - .tap( - "HarmonyImportDependencyParserPlugin", - (expression, members, membersOptionals, memberRanges) => { - const { arguments: args, callee } = expression; - const settings = /** @type {HarmonySettings} */ ( - parser.currentTagData - ); - const nonOptionalMembers = getNonOptionalPart( - members, - membersOptionals - ); - /** @type {Range[]} */ - const ranges = memberRanges.slice( - 0, - memberRanges.length - (members.length - nonOptionalMembers.length) - ); - const expr = - nonOptionalMembers !== members - ? getNonOptionalMemberChain( - callee, - members.length - nonOptionalMembers.length - ) - : callee; - const ids = settings.ids.concat(nonOptionalMembers); - const dep = new HarmonyImportSpecifierDependency( - settings.source, - settings.sourceOrder, - ids, - settings.name, - /** @type {Range} */ (expr.range), - exportPresenceMode, - settings.assertions, - ranges - ); - dep.directImport = members.length === 0; - dep.call = true; - dep.asiSafe = !parser.isAsiPosition( - /** @type {Range} */ (expr.range)[0] - ); - // only in case when we strictly follow the spec we need a special case here - dep.namespaceObjectAsContext = - members.length > 0 && - /** @type {boolean} */ (this.strictThisContextOnImports); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - if (args) parser.walkExpressions(args); - InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); - return true; - } - ); - const { hotAcceptCallback, hotAcceptWithoutCallback } = - HotModuleReplacementPlugin.getParserHooks(parser); - hotAcceptCallback.tap( - "HarmonyImportDependencyParserPlugin", - (expr, requests) => { - if (!HarmonyExports.isEnabled(parser.state)) { - // This is not a harmony module, skip it - return; - } - const dependencies = requests.map(request => { - const dep = new HarmonyAcceptImportDependency(request); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - return dep; - }); - if (dependencies.length > 0) { - const dep = new HarmonyAcceptDependency( - /** @type {Range} */ - (expr.range), - dependencies, - true - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - } - } - ); - hotAcceptWithoutCallback.tap( - "HarmonyImportDependencyParserPlugin", - (expr, requests) => { - if (!HarmonyExports.isEnabled(parser.state)) { - // This is not a harmony module, skip it - return; - } - const dependencies = requests.map(request => { - const dep = new HarmonyAcceptImportDependency(request); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - return dep; - }); - if (dependencies.length > 0) { - const dep = new HarmonyAcceptDependency( - /** @type {Range} */ - (expr.range), - dependencies, - false - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - } - } - ); - } -}; - -module.exports.harmonySpecifierTag = harmonySpecifierTag; diff --git a/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js b/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js deleted file mode 100644 index bf691745ed3..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyImportSideEffectDependency.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const HarmonyImportDependency = require("./HarmonyImportDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class HarmonyImportSideEffectDependency extends HarmonyImportDependency { - /** - * @param {string} request the request string - * @param {number} sourceOrder source order - * @param {ImportAttributes=} attributes import attributes - */ - constructor(request, sourceOrder, attributes) { - super(request, sourceOrder, attributes); - } - - get type() { - return "harmony side effect evaluation"; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return connection => { - const refModule = connection.resolvedModule; - if (!refModule) return true; - return refModule.getSideEffectsConnectionState(moduleGraph); - }; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - const refModule = moduleGraph.getModule(this); - if (!refModule) return true; - return refModule.getSideEffectsConnectionState(moduleGraph); - } -} - -makeSerializable( - HarmonyImportSideEffectDependency, - "webpack/lib/dependencies/HarmonyImportSideEffectDependency" -); - -HarmonyImportSideEffectDependency.Template = class HarmonyImportSideEffectDependencyTemplate extends ( - HarmonyImportDependency.Template -) { - /** - * @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 { moduleGraph, concatenationScope } = templateContext; - if (concatenationScope) { - const module = /** @type {Module} */ (moduleGraph.getModule(dependency)); - if (concatenationScope.isModuleInScope(module)) { - return; - } - } - super.apply(dependency, source, templateContext); - } -}; - -module.exports = HarmonyImportSideEffectDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js b/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js deleted file mode 100644 index 3ea38c111f2..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyImportSpecifierDependency.js +++ /dev/null @@ -1,468 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const Template = require("../Template"); -const { - getDependencyUsedByExportsCondition -} = require("../optimize/InnerGraph"); -const { getTrimmedIdsAndRange } = require("../util/chainedImports"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const HarmonyImportDependency = require("./HarmonyImportDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -const idsSymbol = Symbol("HarmonyImportSpecifierDependency.ids"); - -const { ExportPresenceModes } = HarmonyImportDependency; - -class HarmonyImportSpecifierDependency extends HarmonyImportDependency { - /** - * @param {string} request request - * @param {number} sourceOrder source order - * @param {string[]} ids ids - * @param {string} name name - * @param {Range} range range - * @param {TODO} exportPresenceMode export presence mode - * @param {ImportAttributes | undefined} attributes import attributes - * @param {Range[] | undefined} idRanges ranges for members of ids; the two arrays are right-aligned - */ - constructor( - request, - sourceOrder, - ids, - name, - range, - exportPresenceMode, - attributes, - idRanges // TODO webpack 6 make this non-optional. It must always be set to properly trim ids. - ) { - super(request, sourceOrder, attributes); - this.ids = ids; - this.name = name; - this.range = range; - this.idRanges = idRanges; - this.exportPresenceMode = exportPresenceMode; - this.namespaceObjectAsContext = false; - this.call = undefined; - this.directImport = undefined; - this.shorthand = undefined; - this.asiSafe = undefined; - /** @type {Set | boolean | undefined} */ - this.usedByExports = undefined; - /** @type {Set | undefined} */ - this.referencedPropertiesInDestructuring = undefined; - } - - // TODO webpack 6 remove - get id() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - // TODO webpack 6 remove - getId() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - // TODO webpack 6 remove - setId() { - throw new Error("id was renamed to ids and type changed to string[]"); - } - - get type() { - return "harmony import specifier"; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {string[]} the imported ids - */ - getIds(moduleGraph) { - const meta = moduleGraph.getMetaIfExisting(this); - if (meta === undefined) return this.ids; - const ids = meta[/** @type {keyof object} */ (idsSymbol)]; - return ids !== undefined ? ids : this.ids; - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {string[]} ids the imported ids - * @returns {void} - */ - setIds(moduleGraph, ids) { - /** @type {TODO} */ - (moduleGraph.getMeta(this))[idsSymbol] = ids; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return getDependencyUsedByExportsCondition( - this, - this.usedByExports, - moduleGraph - ); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return false; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - let ids = this.getIds(moduleGraph); - if (ids.length === 0) return this._getReferencedExportsInDestructuring(); - let namespaceObjectAsContext = this.namespaceObjectAsContext; - if (ids[0] === "default") { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(this)); - const importedModule = - /** @type {Module} */ - (moduleGraph.getModule(this)); - switch ( - importedModule.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ - (selfModule.buildMeta).strictHarmonyModule - ) - ) { - case "default-only": - case "default-with-named": - if (ids.length === 1) - return this._getReferencedExportsInDestructuring(); - ids = ids.slice(1); - namespaceObjectAsContext = true; - break; - case "dynamic": - return Dependency.EXPORTS_OBJECT_REFERENCED; - } - } - - if ( - this.call && - !this.directImport && - (namespaceObjectAsContext || ids.length > 1) - ) { - if (ids.length === 1) return Dependency.EXPORTS_OBJECT_REFERENCED; - ids = ids.slice(0, -1); - } - - return this._getReferencedExportsInDestructuring(ids); - } - - /** - * @param {string[]=} ids ids - * @returns {string[][]} referenced exports - */ - _getReferencedExportsInDestructuring(ids) { - if (this.referencedPropertiesInDestructuring) { - /** @type {string[][]} */ - const refs = []; - for (const { id } of this.referencedPropertiesInDestructuring) { - refs.push(ids ? ids.concat([id]) : [id]); - } - return refs; - } - return ids ? [ids] : Dependency.EXPORTS_OBJECT_REFERENCED; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {number} effective mode - */ - _getEffectiveExportPresenceLevel(moduleGraph) { - if (this.exportPresenceMode !== ExportPresenceModes.AUTO) - return this.exportPresenceMode; - const buildMeta = - /** @type {BuildMeta} */ - ( - /** @type {Module} */ - (moduleGraph.getParentModule(this)).buildMeta - ); - return buildMeta.strictHarmonyModule - ? ExportPresenceModes.ERROR - : ExportPresenceModes.WARN; - } - - /** - * Returns warnings - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} warnings - */ - getWarnings(moduleGraph) { - const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); - if (exportsPresence === ExportPresenceModes.WARN) { - return this._getErrors(moduleGraph); - } - return null; - } - - /** - * Returns errors - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} errors - */ - getErrors(moduleGraph) { - const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph); - if (exportsPresence === ExportPresenceModes.ERROR) { - return this._getErrors(moduleGraph); - } - return null; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | undefined} errors - */ - _getErrors(moduleGraph) { - const ids = this.getIds(moduleGraph); - return this.getLinkingErrors( - moduleGraph, - ids, - `(imported as '${this.name}')` - ); - } - - /** - * 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 0; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.ids); - write(this.name); - write(this.range); - write(this.idRanges); - write(this.exportPresenceMode); - write(this.namespaceObjectAsContext); - write(this.call); - write(this.directImport); - write(this.shorthand); - write(this.asiSafe); - write(this.usedByExports); - write(this.referencedPropertiesInDestructuring); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.ids = read(); - this.name = read(); - this.range = read(); - this.idRanges = read(); - this.exportPresenceMode = read(); - this.namespaceObjectAsContext = read(); - this.call = read(); - this.directImport = read(); - this.shorthand = read(); - this.asiSafe = read(); - this.usedByExports = read(); - this.referencedPropertiesInDestructuring = read(); - super.deserialize(context); - } -} - -makeSerializable( - HarmonyImportSpecifierDependency, - "webpack/lib/dependencies/HarmonyImportSpecifierDependency" -); - -HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends ( - HarmonyImportDependency.Template -) { - /** - * @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 dep = /** @type {HarmonyImportSpecifierDependency} */ (dependency); - const { moduleGraph, runtime } = templateContext; - const connection = moduleGraph.getConnection(dep); - // Skip rendering depending when dependency is conditional - if (connection && !connection.isTargetActive(runtime)) return; - - const ids = dep.getIds(moduleGraph); - const { - trimmedRange: [trimmedRangeStart, trimmedRangeEnd], - trimmedIds - } = getTrimmedIdsAndRange(ids, dep.range, dep.idRanges, moduleGraph, dep); - - const exportExpr = this._getCodeForIds( - dep, - source, - templateContext, - trimmedIds - ); - if (dep.shorthand) { - source.insert(trimmedRangeEnd, `: ${exportExpr}`); - } else { - source.replace(trimmedRangeStart, trimmedRangeEnd - 1, exportExpr); - } - - if (dep.referencedPropertiesInDestructuring) { - let prefixedIds = ids; - - if (ids[0] === "default") { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(dep)); - const importedModule = - /** @type {Module} */ - (moduleGraph.getModule(dep)); - const exportsType = importedModule.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ - (selfModule.buildMeta).strictHarmonyModule - ); - if ( - (exportsType === "default-only" || - exportsType === "default-with-named") && - ids.length >= 1 - ) { - prefixedIds = ids.slice(1); - } - } - - for (const { - id, - shorthand, - range - } of dep.referencedPropertiesInDestructuring) { - const concatedIds = prefixedIds.concat([id]); - const module = /** @type {Module} */ (moduleGraph.getModule(dep)); - const used = moduleGraph - .getExportsInfo(module) - .getUsedName(concatedIds, runtime); - if (!used) return; - const newName = used[used.length - 1]; - const name = concatedIds[concatedIds.length - 1]; - if (newName === name) continue; - - const comment = `${Template.toNormalComment(name)} `; - const key = comment + JSON.stringify(newName); - source.replace( - /** @type {Range} */ - (range)[0], - /** @type {Range} */ - (range)[1] - 1, - shorthand ? `${key}: ${name}` : `${key}` - ); - } - } - } - - /** - * @param {HarmonyImportSpecifierDependency} dep dependency - * @param {ReplaceSource} source source - * @param {DependencyTemplateContext} templateContext context - * @param {string[]} ids ids - * @returns {string} generated code - */ - _getCodeForIds(dep, source, templateContext, ids) { - const { moduleGraph, module, runtime, concatenationScope } = - templateContext; - const connection = moduleGraph.getConnection(dep); - let exportExpr; - if ( - connection && - concatenationScope && - concatenationScope.isModuleInScope(connection.module) - ) { - if (ids.length === 0) { - exportExpr = concatenationScope.createModuleReference( - connection.module, - { - asiSafe: dep.asiSafe - } - ); - } else if (dep.namespaceObjectAsContext && ids.length === 1) { - exportExpr = - concatenationScope.createModuleReference(connection.module, { - asiSafe: dep.asiSafe - }) + propertyAccess(ids); - } else { - exportExpr = concatenationScope.createModuleReference( - connection.module, - { - ids, - call: dep.call, - directImport: dep.directImport, - asiSafe: dep.asiSafe - } - ); - } - } else { - super.apply(dep, source, templateContext); - - const { runtimeTemplate, initFragments, runtimeRequirements } = - templateContext; - - exportExpr = runtimeTemplate.exportFromImport({ - moduleGraph, - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - request: dep.request, - exportName: ids, - originModule: module, - asiSafe: dep.shorthand ? true : dep.asiSafe, - isCall: dep.call, - callContext: !dep.directImport, - defaultInterop: true, - importVar: dep.getImportVar(moduleGraph), - initFragments, - runtime, - runtimeRequirements - }); - } - return exportExpr; - } -}; - -module.exports = HarmonyImportSpecifierDependency; diff --git a/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js b/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js deleted file mode 100644 index a3bbd98de82..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyModulesPlugin.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const HarmonyAcceptDependency = require("./HarmonyAcceptDependency"); -const HarmonyAcceptImportDependency = require("./HarmonyAcceptImportDependency"); -const HarmonyCompatibilityDependency = require("./HarmonyCompatibilityDependency"); -const HarmonyEvaluatedImportSpecifierDependency = require("./HarmonyEvaluatedImportSpecifierDependency"); -const HarmonyExportExpressionDependency = require("./HarmonyExportExpressionDependency"); -const HarmonyExportHeaderDependency = require("./HarmonyExportHeaderDependency"); -const HarmonyExportImportedSpecifierDependency = require("./HarmonyExportImportedSpecifierDependency"); -const HarmonyExportSpecifierDependency = require("./HarmonyExportSpecifierDependency"); -const HarmonyImportSideEffectDependency = require("./HarmonyImportSideEffectDependency"); -const HarmonyImportSpecifierDependency = require("./HarmonyImportSpecifierDependency"); - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const HarmonyDetectionParserPlugin = require("./HarmonyDetectionParserPlugin"); -const HarmonyExportDependencyParserPlugin = require("./HarmonyExportDependencyParserPlugin"); -const HarmonyImportDependencyParserPlugin = require("./HarmonyImportDependencyParserPlugin"); -const HarmonyTopLevelThisParserPlugin = require("./HarmonyTopLevelThisParserPlugin"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "HarmonyModulesPlugin"; - -/** @typedef {{ topLevelAwait?: boolean }} HarmonyModulesPluginOptions */ - -class HarmonyModulesPlugin { - /** - * @param {HarmonyModulesPluginOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyTemplates.set( - HarmonyCompatibilityDependency, - new HarmonyCompatibilityDependency.Template() - ); - - compilation.dependencyFactories.set( - HarmonyImportSideEffectDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - HarmonyImportSideEffectDependency, - new HarmonyImportSideEffectDependency.Template() - ); - - compilation.dependencyFactories.set( - HarmonyImportSpecifierDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - HarmonyImportSpecifierDependency, - new HarmonyImportSpecifierDependency.Template() - ); - - compilation.dependencyFactories.set( - HarmonyEvaluatedImportSpecifierDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - HarmonyEvaluatedImportSpecifierDependency, - new HarmonyEvaluatedImportSpecifierDependency.Template() - ); - - compilation.dependencyTemplates.set( - HarmonyExportHeaderDependency, - new HarmonyExportHeaderDependency.Template() - ); - - compilation.dependencyTemplates.set( - HarmonyExportExpressionDependency, - new HarmonyExportExpressionDependency.Template() - ); - - compilation.dependencyTemplates.set( - HarmonyExportSpecifierDependency, - new HarmonyExportSpecifierDependency.Template() - ); - - compilation.dependencyFactories.set( - HarmonyExportImportedSpecifierDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - HarmonyExportImportedSpecifierDependency, - new HarmonyExportImportedSpecifierDependency.Template() - ); - - compilation.dependencyTemplates.set( - HarmonyAcceptDependency, - new HarmonyAcceptDependency.Template() - ); - - compilation.dependencyFactories.set( - HarmonyAcceptImportDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - HarmonyAcceptImportDependency, - new HarmonyAcceptImportDependency.Template() - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - // TODO webpack 6: rename harmony to esm or module - if (parserOptions.harmony !== undefined && !parserOptions.harmony) - return; - - new HarmonyDetectionParserPlugin(this.options).apply(parser); - new HarmonyImportDependencyParserPlugin(parserOptions).apply(parser); - new HarmonyExportDependencyParserPlugin(parserOptions).apply(parser); - new HarmonyTopLevelThisParserPlugin().apply(parser); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, handler); - } - ); - } -} -module.exports = HarmonyModulesPlugin; diff --git a/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js b/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js deleted file mode 100644 index b8ba1848649..00000000000 --- a/webpack-lib/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const ConstDependency = require("./ConstDependency"); -const HarmonyExports = require("./HarmonyExports"); - -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class HarmonyTopLevelThisParserPlugin { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.expression - .for("this") - .tap("HarmonyTopLevelThisParserPlugin", node => { - if (!parser.scope.topLevelScope) return; - if (HarmonyExports.isEnabled(parser.state)) { - const dep = new ConstDependency( - "undefined", - /** @type {Range} */ (node.range), - null - ); - dep.loc = /** @type {DependencyLocation} */ (node.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - }); - } -} - -module.exports = HarmonyTopLevelThisParserPlugin; diff --git a/webpack-lib/lib/dependencies/ImportContextDependency.js b/webpack-lib/lib/dependencies/ImportContextDependency.js deleted file mode 100644 index a1811e722bc..00000000000 --- a/webpack-lib/lib/dependencies/ImportContextDependency.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); -const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ImportContextDependency extends ContextDependency { - /** - * @param {TODO} options options - * @param {Range} range range - * @param {Range} valueRange value range - */ - constructor(options, range, valueRange) { - super(options); - - this.range = range; - this.valueRange = valueRange; - } - - get type() { - return `import() context ${this.options.mode}`; - } - - get category() { - return "esm"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.valueRange); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.valueRange = read(); - - super.deserialize(context); - } -} - -makeSerializable( - ImportContextDependency, - "webpack/lib/dependencies/ImportContextDependency" -); - -ImportContextDependency.Template = ContextDependencyTemplateAsRequireCall; - -module.exports = ImportContextDependency; diff --git a/webpack-lib/lib/dependencies/ImportDependency.js b/webpack-lib/lib/dependencies/ImportDependency.js deleted file mode 100644 index 1368d405a10..00000000000 --- a/webpack-lib/lib/dependencies/ImportDependency.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class ImportDependency extends ModuleDependency { - /** - * @param {string} request the request - * @param {Range} range expression range - * @param {(string[][] | null)=} referencedExports list of referenced exports - * @param {ImportAttributes=} attributes import attributes - */ - constructor(request, range, referencedExports, attributes) { - super(request); - this.range = range; - this.referencedExports = referencedExports; - this.assertions = attributes; - } - - get type() { - return "import()"; - } - - get category() { - return "esm"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - if (!this.referencedExports) return Dependency.EXPORTS_OBJECT_REFERENCED; - const refs = []; - for (const referencedExport of this.referencedExports) { - if (referencedExport[0] === "default") { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(this)); - const importedModule = - /** @type {Module} */ - (moduleGraph.getModule(this)); - const exportsType = importedModule.getExportsType( - moduleGraph, - /** @type {BuildMeta} */ - (selfModule.buildMeta).strictHarmonyModule - ); - if ( - exportsType === "default-only" || - exportsType === "default-with-named" - ) { - return Dependency.EXPORTS_OBJECT_REFERENCED; - } - } - refs.push({ - name: referencedExport, - canMangle: false - }); - } - return refs; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - context.write(this.range); - context.write(this.referencedExports); - context.write(this.assertions); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - this.range = context.read(); - this.referencedExports = context.read(); - this.assertions = context.read(); - super.deserialize(context); - } -} - -makeSerializable(ImportDependency, "webpack/lib/dependencies/ImportDependency"); - -ImportDependency.Template = class ImportDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, - { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ImportDependency} */ (dependency); - const block = /** @type {AsyncDependenciesBlock} */ ( - moduleGraph.getParentBlock(dep) - ); - const content = runtimeTemplate.moduleNamespacePromise({ - chunkGraph, - block, - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - request: dep.request, - strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, - message: "import()", - runtimeRequirements - }); - - source.replace(dep.range[0], dep.range[1] - 1, content); - } -}; - -module.exports = ImportDependency; diff --git a/webpack-lib/lib/dependencies/ImportEagerDependency.js b/webpack-lib/lib/dependencies/ImportEagerDependency.js deleted file mode 100644 index dd607302029..00000000000 --- a/webpack-lib/lib/dependencies/ImportEagerDependency.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ImportDependency = require("./ImportDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ImportEagerDependency extends ImportDependency { - /** - * @param {string} request the request - * @param {Range} range expression range - * @param {(string[][] | null)=} referencedExports list of referenced exports - * @param {ImportAttributes=} attributes import attributes - */ - constructor(request, range, referencedExports, attributes) { - super(request, range, referencedExports, attributes); - } - - get type() { - return "import() eager"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ImportEagerDependency, - "webpack/lib/dependencies/ImportEagerDependency" -); - -ImportEagerDependency.Template = class ImportEagerDependencyTemplate extends ( - ImportDependency.Template -) { - /** - * @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, - { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ImportEagerDependency} */ (dependency); - const content = runtimeTemplate.moduleNamespacePromise({ - chunkGraph, - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - request: dep.request, - strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, - message: "import() eager", - runtimeRequirements - }); - - source.replace(dep.range[0], dep.range[1] - 1, content); - } -}; - -module.exports = ImportEagerDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextDependency.js b/webpack-lib/lib/dependencies/ImportMetaContextDependency.js deleted file mode 100644 index ee27ee1573f..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaContextDependency.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); -const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ - -class ImportMetaContextDependency extends ContextDependency { - /** - * @param {ContextDependencyOptions} options options - * @param {Range} range range - */ - constructor(options, range) { - super(options); - - this.range = range; - } - - get category() { - return "esm"; - } - - get type() { - return `import.meta.webpackContext ${this.options.mode}`; - } -} - -makeSerializable( - ImportMetaContextDependency, - "webpack/lib/dependencies/ImportMetaContextDependency" -); - -ImportMetaContextDependency.Template = ModuleDependencyTemplateAsRequireId; - -module.exports = ImportMetaContextDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js b/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js deleted file mode 100644 index 753f0430e8d..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +++ /dev/null @@ -1,301 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); -const { - evaluateToIdentifier -} = require("../javascript/JavascriptParserHelpers"); -const ImportMetaContextDependency = require("./ImportMetaContextDependency"); - -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").ObjectExpression} ObjectExpression */ -/** @typedef {import("estree").Property} Property */ -/** @typedef {import("estree").SourceLocation} SourceLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../ContextModule").ContextModuleOptions} ContextModuleOptions */ -/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {Pick&{groupOptions: RawChunkGroupOptions, exports?: ContextModuleOptions["referencedExports"]}} ImportMetaContextOptions */ - -/** - * @param {TODO} prop property - * @param {string} expect except message - * @returns {WebpackError} error - */ -function createPropertyParseError(prop, expect) { - return createError( - `Parsing import.meta.webpackContext options failed. Unknown value for property ${JSON.stringify( - prop.key.name - )}, expected type ${expect}.`, - prop.value.loc - ); -} - -/** - * @param {string} msg message - * @param {DependencyLocation} loc location - * @returns {WebpackError} error - */ -function createError(msg, loc) { - const error = new WebpackError(msg); - error.name = "ImportMetaContextError"; - error.loc = loc; - return error; -} - -module.exports = class ImportMetaContextDependencyParserPlugin { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.evaluateIdentifier - .for("import.meta.webpackContext") - .tap("ImportMetaContextDependencyParserPlugin", expr => - evaluateToIdentifier( - "import.meta.webpackContext", - "import.meta", - () => ["webpackContext"], - true - )(expr) - ); - parser.hooks.call - .for("import.meta.webpackContext") - .tap("ImportMetaContextDependencyParserPlugin", expr => { - if (expr.arguments.length < 1 || expr.arguments.length > 2) return; - const [directoryNode, optionsNode] = expr.arguments; - if (optionsNode && optionsNode.type !== "ObjectExpression") return; - const requestExpr = parser.evaluateExpression( - /** @type {Expression} */ (directoryNode) - ); - if (!requestExpr.isString()) return; - const request = /** @type {string} */ (requestExpr.string); - const errors = []; - let regExp = /^\.\/.*$/; - let recursive = true; - /** @type {ContextModuleOptions["mode"]} */ - let mode = "sync"; - /** @type {ContextModuleOptions["include"]} */ - let include; - /** @type {ContextModuleOptions["exclude"]} */ - let exclude; - /** @type {RawChunkGroupOptions} */ - const groupOptions = {}; - /** @type {ContextModuleOptions["chunkName"]} */ - let chunkName; - /** @type {ContextModuleOptions["referencedExports"]} */ - let exports; - if (optionsNode) { - for (const prop of /** @type {ObjectExpression} */ (optionsNode) - .properties) { - if (prop.type !== "Property" || prop.key.type !== "Identifier") { - errors.push( - createError( - "Parsing import.meta.webpackContext options failed.", - /** @type {DependencyLocation} */ (optionsNode.loc) - ) - ); - break; - } - switch (prop.key.name) { - case "regExp": { - const regExpExpr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!regExpExpr.isRegExp()) { - errors.push(createPropertyParseError(prop, "RegExp")); - } else { - regExp = /** @type {RegExp} */ (regExpExpr.regExp); - } - break; - } - case "include": { - const regExpExpr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!regExpExpr.isRegExp()) { - errors.push(createPropertyParseError(prop, "RegExp")); - } else { - include = regExpExpr.regExp; - } - break; - } - case "exclude": { - const regExpExpr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!regExpExpr.isRegExp()) { - errors.push(createPropertyParseError(prop, "RegExp")); - } else { - exclude = regExpExpr.regExp; - } - break; - } - case "mode": { - const modeExpr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!modeExpr.isString()) { - errors.push(createPropertyParseError(prop, "string")); - } else { - mode = /** @type {ContextModuleOptions["mode"]} */ ( - modeExpr.string - ); - } - break; - } - case "chunkName": { - const expr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!expr.isString()) { - errors.push(createPropertyParseError(prop, "string")); - } else { - chunkName = expr.string; - } - break; - } - case "exports": { - const expr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (expr.isString()) { - exports = [[/** @type {string} */ (expr.string)]]; - } else if (expr.isArray()) { - const items = - /** @type {BasicEvaluatedExpression[]} */ - (expr.items); - if ( - items.every(i => { - if (!i.isArray()) return false; - const innerItems = - /** @type {BasicEvaluatedExpression[]} */ (i.items); - return innerItems.every(i => i.isString()); - }) - ) { - exports = []; - for (const i1 of items) { - /** @type {string[]} */ - const export_ = []; - for (const i2 of /** @type {BasicEvaluatedExpression[]} */ ( - i1.items - )) { - export_.push(/** @type {string} */ (i2.string)); - } - exports.push(export_); - } - } else { - errors.push( - createPropertyParseError(prop, "string|string[][]") - ); - } - } else { - errors.push( - createPropertyParseError(prop, "string|string[][]") - ); - } - break; - } - case "prefetch": { - const expr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (expr.isBoolean()) { - groupOptions.prefetchOrder = 0; - } else if (expr.isNumber()) { - groupOptions.prefetchOrder = expr.number; - } else { - errors.push(createPropertyParseError(prop, "boolean|number")); - } - break; - } - case "preload": { - const expr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (expr.isBoolean()) { - groupOptions.preloadOrder = 0; - } else if (expr.isNumber()) { - groupOptions.preloadOrder = expr.number; - } else { - errors.push(createPropertyParseError(prop, "boolean|number")); - } - break; - } - case "fetchPriority": { - const expr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if ( - expr.isString() && - ["high", "low", "auto"].includes( - /** @type {string} */ (expr.string) - ) - ) { - groupOptions.fetchPriority = - /** @type {RawChunkGroupOptions["fetchPriority"]} */ ( - expr.string - ); - } else { - errors.push( - createPropertyParseError(prop, '"high"|"low"|"auto"') - ); - } - break; - } - case "recursive": { - const recursiveExpr = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (!recursiveExpr.isBoolean()) { - errors.push(createPropertyParseError(prop, "boolean")); - } else { - recursive = /** @type {boolean} */ (recursiveExpr.bool); - } - break; - } - default: - errors.push( - createError( - `Parsing import.meta.webpackContext options failed. Unknown property ${JSON.stringify( - prop.key.name - )}.`, - /** @type {DependencyLocation} */ (optionsNode.loc) - ) - ); - } - } - } - if (errors.length) { - for (const error of errors) parser.state.current.addError(error); - return; - } - - const dep = new ImportMetaContextDependency( - { - request, - include, - exclude, - recursive, - regExp, - groupOptions, - chunkName, - referencedExports: exports, - mode, - category: "esm" - }, - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - }); - } -}; diff --git a/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js b/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js deleted file mode 100644 index ed9ac05da53..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaContextPlugin.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const ContextElementDependency = require("./ContextElementDependency"); -const ImportMetaContextDependency = require("./ImportMetaContextDependency"); -const ImportMetaContextDependencyParserPlugin = require("./ImportMetaContextDependencyParserPlugin"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "ImportMetaContextPlugin"; - -class ImportMetaContextPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { contextModuleFactory, normalModuleFactory }) => { - compilation.dependencyFactories.set( - ImportMetaContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - ImportMetaContextDependency, - new ImportMetaContextDependency.Template() - ); - compilation.dependencyFactories.set( - ContextElementDependency, - normalModuleFactory - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if ( - parserOptions.importMetaContext !== undefined && - !parserOptions.importMetaContext - ) - return; - - new ImportMetaContextDependencyParserPlugin().apply(parser); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, handler); - } - ); - } -} - -module.exports = ImportMetaContextPlugin; diff --git a/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js b/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js deleted file mode 100644 index 70d8199338d..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaHotAcceptDependency.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ImportMetaHotAcceptDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - this.range = range; - this.weak = true; - } - - get type() { - return "import.meta.webpackHot.accept"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ImportMetaHotAcceptDependency, - "webpack/lib/dependencies/ImportMetaHotAcceptDependency" -); - -ImportMetaHotAcceptDependency.Template = ModuleDependencyTemplateAsId; - -module.exports = ImportMetaHotAcceptDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js b/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js deleted file mode 100644 index c6c35a250ce..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaHotDeclineDependency.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ImportMetaHotDeclineDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - - this.range = range; - this.weak = true; - } - - get type() { - return "import.meta.webpackHot.decline"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ImportMetaHotDeclineDependency, - "webpack/lib/dependencies/ImportMetaHotDeclineDependency" -); - -ImportMetaHotDeclineDependency.Template = ModuleDependencyTemplateAsId; - -module.exports = ImportMetaHotDeclineDependency; diff --git a/webpack-lib/lib/dependencies/ImportMetaPlugin.js b/webpack-lib/lib/dependencies/ImportMetaPlugin.js deleted file mode 100644 index ff9231d21d0..00000000000 --- a/webpack-lib/lib/dependencies/ImportMetaPlugin.js +++ /dev/null @@ -1,253 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const { pathToFileURL } = require("url"); -const ModuleDependencyWarning = require("../ModuleDependencyWarning"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const Template = require("../Template"); -const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); -const { - evaluateToIdentifier, - toConstantDependency, - evaluateToString, - evaluateToNumber -} = require("../javascript/JavascriptParserHelpers"); -const memoize = require("../util/memoize"); -const propertyAccess = require("../util/propertyAccess"); -const ConstDependency = require("./ConstDependency"); - -/** @typedef {import("estree").MemberExpression} MemberExpression */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const getCriticalDependencyWarning = memoize(() => - require("./CriticalDependencyWarning") -); - -const PLUGIN_NAME = "ImportMetaPlugin"; - -class ImportMetaPlugin { - /** - * @param {Compiler} compiler compiler - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - /** - * @param {NormalModule} module module - * @returns {string} file url - */ - const getUrl = module => pathToFileURL(module.resource).toString(); - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const parserHandler = (parser, { importMeta }) => { - if (importMeta === false) { - const { importMetaName } = compilation.outputOptions; - if (importMetaName === "import.meta") return; - - parser.hooks.expression - .for("import.meta") - .tap(PLUGIN_NAME, metaProperty => { - const dep = new ConstDependency( - /** @type {string} */ (importMetaName), - /** @type {Range} */ (metaProperty.range) - ); - dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - return; - } - - // import.meta direct - const webpackVersion = Number.parseInt( - require("../../package.json").version, - 10 - ); - const importMetaUrl = () => - JSON.stringify(getUrl(parser.state.module)); - const importMetaWebpackVersion = () => JSON.stringify(webpackVersion); - /** - * @param {string[]} members members - * @returns {string} error message - */ - const importMetaUnknownProperty = members => - `${Template.toNormalComment( - `unsupported import.meta.${members.join(".")}` - )} undefined${propertyAccess(members, 1)}`; - parser.hooks.typeof - .for("import.meta") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("object")) - ); - parser.hooks.expression - .for("import.meta") - .tap(PLUGIN_NAME, metaProperty => { - const referencedPropertiesInDestructuring = - parser.destructuringAssignmentPropertiesFor(metaProperty); - if (!referencedPropertiesInDestructuring) { - const CriticalDependencyWarning = - getCriticalDependencyWarning(); - parser.state.module.addWarning( - new ModuleDependencyWarning( - parser.state.module, - new CriticalDependencyWarning( - "Accessing import.meta directly is unsupported (only property access or destructuring is supported)" - ), - /** @type {DependencyLocation} */ (metaProperty.loc) - ) - ); - const dep = new ConstDependency( - `${ - parser.isAsiPosition( - /** @type {Range} */ (metaProperty.range)[0] - ) - ? ";" - : "" - }({})`, - /** @type {Range} */ (metaProperty.range) - ); - dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - } - - let str = ""; - for (const { id: prop } of referencedPropertiesInDestructuring) { - switch (prop) { - case "url": - str += `url: ${importMetaUrl()},`; - break; - case "webpack": - str += `webpack: ${importMetaWebpackVersion()},`; - break; - default: - str += `[${JSON.stringify( - prop - )}]: ${importMetaUnknownProperty([prop])},`; - break; - } - } - const dep = new ConstDependency( - `({${str}})`, - /** @type {Range} */ (metaProperty.range) - ); - dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - parser.hooks.evaluateTypeof - .for("import.meta") - .tap(PLUGIN_NAME, evaluateToString("object")); - parser.hooks.evaluateIdentifier.for("import.meta").tap( - PLUGIN_NAME, - evaluateToIdentifier("import.meta", "import.meta", () => [], true) - ); - - // import.meta.url - parser.hooks.typeof - .for("import.meta.url") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("string")) - ); - parser.hooks.expression - .for("import.meta.url") - .tap(PLUGIN_NAME, expr => { - const dep = new ConstDependency( - importMetaUrl(), - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - parser.hooks.evaluateTypeof - .for("import.meta.url") - .tap(PLUGIN_NAME, evaluateToString("string")); - parser.hooks.evaluateIdentifier - .for("import.meta.url") - .tap(PLUGIN_NAME, expr => - new BasicEvaluatedExpression() - .setString(getUrl(parser.state.module)) - .setRange(/** @type {Range} */ (expr.range)) - ); - - // import.meta.webpack - parser.hooks.typeof - .for("import.meta.webpack") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("number")) - ); - parser.hooks.expression - .for("import.meta.webpack") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, importMetaWebpackVersion()) - ); - parser.hooks.evaluateTypeof - .for("import.meta.webpack") - .tap(PLUGIN_NAME, evaluateToString("number")); - parser.hooks.evaluateIdentifier - .for("import.meta.webpack") - .tap(PLUGIN_NAME, evaluateToNumber(webpackVersion)); - - // Unknown properties - parser.hooks.unhandledExpressionMemberChain - .for("import.meta") - .tap(PLUGIN_NAME, (expr, members) => { - const dep = new ConstDependency( - importMetaUnknownProperty(members), - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - parser.hooks.evaluate - .for("MemberExpression") - .tap(PLUGIN_NAME, expression => { - const expr = /** @type {MemberExpression} */ (expression); - if ( - expr.object.type === "MetaProperty" && - expr.object.meta.name === "import" && - expr.object.property.name === "meta" && - expr.property.type === - (expr.computed ? "Literal" : "Identifier") - ) { - return new BasicEvaluatedExpression() - .setUndefined() - .setRange(/** @type {Range} */ (expr.range)); - } - }); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, parserHandler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, parserHandler); - } - ); - } -} - -module.exports = ImportMetaPlugin; diff --git a/webpack-lib/lib/dependencies/ImportParserPlugin.js b/webpack-lib/lib/dependencies/ImportParserPlugin.js deleted file mode 100644 index 52fdb9317ca..00000000000 --- a/webpack-lib/lib/dependencies/ImportParserPlugin.js +++ /dev/null @@ -1,339 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const { getImportAttributes } = require("../javascript/JavascriptParser"); -const ContextDependencyHelpers = require("./ContextDependencyHelpers"); -const ImportContextDependency = require("./ImportContextDependency"); -const ImportDependency = require("./ImportDependency"); -const ImportEagerDependency = require("./ImportEagerDependency"); -const ImportWeakDependency = require("./ImportWeakDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ -/** @typedef {import("../ContextModule").ContextMode} ContextMode */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").ImportExpression} ImportExpression */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ImportParserPlugin { - /** - * @param {JavascriptParserOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - /** - * @template T - * @param {Iterable} enumerable enumerable - * @returns {T[][]} array of array - */ - const exportsFromEnumerable = enumerable => - Array.from(enumerable, e => [e]); - parser.hooks.importCall.tap("ImportParserPlugin", expr => { - const param = parser.evaluateExpression(expr.source); - - let chunkName = null; - let mode = /** @type {ContextMode} */ (this.options.dynamicImportMode); - let include = null; - let exclude = null; - /** @type {string[][] | null} */ - let exports = null; - /** @type {RawChunkGroupOptions} */ - const groupOptions = {}; - - const { - dynamicImportPreload, - dynamicImportPrefetch, - dynamicImportFetchPriority - } = this.options; - if (dynamicImportPreload !== undefined && dynamicImportPreload !== false) - groupOptions.preloadOrder = - dynamicImportPreload === true ? 0 : dynamicImportPreload; - if ( - dynamicImportPrefetch !== undefined && - dynamicImportPrefetch !== false - ) - groupOptions.prefetchOrder = - dynamicImportPrefetch === true ? 0 : dynamicImportPrefetch; - if ( - dynamicImportFetchPriority !== undefined && - dynamicImportFetchPriority !== false - ) - groupOptions.fetchPriority = dynamicImportFetchPriority; - - const { options: importOptions, errors: commentErrors } = - parser.parseCommentOptions(/** @type {Range} */ (expr.range)); - - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - parser.state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - /** @type {DependencyLocation} */ (comment.loc) - ) - ); - } - } - - if (importOptions) { - if (importOptions.webpackIgnore !== undefined) { - if (typeof importOptions.webpackIgnore !== "boolean") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else if (importOptions.webpackIgnore) { - // Do not instrument `import()` if `webpackIgnore` is `true` - return false; - } - } - if (importOptions.webpackChunkName !== undefined) { - if (typeof importOptions.webpackChunkName !== "string") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - chunkName = importOptions.webpackChunkName; - } - } - if (importOptions.webpackMode !== undefined) { - if (typeof importOptions.webpackMode !== "string") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackMode\` expected a string, but received: ${importOptions.webpackMode}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - mode = /** @type {ContextMode} */ (importOptions.webpackMode); - } - } - if (importOptions.webpackPrefetch !== undefined) { - if (importOptions.webpackPrefetch === true) { - groupOptions.prefetchOrder = 0; - } else if (typeof importOptions.webpackPrefetch === "number") { - groupOptions.prefetchOrder = importOptions.webpackPrefetch; - } else { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackPrefetch\` expected true or a number, but received: ${importOptions.webpackPrefetch}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - } - if (importOptions.webpackPreload !== undefined) { - if (importOptions.webpackPreload === true) { - groupOptions.preloadOrder = 0; - } else if (typeof importOptions.webpackPreload === "number") { - groupOptions.preloadOrder = importOptions.webpackPreload; - } else { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackPreload\` expected true or a number, but received: ${importOptions.webpackPreload}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - } - if (importOptions.webpackFetchPriority !== undefined) { - if ( - typeof importOptions.webpackFetchPriority === "string" && - ["high", "low", "auto"].includes(importOptions.webpackFetchPriority) - ) { - groupOptions.fetchPriority = - /** @type {"low" | "high" | "auto"} */ - (importOptions.webpackFetchPriority); - } else { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackFetchPriority\` expected true or "low", "high" or "auto", but received: ${importOptions.webpackFetchPriority}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - } - if (importOptions.webpackInclude !== undefined) { - if ( - !importOptions.webpackInclude || - !(importOptions.webpackInclude instanceof RegExp) - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackInclude\` expected a regular expression, but received: ${importOptions.webpackInclude}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - include = importOptions.webpackInclude; - } - } - if (importOptions.webpackExclude !== undefined) { - if ( - !importOptions.webpackExclude || - !(importOptions.webpackExclude instanceof RegExp) - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackExclude\` expected a regular expression, but received: ${importOptions.webpackExclude}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - exclude = importOptions.webpackExclude; - } - } - if (importOptions.webpackExports !== undefined) { - if ( - !( - typeof importOptions.webpackExports === "string" || - (Array.isArray(importOptions.webpackExports) && - /** @type {string[]} */ (importOptions.webpackExports).every( - item => typeof item === "string" - )) - ) - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else if (typeof importOptions.webpackExports === "string") { - exports = [[importOptions.webpackExports]]; - } else { - exports = exportsFromEnumerable(importOptions.webpackExports); - } - } - } - - if ( - mode !== "lazy" && - mode !== "lazy-once" && - mode !== "eager" && - mode !== "weak" - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackMode\` expected 'lazy', 'lazy-once', 'eager' or 'weak', but received: ${mode}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - mode = "lazy"; - } - - const referencedPropertiesInDestructuring = - parser.destructuringAssignmentPropertiesFor(expr); - if (referencedPropertiesInDestructuring) { - if (exports) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - "`webpackExports` could not be used with destructuring assignment.", - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - exports = exportsFromEnumerable( - [...referencedPropertiesInDestructuring].map(({ id }) => id) - ); - } - - if (param.isString()) { - const attributes = getImportAttributes(expr); - - if (mode === "eager") { - const dep = new ImportEagerDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.range), - exports, - attributes - ); - parser.state.current.addDependency(dep); - } else if (mode === "weak") { - const dep = new ImportWeakDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.range), - exports, - attributes - ); - parser.state.current.addDependency(dep); - } else { - const depBlock = new AsyncDependenciesBlock( - { - ...groupOptions, - name: chunkName - }, - /** @type {DependencyLocation} */ (expr.loc), - param.string - ); - const dep = new ImportDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.range), - exports, - attributes - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - depBlock.addDependency(dep); - parser.state.current.addBlock(depBlock); - } - return true; - } - if (mode === "weak") { - mode = "async-weak"; - } - const dep = ContextDependencyHelpers.create( - ImportContextDependency, - /** @type {Range} */ (expr.range), - param, - expr, - this.options, - { - chunkName, - groupOptions, - include, - exclude, - mode, - namespaceObject: /** @type {BuildMeta} */ ( - parser.state.module.buildMeta - ).strictHarmonyModule - ? "strict" - : true, - typePrefix: "import()", - category: "esm", - referencedExports: exports, - attributes: getImportAttributes(expr) - }, - parser - ); - if (!dep) return; - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - }); - } -} - -module.exports = ImportParserPlugin; diff --git a/webpack-lib/lib/dependencies/ImportPlugin.js b/webpack-lib/lib/dependencies/ImportPlugin.js deleted file mode 100644 index 4ee51a8f748..00000000000 --- a/webpack-lib/lib/dependencies/ImportPlugin.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - 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 ImportContextDependency = require("./ImportContextDependency"); -const ImportDependency = require("./ImportDependency"); -const ImportEagerDependency = require("./ImportEagerDependency"); -const ImportParserPlugin = require("./ImportParserPlugin"); -const ImportWeakDependency = require("./ImportWeakDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "ImportPlugin"; - -class ImportPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { contextModuleFactory, normalModuleFactory }) => { - compilation.dependencyFactories.set( - ImportDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - ImportDependency, - new ImportDependency.Template() - ); - - compilation.dependencyFactories.set( - ImportEagerDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - ImportEagerDependency, - new ImportEagerDependency.Template() - ); - - compilation.dependencyFactories.set( - ImportWeakDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - ImportWeakDependency, - new ImportWeakDependency.Template() - ); - - compilation.dependencyFactories.set( - ImportContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - ImportContextDependency, - new ImportContextDependency.Template() - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.import !== undefined && !parserOptions.import) - return; - - new ImportParserPlugin(parserOptions).apply(parser); - }; - - 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 = ImportPlugin; diff --git a/webpack-lib/lib/dependencies/ImportWeakDependency.js b/webpack-lib/lib/dependencies/ImportWeakDependency.js deleted file mode 100644 index 0ed3b053f96..00000000000 --- a/webpack-lib/lib/dependencies/ImportWeakDependency.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ImportDependency = require("./ImportDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ImportWeakDependency extends ImportDependency { - /** - * @param {string} request the request - * @param {Range} range expression range - * @param {(string[][] | null)=} referencedExports list of referenced exports - * @param {ImportAttributes=} attributes import attributes - */ - constructor(request, range, referencedExports, attributes) { - super(request, range, referencedExports, attributes); - this.weak = true; - } - - get type() { - return "import() weak"; - } -} - -makeSerializable( - ImportWeakDependency, - "webpack/lib/dependencies/ImportWeakDependency" -); - -ImportWeakDependency.Template = class ImportDependencyTemplate extends ( - ImportDependency.Template -) { - /** - * @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, - { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ImportWeakDependency} */ (dependency); - const content = runtimeTemplate.moduleNamespacePromise({ - chunkGraph, - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - request: dep.request, - strict: /** @type {BuildMeta} */ (module.buildMeta).strictHarmonyModule, - message: "import() weak", - weak: true, - runtimeRequirements - }); - - source.replace(dep.range[0], dep.range[1] - 1, content); - } -}; - -module.exports = ImportWeakDependency; diff --git a/webpack-lib/lib/dependencies/JsonExportsDependency.js b/webpack-lib/lib/dependencies/JsonExportsDependency.js deleted file mode 100644 index 07d022b90a4..00000000000 --- a/webpack-lib/lib/dependencies/JsonExportsDependency.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ExportSpec} ExportSpec */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../json/JsonData")} JsonData */ -/** @typedef {import("../json/JsonData").RawJsonData} RawJsonData */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -/** - * @param {number} exportsDepth exportsDepth - * @returns {((data: RawJsonData, curDepth?: number) => ExportSpec[] | undefined)} value - */ -const getExportsWithDepth = exportsDepth => - function getExportsFromData(data, curDepth = 1) { - if (curDepth > exportsDepth) return undefined; - if (data && typeof data === "object") { - if (Array.isArray(data)) { - return data.length < 100 - ? data.map((item, idx) => ({ - name: `${idx}`, - canMangle: true, - exports: getExportsFromData(item, curDepth + 1) - })) - : undefined; - } - const exports = []; - for (const key of Object.keys(data)) { - exports.push({ - name: key, - canMangle: true, - exports: getExportsFromData(data[key], curDepth + 1) - }); - } - return exports; - } - return undefined; - }; - -class JsonExportsDependency extends NullDependency { - /** - * @param {JsonData} data json data - * @param {number} exportsDepth the depth of json exports to analyze - */ - constructor(data, exportsDepth) { - super(); - this.data = data; - this.exportsDepth = exportsDepth; - } - - get type() { - return "json exports"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - return { - exports: getExportsWithDepth(this.exportsDepth)( - this.data && /** @type {RawJsonData} */ (this.data.get()) - ), - dependencies: undefined - }; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - this.data.updateHash(hash); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.data); - write(this.exportsDepth); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.data = read(); - this.exportsDepth = read(); - super.deserialize(context); - } -} - -makeSerializable( - JsonExportsDependency, - "webpack/lib/dependencies/JsonExportsDependency" -); - -module.exports = JsonExportsDependency; diff --git a/webpack-lib/lib/dependencies/LoaderDependency.js b/webpack-lib/lib/dependencies/LoaderDependency.js deleted file mode 100644 index 7ae66b3d2b0..00000000000 --- a/webpack-lib/lib/dependencies/LoaderDependency.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class LoaderDependency extends ModuleDependency { - /** - * @param {string} request request string - */ - constructor(request) { - super(request); - } - - get type() { - return "loader"; - } - - get category() { - return "loader"; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return false; - } -} - -module.exports = LoaderDependency; diff --git a/webpack-lib/lib/dependencies/LoaderImportDependency.js b/webpack-lib/lib/dependencies/LoaderImportDependency.js deleted file mode 100644 index 94937922d60..00000000000 --- a/webpack-lib/lib/dependencies/LoaderImportDependency.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class LoaderImportDependency extends ModuleDependency { - /** - * @param {string} request request string - */ - constructor(request) { - super(request); - this.weak = true; - } - - get type() { - return "loader import"; - } - - get category() { - return "loaderImport"; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return false; - } -} - -module.exports = LoaderImportDependency; diff --git a/webpack-lib/lib/dependencies/LoaderPlugin.js b/webpack-lib/lib/dependencies/LoaderPlugin.js deleted file mode 100644 index 3612cbeac8c..00000000000 --- a/webpack-lib/lib/dependencies/LoaderPlugin.js +++ /dev/null @@ -1,292 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NormalModule = require("../NormalModule"); -const LazySet = require("../util/LazySet"); -const LoaderDependency = require("./LoaderDependency"); -const LoaderImportDependency = require("./LoaderImportDependency"); - -/** @typedef {import("../../declarations/LoaderContext").LoaderPluginLoaderContext} LoaderPluginLoaderContext */ -/** @typedef {import("../Compilation").DepConstructor} DepConstructor */ -/** @typedef {import("../Compilation").ExecuteModuleResult} ExecuteModuleResult */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ - -/** - * @callback ImportModuleCallback - * @param {(Error | null)=} err error object - * @param {any=} exports exports of the evaluated module - */ - -/** - * @typedef {object} ImportModuleOptions - * @property {string=} layer the target layer - * @property {string=} publicPath the target public path - * @property {string=} baseUri target base uri - */ - -class LoaderPlugin { - /** - * @param {object} options options - */ - constructor(options = {}) {} - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "LoaderPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - LoaderDependency, - normalModuleFactory - ); - compilation.dependencyFactories.set( - LoaderImportDependency, - normalModuleFactory - ); - } - ); - - compiler.hooks.compilation.tap("LoaderPlugin", compilation => { - const moduleGraph = compilation.moduleGraph; - NormalModule.getCompilationHooks(compilation).loader.tap( - "LoaderPlugin", - loaderContext => { - loaderContext.loadModule = (request, callback) => { - const dep = new LoaderDependency(request); - dep.loc = { - name: request - }; - const factory = compilation.dependencyFactories.get( - /** @type {DepConstructor} */ (dep.constructor) - ); - if (factory === undefined) { - return callback( - new Error( - `No module factory available for dependency type: ${dep.constructor.name}` - ) - ); - } - const oldFactorizeQueueContext = - compilation.factorizeQueue.getContext(); - compilation.factorizeQueue.setContext("load-module"); - const oldAddModuleQueueContext = - compilation.addModuleQueue.getContext(); - compilation.addModuleQueue.setContext("load-module"); - compilation.buildQueue.increaseParallelism(); - compilation.handleModuleCreation( - { - factory, - dependencies: [dep], - originModule: - /** @type {NormalModule} */ - (loaderContext._module), - context: loaderContext.context, - recursive: false - }, - err => { - compilation.factorizeQueue.setContext(oldFactorizeQueueContext); - compilation.addModuleQueue.setContext(oldAddModuleQueueContext); - compilation.buildQueue.decreaseParallelism(); - if (err) { - return callback(err); - } - const referencedModule = moduleGraph.getModule(dep); - if (!referencedModule) { - return callback(new Error("Cannot load the module")); - } - if (referencedModule.getNumberOfErrors() > 0) { - return callback( - new Error("The loaded module contains errors") - ); - } - const moduleSource = referencedModule.originalSource(); - if (!moduleSource) { - return callback( - new Error( - "The module created for a LoaderDependency must have an original source" - ) - ); - } - let map; - let source; - if (moduleSource.sourceAndMap) { - const sourceAndMap = moduleSource.sourceAndMap(); - map = sourceAndMap.map; - source = sourceAndMap.source; - } else { - map = moduleSource.map(); - source = moduleSource.source(); - } - const fileDependencies = new LazySet(); - const contextDependencies = new LazySet(); - const missingDependencies = new LazySet(); - const buildDependencies = new LazySet(); - referencedModule.addCacheDependencies( - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - ); - - for (const d of fileDependencies) { - loaderContext.addDependency(d); - } - for (const d of contextDependencies) { - loaderContext.addContextDependency(d); - } - for (const d of missingDependencies) { - loaderContext.addMissingDependency(d); - } - for (const d of buildDependencies) { - loaderContext.addBuildDependency(d); - } - return callback( - null, - source, - /** @type {object | null} */ (map), - referencedModule - ); - } - ); - }; - - /** - * @param {string} request the request string to load the module from - * @param {ImportModuleOptions} options options - * @param {ImportModuleCallback} callback callback returning the exports - * @returns {void} - */ - const importModule = (request, options, callback) => { - const dep = new LoaderImportDependency(request); - dep.loc = { - name: request - }; - const factory = compilation.dependencyFactories.get( - /** @type {DepConstructor} */ (dep.constructor) - ); - if (factory === undefined) { - return callback( - new Error( - `No module factory available for dependency type: ${dep.constructor.name}` - ) - ); - } - - const oldFactorizeQueueContext = - compilation.factorizeQueue.getContext(); - compilation.factorizeQueue.setContext("import-module"); - const oldAddModuleQueueContext = - compilation.addModuleQueue.getContext(); - compilation.addModuleQueue.setContext("import-module"); - compilation.buildQueue.increaseParallelism(); - compilation.handleModuleCreation( - { - factory, - dependencies: [dep], - originModule: - /** @type {NormalModule} */ - (loaderContext._module), - contextInfo: { - issuerLayer: options.layer - }, - context: loaderContext.context, - connectOrigin: false, - checkCycle: true - }, - err => { - compilation.factorizeQueue.setContext(oldFactorizeQueueContext); - compilation.addModuleQueue.setContext(oldAddModuleQueueContext); - compilation.buildQueue.decreaseParallelism(); - if (err) { - return callback(err); - } - const referencedModule = moduleGraph.getModule(dep); - if (!referencedModule) { - return callback(new Error("Cannot load the module")); - } - compilation.buildQueue.increaseParallelism(); - compilation.executeModule( - referencedModule, - { - entryOptions: { - baseUri: options.baseUri, - publicPath: options.publicPath - } - }, - (err, result) => { - compilation.buildQueue.decreaseParallelism(); - if (err) return callback(err); - const { - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies, - cacheable, - assets, - exports - } = /** @type {ExecuteModuleResult} */ (result); - for (const d of fileDependencies) { - loaderContext.addDependency(d); - } - for (const d of contextDependencies) { - loaderContext.addContextDependency(d); - } - for (const d of missingDependencies) { - loaderContext.addMissingDependency(d); - } - for (const d of buildDependencies) { - loaderContext.addBuildDependency(d); - } - if (cacheable === false) loaderContext.cacheable(false); - for (const [name, { source, info }] of assets) { - const buildInfo = - /** @type {BuildInfo} */ - ( - /** @type {NormalModule} */ (loaderContext._module) - .buildInfo - ); - if (!buildInfo.assets) { - buildInfo.assets = Object.create(null); - buildInfo.assetsInfo = new Map(); - } - /** @type {NonNullable} */ - (buildInfo.assets)[name] = source; - /** @type {NonNullable} */ - (buildInfo.assetsInfo).set(name, info); - } - callback(null, exports); - } - ); - } - ); - }; - - // eslint-disable-next-line no-warning-comments - // @ts-ignore Overloading doesn't work - loaderContext.importModule = (request, options, callback) => { - if (!callback) { - return new Promise((resolve, reject) => { - importModule(request, options || {}, (err, result) => { - if (err) reject(err); - else resolve(result); - }); - }); - } - return importModule(request, options || {}, callback); - }; - } - ); - }); - } -} -module.exports = LoaderPlugin; diff --git a/webpack-lib/lib/dependencies/LocalModule.js b/webpack-lib/lib/dependencies/LocalModule.js deleted file mode 100644 index 7748a06ba6a..00000000000 --- a/webpack-lib/lib/dependencies/LocalModule.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class LocalModule { - /** - * @param {string} name name - * @param {number} idx index - */ - constructor(name, idx) { - this.name = name; - this.idx = idx; - this.used = false; - } - - flagUsed() { - this.used = true; - } - - /** - * @returns {string} variable name - */ - variableName() { - return `__WEBPACK_LOCAL_MODULE_${this.idx}__`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.name); - write(this.idx); - write(this.used); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.name = read(); - this.idx = read(); - this.used = read(); - } -} - -makeSerializable(LocalModule, "webpack/lib/dependencies/LocalModule"); - -module.exports = LocalModule; diff --git a/webpack-lib/lib/dependencies/LocalModuleDependency.js b/webpack-lib/lib/dependencies/LocalModuleDependency.js deleted file mode 100644 index 2cde22fe145..00000000000 --- a/webpack-lib/lib/dependencies/LocalModuleDependency.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./LocalModule")} LocalModule */ - -class LocalModuleDependency extends NullDependency { - /** - * @param {LocalModule} localModule local module - * @param {Range | undefined} range range - * @param {boolean} callNew true, when the local module should be called with new - */ - constructor(localModule, range, callNew) { - super(); - - this.localModule = localModule; - this.range = range; - this.callNew = callNew; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.localModule); - write(this.range); - write(this.callNew); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.localModule = read(); - this.range = read(); - this.callNew = read(); - - super.deserialize(context); - } -} - -makeSerializable( - LocalModuleDependency, - "webpack/lib/dependencies/LocalModuleDependency" -); - -LocalModuleDependency.Template = class LocalModuleDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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 dep = /** @type {LocalModuleDependency} */ (dependency); - if (!dep.range) return; - const moduleInstance = dep.callNew - ? `new (function () { return ${dep.localModule.variableName()}; })()` - : dep.localModule.variableName(); - source.replace(dep.range[0], dep.range[1] - 1, moduleInstance); - } -}; - -module.exports = LocalModuleDependency; diff --git a/webpack-lib/lib/dependencies/LocalModulesHelpers.js b/webpack-lib/lib/dependencies/LocalModulesHelpers.js deleted file mode 100644 index bcb947c2b02..00000000000 --- a/webpack-lib/lib/dependencies/LocalModulesHelpers.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const LocalModule = require("./LocalModule"); - -/** @typedef {import("../javascript/JavascriptParser").ParserState} ParserState */ - -/** - * @param {string} parent parent module - * @param {string} mod module to resolve - * @returns {string} resolved module - */ -const lookup = (parent, mod) => { - if (mod.charAt(0) !== ".") return mod; - - const path = parent.split("/"); - const segments = mod.split("/"); - path.pop(); - - for (let i = 0; i < segments.length; i++) { - const seg = segments[i]; - if (seg === "..") { - path.pop(); - } else if (seg !== ".") { - path.push(seg); - } - } - - return path.join("/"); -}; - -/** - * @param {ParserState} state parser state - * @param {string} name name - * @returns {LocalModule} local module - */ -module.exports.addLocalModule = (state, name) => { - if (!state.localModules) { - state.localModules = []; - } - const m = new LocalModule(name, state.localModules.length); - state.localModules.push(m); - return m; -}; - -/** - * @param {ParserState} state parser state - * @param {string} name name - * @param {string} [namedModule] named module - * @returns {LocalModule | null} local module or null - */ -module.exports.getLocalModule = (state, name, namedModule) => { - if (!state.localModules) return null; - if (namedModule) { - // resolve dependency name relative to the defining named module - name = lookup(namedModule, name); - } - for (let i = 0; i < state.localModules.length; i++) { - if (state.localModules[i].name === name) { - return state.localModules[i]; - } - } - return null; -}; diff --git a/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js b/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js deleted file mode 100644 index fd2b3fe5f73..00000000000 --- a/webpack-lib/lib/dependencies/ModuleDecoratorDependency.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const InitFragment = require("../InitFragment"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class ModuleDecoratorDependency extends NullDependency { - /** - * @param {string} decorator the decorator requirement - * @param {boolean} allowExportsAccess allow to access exports from module - */ - constructor(decorator, allowExportsAccess) { - super(); - this.decorator = decorator; - this.allowExportsAccess = allowExportsAccess; - this._hashUpdate = undefined; - } - - /** - * @returns {string} a display name for the type of dependency - */ - get type() { - return "module decorator"; - } - - get category() { - return "self"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return "self"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return this.allowExportsAccess - ? Dependency.EXPORTS_OBJECT_REFERENCED - : Dependency.NO_EXPORTS_REFERENCED; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = `${this.decorator}${this.allowExportsAccess}`; - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.decorator); - write(this.allowExportsAccess); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.decorator = read(); - this.allowExportsAccess = read(); - super.deserialize(context); - } -} - -makeSerializable( - ModuleDecoratorDependency, - "webpack/lib/dependencies/ModuleDecoratorDependency" -); - -ModuleDecoratorDependency.Template = class ModuleDecoratorDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { module, chunkGraph, initFragments, runtimeRequirements } - ) { - const dep = /** @type {ModuleDecoratorDependency} */ (dependency); - runtimeRequirements.add(RuntimeGlobals.moduleLoaded); - runtimeRequirements.add(RuntimeGlobals.moduleId); - runtimeRequirements.add(RuntimeGlobals.module); - runtimeRequirements.add(dep.decorator); - initFragments.push( - new InitFragment( - `/* module decorator */ ${module.moduleArgument} = ${dep.decorator}(${module.moduleArgument});\n`, - InitFragment.STAGE_PROVIDES, - 0, - `module decorator ${chunkGraph.getModuleId(module)}` - ) - ); - } -}; - -module.exports = ModuleDecoratorDependency; diff --git a/webpack-lib/lib/dependencies/ModuleDependency.js b/webpack-lib/lib/dependencies/ModuleDependency.js deleted file mode 100644 index 76b9d17484a..00000000000 --- a/webpack-lib/lib/dependencies/ModuleDependency.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const DependencyTemplate = require("../DependencyTemplate"); -const RawModule = require("../RawModule"); - -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../javascript/JavascriptParser").ImportAttributes} ImportAttributes */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ModuleDependency extends Dependency { - /** - * @param {string} request request path which needs resolving - */ - constructor(request) { - super(); - this.request = request; - this.userRequest = request; - this.range = undefined; - // TODO move it to subclasses and rename - // assertions must be serialized by subclasses that use it - /** @type {ImportAttributes | undefined} */ - this.assertions = undefined; - this._context = undefined; - } - - /** - * @returns {string | undefined} a request context - */ - getContext() { - return this._context; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - let str = `context${this._context || ""}|module${this.request}`; - if (this.assertions !== undefined) { - str += JSON.stringify(this.assertions); - } - return str; - } - - /** - * @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 true; - } - - /** - * @param {string} context context directory - * @returns {Module | null} a module - */ - createIgnoredModule(context) { - return new RawModule( - "/* (ignored) */", - `ignored|${context}|${this.request}`, - `${this.request} (ignored)` - ); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.request); - write(this.userRequest); - write(this._context); - write(this.range); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.request = read(); - this.userRequest = read(); - this._context = read(); - this.range = read(); - super.deserialize(context); - } -} - -ModuleDependency.Template = DependencyTemplate; - -module.exports = ModuleDependency; diff --git a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js deleted file mode 100644 index 8086fc79717..00000000000 --- a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsId.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ - -class ModuleDependencyTemplateAsId extends ModuleDependency.Template { - /** - * @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, { runtimeTemplate, moduleGraph, chunkGraph }) { - const dep = /** @type {ModuleDependency} */ (dependency); - if (!dep.range) return; - const content = runtimeTemplate.moduleId({ - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - chunkGraph, - request: dep.request, - weak: dep.weak - }); - source.replace(dep.range[0], dep.range[1] - 1, content); - } -} - -module.exports = ModuleDependencyTemplateAsId; diff --git a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js b/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js deleted file mode 100644 index 9e05906cfe1..00000000000 --- a/webpack-lib/lib/dependencies/ModuleDependencyTemplateAsRequireId.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ - -class ModuleDependencyTemplateAsRequireId extends ModuleDependency.Template { - /** - * @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, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {ModuleDependency} */ (dependency); - if (!dep.range) return; - const content = runtimeTemplate.moduleExports({ - module: moduleGraph.getModule(dep), - chunkGraph, - request: dep.request, - weak: dep.weak, - runtimeRequirements - }); - source.replace(dep.range[0], dep.range[1] - 1, content); - } -} -module.exports = ModuleDependencyTemplateAsRequireId; diff --git a/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js b/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js deleted file mode 100644 index 1916a7e2563..00000000000 --- a/webpack-lib/lib/dependencies/ModuleHotAcceptDependency.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ModuleHotAcceptDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - this.range = range; - this.weak = true; - } - - get type() { - return "module.hot.accept"; - } - - get category() { - return "commonjs"; - } -} - -makeSerializable( - ModuleHotAcceptDependency, - "webpack/lib/dependencies/ModuleHotAcceptDependency" -); - -ModuleHotAcceptDependency.Template = ModuleDependencyTemplateAsId; - -module.exports = ModuleHotAcceptDependency; diff --git a/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js b/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js deleted file mode 100644 index 70423774b4e..00000000000 --- a/webpack-lib/lib/dependencies/ModuleHotDeclineDependency.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class ModuleHotDeclineDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - - this.range = range; - this.weak = true; - } - - get type() { - return "module.hot.decline"; - } - - get category() { - return "commonjs"; - } -} - -makeSerializable( - ModuleHotDeclineDependency, - "webpack/lib/dependencies/ModuleHotDeclineDependency" -); - -ModuleHotDeclineDependency.Template = ModuleDependencyTemplateAsId; - -module.exports = ModuleHotDeclineDependency; diff --git a/webpack-lib/lib/dependencies/NullDependency.js b/webpack-lib/lib/dependencies/NullDependency.js deleted file mode 100644 index c22cafc7c7a..00000000000 --- a/webpack-lib/lib/dependencies/NullDependency.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const DependencyTemplate = require("../DependencyTemplate"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ - -class NullDependency extends Dependency { - get type() { - return "null"; - } - - /** - * @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 false; - } -} - -NullDependency.Template = class NullDependencyTemplate extends ( - DependencyTemplate -) { - /** - * @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) {} -}; - -module.exports = NullDependency; diff --git a/webpack-lib/lib/dependencies/PrefetchDependency.js b/webpack-lib/lib/dependencies/PrefetchDependency.js deleted file mode 100644 index 59e22c59a79..00000000000 --- a/webpack-lib/lib/dependencies/PrefetchDependency.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("./ModuleDependency"); - -class PrefetchDependency extends ModuleDependency { - /** - * @param {string} request the request string - */ - constructor(request) { - super(request); - } - - get type() { - return "prefetch"; - } - - get category() { - return "esm"; - } -} - -module.exports = PrefetchDependency; diff --git a/webpack-lib/lib/dependencies/ProvidedDependency.js b/webpack-lib/lib/dependencies/ProvidedDependency.js deleted file mode 100644 index 9f1d3f6e7dc..00000000000 --- a/webpack-lib/lib/dependencies/ProvidedDependency.js +++ /dev/null @@ -1,157 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const InitFragment = require("../InitFragment"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @param {string[]|null} path the property path array - * @returns {string} the converted path - */ -const pathToString = path => - path !== null && path.length > 0 - ? path.map(part => `[${JSON.stringify(part)}]`).join("") - : ""; - -class ProvidedDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {string} identifier identifier - * @param {string[]} ids ids - * @param {Range} range range - */ - constructor(request, identifier, ids, range) { - super(request); - this.identifier = identifier; - this.ids = ids; - this.range = range; - this._hashUpdate = undefined; - } - - get type() { - return "provided"; - } - - get category() { - return "esm"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - const ids = this.ids; - if (ids.length === 0) return Dependency.EXPORTS_OBJECT_REFERENCED; - return [ids]; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = this.identifier + (this.ids ? this.ids.join(",") : ""); - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.identifier); - write(this.ids); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.identifier = read(); - this.ids = read(); - super.deserialize(context); - } -} - -makeSerializable( - ProvidedDependency, - "webpack/lib/dependencies/ProvidedDependency" -); - -class ProvidedDependencyTemplate extends ModuleDependency.Template { - /** - * @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, - { - runtime, - runtimeTemplate, - moduleGraph, - chunkGraph, - initFragments, - runtimeRequirements - } - ) { - const dep = /** @type {ProvidedDependency} */ (dependency); - const connection = - /** @type {ModuleGraphConnection} */ - (moduleGraph.getConnection(dep)); - const exportsInfo = moduleGraph.getExportsInfo(connection.module); - const usedName = exportsInfo.getUsedName(dep.ids, runtime); - initFragments.push( - new InitFragment( - `/* provided dependency */ var ${ - dep.identifier - } = ${runtimeTemplate.moduleExports({ - module: moduleGraph.getModule(dep), - chunkGraph, - request: dep.request, - runtimeRequirements - })}${pathToString(/** @type {string[]} */ (usedName))};\n`, - InitFragment.STAGE_PROVIDES, - 1, - `provided ${dep.identifier}` - ) - ); - source.replace(dep.range[0], dep.range[1] - 1, dep.identifier); - } -} - -ProvidedDependency.Template = ProvidedDependencyTemplate; - -module.exports = ProvidedDependency; diff --git a/webpack-lib/lib/dependencies/PureExpressionDependency.js b/webpack-lib/lib/dependencies/PureExpressionDependency.js deleted file mode 100644 index 3c4312c9847..00000000000 --- a/webpack-lib/lib/dependencies/PureExpressionDependency.js +++ /dev/null @@ -1,162 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); -const makeSerializable = require("../util/makeSerializable"); -const { filterRuntime, runtimeToString } = require("../util/runtime"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class PureExpressionDependency extends NullDependency { - /** - * @param {Range} range the source range - */ - constructor(range) { - super(); - this.range = range; - /** @type {Set | false} */ - this.usedByExports = false; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @param {RuntimeSpec} runtime current runtimes - * @returns {boolean | RuntimeSpec} runtime condition - */ - _getRuntimeCondition(moduleGraph, runtime) { - const usedByExports = this.usedByExports; - if (usedByExports !== false) { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(this)); - const exportsInfo = moduleGraph.getExportsInfo(selfModule); - const runtimeCondition = filterRuntime(runtime, runtime => { - for (const exportName of usedByExports) { - if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) { - return true; - } - } - return false; - }); - return runtimeCondition; - } - return false; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - const runtimeCondition = this._getRuntimeCondition( - context.chunkGraph.moduleGraph, - context.runtime - ); - if (runtimeCondition === true) { - return; - } else if (runtimeCondition === false) { - hash.update("null"); - } else { - hash.update( - `${runtimeToString(runtimeCondition)}|${runtimeToString( - context.runtime - )}` - ); - } - hash.update(String(this.range)); - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConnectionState} how this dependency connects the module to referencing modules - */ - getModuleEvaluationSideEffectsState(moduleGraph) { - return false; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - write(this.usedByExports); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.range = read(); - this.usedByExports = read(); - super.deserialize(context); - } -} - -makeSerializable( - PureExpressionDependency, - "webpack/lib/dependencies/PureExpressionDependency" -); - -PureExpressionDependency.Template = class PureExpressionDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { chunkGraph, moduleGraph, runtime, runtimeTemplate, runtimeRequirements } - ) { - const dep = /** @type {PureExpressionDependency} */ (dependency); - const runtimeCondition = dep._getRuntimeCondition(moduleGraph, runtime); - if (runtimeCondition === true) { - // Do nothing - } else if (runtimeCondition === false) { - source.insert( - dep.range[0], - "(/* unused pure expression or super */ null && (" - ); - source.insert(dep.range[1], "))"); - } else { - const condition = runtimeTemplate.runtimeConditionExpression({ - chunkGraph, - runtime, - runtimeCondition, - runtimeRequirements - }); - source.insert( - dep.range[0], - `(/* runtime-dependent pure expression or super */ ${condition} ? (` - ); - source.insert(dep.range[1], ") : null)"); - } - } -}; - -module.exports = PureExpressionDependency; diff --git a/webpack-lib/lib/dependencies/RequireContextDependency.js b/webpack-lib/lib/dependencies/RequireContextDependency.js deleted file mode 100644 index 9aa883f2edb..00000000000 --- a/webpack-lib/lib/dependencies/RequireContextDependency.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); -const ModuleDependencyTemplateAsRequireId = require("./ModuleDependencyTemplateAsRequireId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -class RequireContextDependency extends ContextDependency { - /** - * @param {TODO} options options - * @param {Range} range range - */ - constructor(options, range) { - super(options); - - this.range = range; - } - - get type() { - return "require.context"; - } -} - -makeSerializable( - RequireContextDependency, - "webpack/lib/dependencies/RequireContextDependency" -); - -RequireContextDependency.Template = ModuleDependencyTemplateAsRequireId; - -module.exports = RequireContextDependency; diff --git a/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js b/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js deleted file mode 100644 index 02ce1c1487e..00000000000 --- a/webpack-lib/lib/dependencies/RequireContextDependencyParserPlugin.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RequireContextDependency = require("./RequireContextDependency"); - -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -module.exports = class RequireContextDependencyParserPlugin { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.call - .for("require.context") - .tap("RequireContextDependencyParserPlugin", expr => { - let regExp = /^\.\/.*$/; - let recursive = true; - let mode = "sync"; - switch (expr.arguments.length) { - case 4: { - const modeExpr = parser.evaluateExpression(expr.arguments[3]); - if (!modeExpr.isString()) return; - mode = /** @type {string} */ (modeExpr.string); - } - // falls through - case 3: { - const regExpExpr = parser.evaluateExpression(expr.arguments[2]); - if (!regExpExpr.isRegExp()) return; - regExp = /** @type {RegExp} */ (regExpExpr.regExp); - } - // falls through - case 2: { - const recursiveExpr = parser.evaluateExpression(expr.arguments[1]); - if (!recursiveExpr.isBoolean()) return; - recursive = /** @type {boolean} */ (recursiveExpr.bool); - } - // falls through - case 1: { - const requestExpr = parser.evaluateExpression(expr.arguments[0]); - if (!requestExpr.isString()) return; - const dep = new RequireContextDependency( - { - request: requestExpr.string, - recursive, - regExp, - mode, - category: "commonjs" - }, - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - dep.optional = Boolean(parser.scope.inTry); - parser.state.current.addDependency(dep); - return true; - } - } - }); - } -}; diff --git a/webpack-lib/lib/dependencies/RequireContextPlugin.js b/webpack-lib/lib/dependencies/RequireContextPlugin.js deleted file mode 100644 index 30e87fb9e8c..00000000000 --- a/webpack-lib/lib/dependencies/RequireContextPlugin.js +++ /dev/null @@ -1,163 +0,0 @@ -/* - 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 -} = require("../ModuleTypeConstants"); -const { cachedSetProperty } = require("../util/cleverMerge"); -const ContextElementDependency = require("./ContextElementDependency"); -const RequireContextDependency = require("./RequireContextDependency"); -const RequireContextDependencyParserPlugin = require("./RequireContextDependencyParserPlugin"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -/** @type {ResolveOptions} */ -const EMPTY_RESOLVE_OPTIONS = {}; - -const PLUGIN_NAME = "RequireContextPlugin"; - -class RequireContextPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { contextModuleFactory, normalModuleFactory }) => { - compilation.dependencyFactories.set( - RequireContextDependency, - contextModuleFactory - ); - compilation.dependencyTemplates.set( - RequireContextDependency, - new RequireContextDependency.Template() - ); - - compilation.dependencyFactories.set( - ContextElementDependency, - normalModuleFactory - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if ( - parserOptions.requireContext !== undefined && - !parserOptions.requireContext - ) - return; - - new RequireContextDependencyParserPlugin().apply(parser); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - - contextModuleFactory.hooks.alternativeRequests.tap( - PLUGIN_NAME, - (items, options) => { - if (items.length === 0) return items; - - const finalResolveOptions = compiler.resolverFactory.get( - "normal", - cachedSetProperty( - options.resolveOptions || EMPTY_RESOLVE_OPTIONS, - "dependencyType", - /** @type {string} */ (options.category) - ) - ).options; - - let newItems; - if (!finalResolveOptions.fullySpecified) { - newItems = []; - for (const item of items) { - const { request, context } = item; - for (const ext of finalResolveOptions.extensions) { - if (request.endsWith(ext)) { - newItems.push({ - context, - request: request.slice(0, -ext.length) - }); - } - } - if (!finalResolveOptions.enforceExtension) { - newItems.push(item); - } - } - items = newItems; - - newItems = []; - for (const obj of items) { - const { request, context } = obj; - for (const mainFile of finalResolveOptions.mainFiles) { - if (request.endsWith(`/${mainFile}`)) { - newItems.push({ - context, - request: request.slice(0, -mainFile.length) - }); - newItems.push({ - context, - request: request.slice(0, -mainFile.length - 1) - }); - } - } - newItems.push(obj); - } - items = newItems; - } - - newItems = []; - for (const item of items) { - let hideOriginal = false; - for (const modulesItems of finalResolveOptions.modules) { - if (Array.isArray(modulesItems)) { - for (const dir of modulesItems) { - if (item.request.startsWith(`./${dir}/`)) { - newItems.push({ - context: item.context, - request: item.request.slice(dir.length + 3) - }); - hideOriginal = true; - } - } - } else { - const dir = modulesItems.replace(/\\/g, "/"); - const fullPath = - item.context.replace(/\\/g, "/") + item.request.slice(1); - if (fullPath.startsWith(dir)) { - newItems.push({ - context: item.context, - request: fullPath.slice(dir.length + 1) - }); - } - } - } - if (!hideOriginal) { - newItems.push(item); - } - } - return newItems; - } - ); - } - ); - } -} -module.exports = RequireContextPlugin; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js deleted file mode 100644 index 6cf8294e5e7..00000000000 --- a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlock.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ - -class RequireEnsureDependenciesBlock extends AsyncDependenciesBlock { - /** - * @param {ChunkGroupOptions & { entryOptions?: TODO }} chunkName chunk name - * @param {DependencyLocation} loc location info - */ - constructor(chunkName, loc) { - super(chunkName, loc, null); - } -} - -makeSerializable( - RequireEnsureDependenciesBlock, - "webpack/lib/dependencies/RequireEnsureDependenciesBlock" -); - -module.exports = RequireEnsureDependenciesBlock; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js b/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js deleted file mode 100644 index c81fada8b49..00000000000 --- a/webpack-lib/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +++ /dev/null @@ -1,138 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RequireEnsureDependenciesBlock = require("./RequireEnsureDependenciesBlock"); -const RequireEnsureDependency = require("./RequireEnsureDependency"); -const RequireEnsureItemDependency = require("./RequireEnsureItemDependency"); -const getFunctionExpression = require("./getFunctionExpression"); - -/** @typedef {import("../ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -module.exports = class RequireEnsureDependenciesBlockParserPlugin { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - parser.hooks.call - .for("require.ensure") - .tap("RequireEnsureDependenciesBlockParserPlugin", expr => { - let chunkName = null; - let errorExpressionArg = null; - let errorExpression = null; - switch (expr.arguments.length) { - case 4: { - const chunkNameExpr = parser.evaluateExpression(expr.arguments[3]); - if (!chunkNameExpr.isString()) return; - chunkName = chunkNameExpr.string; - } - // falls through - case 3: { - errorExpressionArg = expr.arguments[2]; - errorExpression = getFunctionExpression(errorExpressionArg); - - if (!errorExpression && !chunkName) { - const chunkNameExpr = parser.evaluateExpression( - expr.arguments[2] - ); - if (!chunkNameExpr.isString()) return; - chunkName = chunkNameExpr.string; - } - } - // falls through - case 2: { - const dependenciesExpr = parser.evaluateExpression( - expr.arguments[0] - ); - const dependenciesItems = - /** @type {BasicEvaluatedExpression[]} */ ( - dependenciesExpr.isArray() - ? dependenciesExpr.items - : [dependenciesExpr] - ); - const successExpressionArg = expr.arguments[1]; - const successExpression = - getFunctionExpression(successExpressionArg); - - if (successExpression) { - parser.walkExpressions(successExpression.expressions); - } - if (errorExpression) { - parser.walkExpressions(errorExpression.expressions); - } - - const depBlock = new RequireEnsureDependenciesBlock( - /** @type {ChunkGroupOptions & { entryOptions?: TODO }} */ - (chunkName), - /** @type {DependencyLocation} */ (expr.loc) - ); - const errorCallbackExists = - expr.arguments.length === 4 || - (!chunkName && expr.arguments.length === 3); - const dep = new RequireEnsureDependency( - /** @type {Range} */ (expr.range), - /** @type {Range} */ (expr.arguments[1].range), - errorCallbackExists && - /** @type {Range} */ (expr.arguments[2].range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - depBlock.addDependency(dep); - const old = parser.state.current; - parser.state.current = /** @type {TODO} */ (depBlock); - try { - let failed = false; - parser.inScope([], () => { - for (const ee of dependenciesItems) { - if (ee.isString()) { - const ensureDependency = new RequireEnsureItemDependency( - /** @type {string} */ (ee.string) - ); - ensureDependency.loc = - /** @type {DependencyLocation} */ - (expr.loc); - depBlock.addDependency(ensureDependency); - } else { - failed = true; - } - } - }); - if (failed) { - return; - } - if (successExpression) { - if (successExpression.fn.body.type === "BlockStatement") { - parser.walkStatement(successExpression.fn.body); - } else { - parser.walkExpression(successExpression.fn.body); - } - } - old.addBlock(depBlock); - } finally { - parser.state.current = old; - } - if (!successExpression) { - parser.walkExpression(successExpressionArg); - } - if (errorExpression) { - if (errorExpression.fn.body.type === "BlockStatement") { - parser.walkStatement(errorExpression.fn.body); - } else { - parser.walkExpression(errorExpression.fn.body); - } - } else if (errorExpressionArg) { - parser.walkExpression(errorExpressionArg); - } - return true; - } - } - }); - } -}; diff --git a/webpack-lib/lib/dependencies/RequireEnsureDependency.js b/webpack-lib/lib/dependencies/RequireEnsureDependency.js deleted file mode 100644 index 4fcec7731ce..00000000000 --- a/webpack-lib/lib/dependencies/RequireEnsureDependency.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class RequireEnsureDependency extends NullDependency { - /** - * @param {Range} range range - * @param {Range} contentRange content range - * @param {Range | false} errorHandlerRange error handler range - */ - constructor(range, contentRange, errorHandlerRange) { - super(); - - this.range = range; - this.contentRange = contentRange; - this.errorHandlerRange = errorHandlerRange; - } - - get type() { - return "require.ensure"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.range); - write(this.contentRange); - write(this.errorHandlerRange); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.range = read(); - this.contentRange = read(); - this.errorHandlerRange = read(); - - super.deserialize(context); - } -} - -makeSerializable( - RequireEnsureDependency, - "webpack/lib/dependencies/RequireEnsureDependency" -); - -RequireEnsureDependency.Template = class RequireEnsureDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, - { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } - ) { - const dep = /** @type {RequireEnsureDependency} */ (dependency); - const depBlock = /** @type {AsyncDependenciesBlock} */ ( - moduleGraph.getParentBlock(dep) - ); - const promise = runtimeTemplate.blockPromise({ - chunkGraph, - block: depBlock, - message: "require.ensure", - runtimeRequirements - }); - const range = dep.range; - const contentRange = dep.contentRange; - const errorHandlerRange = dep.errorHandlerRange; - source.replace(range[0], contentRange[0] - 1, `${promise}.then((`); - if (errorHandlerRange) { - source.replace( - contentRange[1], - errorHandlerRange[0] - 1, - `).bind(null, ${RuntimeGlobals.require}))['catch'](` - ); - source.replace(errorHandlerRange[1], range[1] - 1, ")"); - } else { - source.replace( - contentRange[1], - range[1] - 1, - `).bind(null, ${RuntimeGlobals.require}))['catch'](${RuntimeGlobals.uncaughtErrorHandler})` - ); - } - } -}; - -module.exports = RequireEnsureDependency; diff --git a/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js b/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js deleted file mode 100644 index f9a465a55c9..00000000000 --- a/webpack-lib/lib/dependencies/RequireEnsureItemDependency.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const NullDependency = require("./NullDependency"); - -class RequireEnsureItemDependency extends ModuleDependency { - /** - * @param {string} request the request string - */ - constructor(request) { - super(request); - } - - get type() { - return "require.ensure item"; - } - - get category() { - return "commonjs"; - } -} - -makeSerializable( - RequireEnsureItemDependency, - "webpack/lib/dependencies/RequireEnsureItemDependency" -); - -RequireEnsureItemDependency.Template = NullDependency.Template; - -module.exports = RequireEnsureItemDependency; diff --git a/webpack-lib/lib/dependencies/RequireEnsurePlugin.js b/webpack-lib/lib/dependencies/RequireEnsurePlugin.js deleted file mode 100644 index 5a1dc31dbdd..00000000000 --- a/webpack-lib/lib/dependencies/RequireEnsurePlugin.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RequireEnsureDependency = require("./RequireEnsureDependency"); -const RequireEnsureItemDependency = require("./RequireEnsureItemDependency"); - -const RequireEnsureDependenciesBlockParserPlugin = require("./RequireEnsureDependenciesBlockParserPlugin"); - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC -} = require("../ModuleTypeConstants"); -const { - evaluateToString, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "RequireEnsurePlugin"; - -class RequireEnsurePlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - RequireEnsureItemDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - RequireEnsureItemDependency, - new RequireEnsureItemDependency.Template() - ); - - compilation.dependencyTemplates.set( - RequireEnsureDependency, - new RequireEnsureDependency.Template() - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if ( - parserOptions.requireEnsure !== undefined && - !parserOptions.requireEnsure - ) - return; - - new RequireEnsureDependenciesBlockParserPlugin().apply(parser); - parser.hooks.evaluateTypeof - .for("require.ensure") - .tap(PLUGIN_NAME, evaluateToString("function")); - parser.hooks.typeof - .for("require.ensure") - .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); - } - ); - } -} -module.exports = RequireEnsurePlugin; diff --git a/webpack-lib/lib/dependencies/RequireHeaderDependency.js b/webpack-lib/lib/dependencies/RequireHeaderDependency.js deleted file mode 100644 index 7bf75603593..00000000000 --- a/webpack-lib/lib/dependencies/RequireHeaderDependency.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class RequireHeaderDependency extends NullDependency { - /** - * @param {Range} range range - */ - constructor(range) { - super(); - if (!Array.isArray(range)) throw new Error("range must be valid"); - this.range = range; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.range); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RequireHeaderDependency} RequireHeaderDependency - */ - static deserialize(context) { - const obj = new RequireHeaderDependency(context.read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - RequireHeaderDependency, - "webpack/lib/dependencies/RequireHeaderDependency" -); - -RequireHeaderDependency.Template = class RequireHeaderDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { runtimeRequirements }) { - const dep = /** @type {RequireHeaderDependency} */ (dependency); - runtimeRequirements.add(RuntimeGlobals.require); - source.replace(dep.range[0], dep.range[1] - 1, RuntimeGlobals.require); - } -}; - -module.exports = RequireHeaderDependency; diff --git a/webpack-lib/lib/dependencies/RequireIncludeDependency.js b/webpack-lib/lib/dependencies/RequireIncludeDependency.js deleted file mode 100644 index 3a25e84a8ff..00000000000 --- a/webpack-lib/lib/dependencies/RequireIncludeDependency.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const Template = require("../Template"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class RequireIncludeDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - - this.range = range; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - // This doesn't use any export - return Dependency.NO_EXPORTS_REFERENCED; - } - - get type() { - return "require.include"; - } - - get category() { - return "commonjs"; - } -} - -makeSerializable( - RequireIncludeDependency, - "webpack/lib/dependencies/RequireIncludeDependency" -); - -RequireIncludeDependency.Template = class RequireIncludeDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, { runtimeTemplate }) { - const dep = /** @type {RequireIncludeDependency} */ (dependency); - const comment = runtimeTemplate.outputOptions.pathinfo - ? Template.toComment( - `require.include ${runtimeTemplate.requestShortener.shorten( - dep.request - )}` - ) - : ""; - - source.replace(dep.range[0], dep.range[1] - 1, `undefined${comment}`); - } -}; - -module.exports = RequireIncludeDependency; diff --git a/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js b/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js deleted file mode 100644 index 7b9de2c9324..00000000000 --- a/webpack-lib/lib/dependencies/RequireIncludeDependencyParserPlugin.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); -const { - evaluateToString, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); -const makeSerializable = require("../util/makeSerializable"); -const RequireIncludeDependency = require("./RequireIncludeDependency"); - -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -module.exports = class RequireIncludeDependencyParserPlugin { - /** - * @param {boolean} warn true: warn about deprecation, false: don't warn - */ - constructor(warn) { - this.warn = warn; - } - - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - apply(parser) { - const { warn } = this; - parser.hooks.call - .for("require.include") - .tap("RequireIncludeDependencyParserPlugin", expr => { - if (expr.arguments.length !== 1) return; - const param = parser.evaluateExpression(expr.arguments[0]); - if (!param.isString()) return; - - if (warn) { - parser.state.module.addWarning( - new RequireIncludeDeprecationWarning( - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - - const dep = new RequireIncludeDependency( - /** @type {string} */ (param.string), - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.current.addDependency(dep); - return true; - }); - parser.hooks.evaluateTypeof - .for("require.include") - .tap("RequireIncludePlugin", expr => { - if (warn) { - parser.state.module.addWarning( - new RequireIncludeDeprecationWarning( - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - return evaluateToString("function")(expr); - }); - parser.hooks.typeof - .for("require.include") - .tap("RequireIncludePlugin", expr => { - if (warn) { - parser.state.module.addWarning( - new RequireIncludeDeprecationWarning( - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } - return toConstantDependency(parser, JSON.stringify("function"))(expr); - }); - } -}; - -class RequireIncludeDeprecationWarning extends WebpackError { - /** - * @param {DependencyLocation} loc location - */ - constructor(loc) { - super("require.include() is deprecated and will be removed soon."); - - this.name = "RequireIncludeDeprecationWarning"; - - this.loc = loc; - } -} - -makeSerializable( - RequireIncludeDeprecationWarning, - "webpack/lib/dependencies/RequireIncludeDependencyParserPlugin", - "RequireIncludeDeprecationWarning" -); diff --git a/webpack-lib/lib/dependencies/RequireIncludePlugin.js b/webpack-lib/lib/dependencies/RequireIncludePlugin.js deleted file mode 100644 index af5bd0215fd..00000000000 --- a/webpack-lib/lib/dependencies/RequireIncludePlugin.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - 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 -} = require("../ModuleTypeConstants"); -const RequireIncludeDependency = require("./RequireIncludeDependency"); -const RequireIncludeDependencyParserPlugin = require("./RequireIncludeDependencyParserPlugin"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ - -const PLUGIN_NAME = "RequireIncludePlugin"; - -class RequireIncludePlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - RequireIncludeDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - RequireIncludeDependency, - new RequireIncludeDependency.Template() - ); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.requireInclude === false) return; - const warn = parserOptions.requireInclude === undefined; - - new RequireIncludeDependencyParserPlugin(warn).apply(parser); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - } - ); - } -} -module.exports = RequireIncludePlugin; diff --git a/webpack-lib/lib/dependencies/RequireResolveContextDependency.js b/webpack-lib/lib/dependencies/RequireResolveContextDependency.js deleted file mode 100644 index dd82e922094..00000000000 --- a/webpack-lib/lib/dependencies/RequireResolveContextDependency.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const ContextDependency = require("./ContextDependency"); -const ContextDependencyTemplateAsId = require("./ContextDependencyTemplateAsId"); - -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */ - -class RequireResolveContextDependency extends ContextDependency { - /** - * @param {ContextDependencyOptions} options options - * @param {Range} range range - * @param {Range} valueRange value range - * @param {TODO} context context - */ - constructor(options, range, valueRange, context) { - super(options, context); - - this.range = range; - this.valueRange = valueRange; - } - - get type() { - return "amd require context"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.range); - write(this.valueRange); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.range = read(); - this.valueRange = read(); - - super.deserialize(context); - } -} - -makeSerializable( - RequireResolveContextDependency, - "webpack/lib/dependencies/RequireResolveContextDependency" -); - -RequireResolveContextDependency.Template = ContextDependencyTemplateAsId; - -module.exports = RequireResolveContextDependency; diff --git a/webpack-lib/lib/dependencies/RequireResolveDependency.js b/webpack-lib/lib/dependencies/RequireResolveDependency.js deleted file mode 100644 index da0bd319b9d..00000000000 --- a/webpack-lib/lib/dependencies/RequireResolveDependency.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); -const ModuleDependencyAsId = require("./ModuleDependencyTemplateAsId"); - -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class RequireResolveDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - * @param {string} [context] context - */ - constructor(request, range, context) { - super(request); - - this.range = range; - this._context = context; - } - - get type() { - return "require.resolve"; - } - - get category() { - return "commonjs"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - // This doesn't use any export - return Dependency.NO_EXPORTS_REFERENCED; - } -} - -makeSerializable( - RequireResolveDependency, - "webpack/lib/dependencies/RequireResolveDependency" -); - -RequireResolveDependency.Template = ModuleDependencyAsId; - -module.exports = RequireResolveDependency; diff --git a/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js b/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js deleted file mode 100644 index 2c9524c98ee..00000000000 --- a/webpack-lib/lib/dependencies/RequireResolveHeaderDependency.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class RequireResolveHeaderDependency extends NullDependency { - /** - * @param {Range} range range - */ - constructor(range) { - super(); - - if (!Array.isArray(range)) throw new Error("range must be valid"); - - this.range = range; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.range); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RequireResolveHeaderDependency} RequireResolveHeaderDependency - */ - static deserialize(context) { - const obj = new RequireResolveHeaderDependency(context.read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - RequireResolveHeaderDependency, - "webpack/lib/dependencies/RequireResolveHeaderDependency" -); - -RequireResolveHeaderDependency.Template = class RequireResolveHeaderDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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 dep = /** @type {RequireResolveHeaderDependency} */ (dependency); - source.replace(dep.range[0], dep.range[1] - 1, "/*require.resolve*/"); - } - - /** - * @param {string} name name - * @param {RequireResolveHeaderDependency} dep dependency - * @param {ReplaceSource} source source - */ - applyAsTemplateArgument(name, dep, source) { - source.replace(dep.range[0], dep.range[1] - 1, "/*require.resolve*/"); - } -}; - -module.exports = RequireResolveHeaderDependency; diff --git a/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js b/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js deleted file mode 100644 index 714567b7140..00000000000 --- a/webpack-lib/lib/dependencies/RuntimeRequirementsDependency.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class RuntimeRequirementsDependency extends NullDependency { - /** - * @param {string[]} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super(); - this.runtimeRequirements = new Set(runtimeRequirements); - this._hashUpdate = undefined; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = `${Array.from(this.runtimeRequirements).join()}`; - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.runtimeRequirements); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.runtimeRequirements = read(); - super.deserialize(context); - } -} - -makeSerializable( - RuntimeRequirementsDependency, - "webpack/lib/dependencies/RuntimeRequirementsDependency" -); - -RuntimeRequirementsDependency.Template = class RuntimeRequirementsDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { runtimeRequirements }) { - const dep = /** @type {RuntimeRequirementsDependency} */ (dependency); - for (const req of dep.runtimeRequirements) { - runtimeRequirements.add(req); - } - } -}; - -module.exports = RuntimeRequirementsDependency; diff --git a/webpack-lib/lib/dependencies/StaticExportsDependency.js b/webpack-lib/lib/dependencies/StaticExportsDependency.js deleted file mode 100644 index d91b5e43da5..00000000000 --- a/webpack-lib/lib/dependencies/StaticExportsDependency.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ExportSpec} ExportSpec */ -/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ - -class StaticExportsDependency extends NullDependency { - /** - * @param {string[] | true} exports export names - * @param {boolean} canMangle true, if mangling exports names is allowed - */ - constructor(exports, canMangle) { - super(); - this.exports = exports; - this.canMangle = canMangle; - } - - get type() { - return "static exports"; - } - - /** - * Returns the exported names - * @param {ModuleGraph} moduleGraph module graph - * @returns {ExportsSpec | undefined} export names - */ - getExports(moduleGraph) { - return { - exports: this.exports, - canMangle: this.canMangle, - dependencies: undefined - }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.exports); - write(this.canMangle); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.exports = read(); - this.canMangle = read(); - super.deserialize(context); - } -} - -makeSerializable( - StaticExportsDependency, - "webpack/lib/dependencies/StaticExportsDependency" -); - -module.exports = StaticExportsDependency; diff --git a/webpack-lib/lib/dependencies/SystemPlugin.js b/webpack-lib/lib/dependencies/SystemPlugin.js deleted file mode 100644 index 367020d64a9..00000000000 --- a/webpack-lib/lib/dependencies/SystemPlugin.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - 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 -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const WebpackError = require("../WebpackError"); -const { - evaluateToString, - expressionIsUnsupported, - toConstantDependency -} = require("../javascript/JavascriptParserHelpers"); -const makeSerializable = require("../util/makeSerializable"); -const ConstDependency = require("./ConstDependency"); -const SystemRuntimeModule = require("./SystemRuntimeModule"); - -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const PLUGIN_NAME = "SystemPlugin"; - -class SystemPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.hooks.runtimeRequirementInModule - .for(RuntimeGlobals.system) - .tap(PLUGIN_NAME, (module, set) => { - set.add(RuntimeGlobals.requireScope); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.system) - .tap(PLUGIN_NAME, (chunk, set) => { - compilation.addRuntimeModule(chunk, new SystemRuntimeModule()); - }); - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const handler = (parser, parserOptions) => { - if (parserOptions.system === undefined || !parserOptions.system) { - return; - } - - /** - * @param {string} name name - */ - const setNotSupported = name => { - parser.hooks.evaluateTypeof - .for(name) - .tap(PLUGIN_NAME, evaluateToString("undefined")); - parser.hooks.expression - .for(name) - .tap( - PLUGIN_NAME, - expressionIsUnsupported( - parser, - `${name} is not supported by webpack.` - ) - ); - }; - - parser.hooks.typeof - .for("System.import") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("function")) - ); - parser.hooks.evaluateTypeof - .for("System.import") - .tap(PLUGIN_NAME, evaluateToString("function")); - parser.hooks.typeof - .for("System") - .tap( - PLUGIN_NAME, - toConstantDependency(parser, JSON.stringify("object")) - ); - parser.hooks.evaluateTypeof - .for("System") - .tap(PLUGIN_NAME, evaluateToString("object")); - - setNotSupported("System.set"); - setNotSupported("System.get"); - setNotSupported("System.register"); - - parser.hooks.expression.for("System").tap(PLUGIN_NAME, expr => { - const dep = new ConstDependency( - RuntimeGlobals.system, - /** @type {Range} */ (expr.range), - [RuntimeGlobals.system] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }); - - parser.hooks.call.for("System.import").tap(PLUGIN_NAME, expr => { - parser.state.module.addWarning( - new SystemImportDeprecationWarning( - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - - return parser.hooks.importCall.call({ - type: "ImportExpression", - source: - /** @type {import("estree").Literal} */ - (expr.arguments[0]), - loc: expr.loc, - range: expr.range, - options: null - }); - }); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, handler); - } - ); - } -} - -class SystemImportDeprecationWarning extends WebpackError { - /** - * @param {DependencyLocation} loc location - */ - constructor(loc) { - super( - "System.import() is deprecated and will be removed soon. Use import() instead.\n" + - "For more info visit https://webpack.js.org/guides/code-splitting/" - ); - - this.name = "SystemImportDeprecationWarning"; - - this.loc = loc; - } -} - -makeSerializable( - SystemImportDeprecationWarning, - "webpack/lib/dependencies/SystemPlugin", - "SystemImportDeprecationWarning" -); - -module.exports = SystemPlugin; -module.exports.SystemImportDeprecationWarning = SystemImportDeprecationWarning; diff --git a/webpack-lib/lib/dependencies/SystemRuntimeModule.js b/webpack-lib/lib/dependencies/SystemRuntimeModule.js deleted file mode 100644 index a7c3fba72f9..00000000000 --- a/webpack-lib/lib/dependencies/SystemRuntimeModule.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -class SystemRuntimeModule extends RuntimeModule { - constructor() { - super("system"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return Template.asString([ - `${RuntimeGlobals.system} = {`, - Template.indent([ - "import: function () {", - Template.indent( - "throw new Error('System.import cannot be used indirectly');" - ), - "}" - ]), - "};" - ]); - } -} - -module.exports = SystemRuntimeModule; diff --git a/webpack-lib/lib/dependencies/URLDependency.js b/webpack-lib/lib/dependencies/URLDependency.js deleted file mode 100644 index 0d5b0a58bfe..00000000000 --- a/webpack-lib/lib/dependencies/URLDependency.js +++ /dev/null @@ -1,170 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RawDataUrlModule = require("../asset/RawDataUrlModule"); -const { - getDependencyUsedByExportsCondition -} = require("../optimize/InnerGraph"); -const makeSerializable = require("../util/makeSerializable"); -const memoize = require("../util/memoize"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").GetConditionFn} GetConditionFn */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -const getIgnoredRawDataUrlModule = memoize( - () => new RawDataUrlModule("data:,", "ignored-asset", "(ignored asset)") -); - -class URLDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {Range} range range of the arguments of new URL( |> ... <| ) - * @param {Range} outerRange range of the full |> new URL(...) <| - * @param {boolean=} relative use relative urls instead of absolute with base uri - */ - constructor(request, range, outerRange, relative) { - super(request); - this.range = range; - this.outerRange = outerRange; - this.relative = relative || false; - /** @type {Set | boolean | undefined} */ - this.usedByExports = undefined; - } - - get type() { - return "new URL()"; - } - - get category() { - return "url"; - } - - /** - * @param {ModuleGraph} moduleGraph module graph - * @returns {null | false | GetConditionFn} function to determine if the connection is active - */ - getCondition(moduleGraph) { - return getDependencyUsedByExportsCondition( - this, - this.usedByExports, - moduleGraph - ); - } - - /** - * @param {string} context context directory - * @returns {Module | null} a module - */ - createIgnoredModule(context) { - return getIgnoredRawDataUrlModule(); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.outerRange); - write(this.relative); - write(this.usedByExports); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.outerRange = read(); - this.relative = read(); - this.usedByExports = read(); - super.deserialize(context); - } -} - -URLDependency.Template = class URLDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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 { - chunkGraph, - moduleGraph, - runtimeRequirements, - runtimeTemplate, - runtime - } = templateContext; - const dep = /** @type {URLDependency} */ (dependency); - const connection = moduleGraph.getConnection(dep); - // Skip rendering depending when dependency is conditional - if (connection && !connection.isTargetActive(runtime)) { - source.replace( - dep.outerRange[0], - dep.outerRange[1] - 1, - "/* unused asset import */ undefined" - ); - return; - } - - runtimeRequirements.add(RuntimeGlobals.require); - - if (dep.relative) { - runtimeRequirements.add(RuntimeGlobals.relativeUrl); - source.replace( - dep.outerRange[0], - dep.outerRange[1] - 1, - `/* asset import */ new ${ - RuntimeGlobals.relativeUrl - }(${runtimeTemplate.moduleRaw({ - chunkGraph, - module: moduleGraph.getModule(dep), - request: dep.request, - runtimeRequirements, - weak: false - })})` - ); - } else { - runtimeRequirements.add(RuntimeGlobals.baseURI); - - source.replace( - dep.range[0], - dep.range[1] - 1, - `/* asset import */ ${runtimeTemplate.moduleRaw({ - chunkGraph, - module: moduleGraph.getModule(dep), - request: dep.request, - runtimeRequirements, - weak: false - })}, ${RuntimeGlobals.baseURI}` - ); - } - } -}; - -makeSerializable(URLDependency, "webpack/lib/dependencies/URLDependency"); - -module.exports = URLDependency; diff --git a/webpack-lib/lib/dependencies/URLPlugin.js b/webpack-lib/lib/dependencies/URLPlugin.js deleted file mode 100644 index 0409f0689b6..00000000000 --- a/webpack-lib/lib/dependencies/URLPlugin.js +++ /dev/null @@ -1,208 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const { pathToFileURL } = require("url"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); -const { approve } = require("../javascript/JavascriptParserHelpers"); -const InnerGraph = require("../optimize/InnerGraph"); -const ConstDependency = require("./ConstDependency"); -const URLDependency = require("./URLDependency"); - -/** @typedef {import("estree").MemberExpression} MemberExpression */ -/** @typedef {import("estree").NewExpression} NewExpressionNode */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -const PLUGIN_NAME = "URLPlugin"; - -class URLPlugin { - /** - * @param {Compiler} compiler compiler - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set(URLDependency, normalModuleFactory); - compilation.dependencyTemplates.set( - URLDependency, - new URLDependency.Template() - ); - - /** - * @param {NormalModule} module module - * @returns {URL} file url - */ - const getUrl = module => pathToFileURL(module.resource); - - /** - * @param {Parser} parser parser parser - * @param {MemberExpression} arg arg - * @returns {boolean} true when it is `meta.url`, otherwise false - */ - const isMetaUrl = (parser, arg) => { - const chain = parser.extractMemberExpressionChain(arg); - - if ( - chain.members.length !== 1 || - chain.object.type !== "MetaProperty" || - chain.object.meta.name !== "import" || - chain.object.property.name !== "meta" || - chain.members[0] !== "url" - ) - return false; - - return true; - }; - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const parserCallback = (parser, parserOptions) => { - if (parserOptions.url === false) return; - const relative = parserOptions.url === "relative"; - - /** - * @param {NewExpressionNode} expr expression - * @returns {undefined | string} request - */ - const getUrlRequest = expr => { - if (expr.arguments.length !== 2) return; - - const [arg1, arg2] = expr.arguments; - - if ( - arg2.type !== "MemberExpression" || - arg1.type === "SpreadElement" - ) - return; - - if (!isMetaUrl(parser, arg2)) return; - - return parser.evaluateExpression(arg1).asString(); - }; - - parser.hooks.canRename.for("URL").tap(PLUGIN_NAME, approve); - parser.hooks.evaluateNewExpression - .for("URL") - .tap(PLUGIN_NAME, expr => { - const request = getUrlRequest(expr); - if (!request) return; - const url = new URL(request, getUrl(parser.state.module)); - - return new BasicEvaluatedExpression() - .setString(url.toString()) - .setRange(/** @type {Range} */ (expr.range)); - }); - parser.hooks.new.for("URL").tap(PLUGIN_NAME, _expr => { - const expr = /** @type {NewExpressionNode} */ (_expr); - const { options: importOptions, errors: commentErrors } = - parser.parseCommentOptions(/** @type {Range} */ (expr.range)); - - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - parser.state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - /** @type {DependencyLocation} */ (comment.loc) - ) - ); - } - } - - if (importOptions && importOptions.webpackIgnore !== undefined) { - if (typeof importOptions.webpackIgnore !== "boolean") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - return; - } else if (importOptions.webpackIgnore) { - if (expr.arguments.length !== 2) return; - - const [, arg2] = expr.arguments; - - if ( - arg2.type !== "MemberExpression" || - !isMetaUrl(parser, arg2) - ) - return; - - const dep = new ConstDependency( - RuntimeGlobals.baseURI, - /** @type {Range} */ (arg2.range), - [RuntimeGlobals.baseURI] - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - - return true; - } - } - - const request = getUrlRequest(expr); - - if (!request) return; - - const [arg1, arg2] = expr.arguments; - const dep = new URLDependency( - request, - [ - /** @type {Range} */ (arg1.range)[0], - /** @type {Range} */ (arg2.range)[1] - ], - /** @type {Range} */ (expr.range), - relative - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.current.addDependency(dep); - InnerGraph.onUsage(parser.state, e => (dep.usedByExports = e)); - return true; - }); - parser.hooks.isPure.for("NewExpression").tap(PLUGIN_NAME, _expr => { - const expr = /** @type {NewExpressionNode} */ (_expr); - const { callee } = expr; - if (callee.type !== "Identifier") return; - const calleeInfo = parser.getFreeInfoFromVariable(callee.name); - if (!calleeInfo || calleeInfo.name !== "URL") return; - - const request = getUrlRequest(expr); - - if (request) return true; - }); - }; - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, parserCallback); - - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, parserCallback); - } - ); - } -} - -module.exports = URLPlugin; diff --git a/webpack-lib/lib/dependencies/UnsupportedDependency.js b/webpack-lib/lib/dependencies/UnsupportedDependency.js deleted file mode 100644 index 6796634c9b4..00000000000 --- a/webpack-lib/lib/dependencies/UnsupportedDependency.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const NullDependency = require("./NullDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class UnsupportedDependency extends NullDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(); - - this.request = request; - this.range = range; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.request); - write(this.range); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.request = read(); - this.range = read(); - - super.deserialize(context); - } -} - -makeSerializable( - UnsupportedDependency, - "webpack/lib/dependencies/UnsupportedDependency" -); - -UnsupportedDependency.Template = class UnsupportedDependencyTemplate extends ( - NullDependency.Template -) { - /** - * @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, { runtimeTemplate }) { - const dep = /** @type {UnsupportedDependency} */ (dependency); - - source.replace( - dep.range[0], - dep.range[1], - runtimeTemplate.missingModule({ - request: dep.request - }) - ); - } -}; - -module.exports = UnsupportedDependency; diff --git a/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js b/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js deleted file mode 100644 index c2452ecd1c6..00000000000 --- a/webpack-lib/lib/dependencies/WebAssemblyExportImportedDependency.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").TRANSITIVE} TRANSITIVE */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class WebAssemblyExportImportedDependency extends ModuleDependency { - /** - * @param {string} exportName export name - * @param {string} request request - * @param {string} name name - * @param {TODO} valueType value type - */ - constructor(exportName, request, name, valueType) { - super(request); - /** @type {string} */ - this.exportName = exportName; - /** @type {string} */ - this.name = name; - /** @type {string} */ - this.valueType = valueType; - } - - /** - * @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 Dependency.TRANSITIVE; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return [[this.name]]; - } - - get type() { - return "wasm export import"; - } - - get category() { - return "wasm"; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.exportName); - write(this.name); - write(this.valueType); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.exportName = read(); - this.name = read(); - this.valueType = read(); - - super.deserialize(context); - } -} - -makeSerializable( - WebAssemblyExportImportedDependency, - "webpack/lib/dependencies/WebAssemblyExportImportedDependency" -); - -module.exports = WebAssemblyExportImportedDependency; diff --git a/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js b/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js deleted file mode 100644 index de23f76f19a..00000000000 --- a/webpack-lib/lib/dependencies/WebAssemblyImportDependency.js +++ /dev/null @@ -1,108 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("../util/makeSerializable"); -const UnsupportedWebAssemblyFeatureError = require("../wasm-sync/UnsupportedWebAssemblyFeatureError"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("@webassemblyjs/ast").ModuleImportDescription} ModuleImportDescription */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class WebAssemblyImportDependency extends ModuleDependency { - /** - * @param {string} request the request - * @param {string} name the imported name - * @param {ModuleImportDescription} description the WASM ast node - * @param {false | string} onlyDirectImport if only direct imports are allowed - */ - constructor(request, name, description, onlyDirectImport) { - super(request); - /** @type {string} */ - this.name = name; - /** @type {ModuleImportDescription} */ - this.description = description; - /** @type {false | string} */ - this.onlyDirectImport = onlyDirectImport; - } - - get type() { - return "wasm import"; - } - - get category() { - return "wasm"; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return [[this.name]]; - } - - /** - * Returns errors - * @param {ModuleGraph} moduleGraph module graph - * @returns {WebpackError[] | null | undefined} errors - */ - getErrors(moduleGraph) { - const module = moduleGraph.getModule(this); - - if ( - this.onlyDirectImport && - module && - !module.type.startsWith("webassembly") - ) { - return [ - new UnsupportedWebAssemblyFeatureError( - `Import "${this.name}" from "${this.request}" with ${this.onlyDirectImport} can only be used for direct wasm to wasm dependencies` - ) - ]; - } - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - - write(this.name); - write(this.description); - write(this.onlyDirectImport); - - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - - this.name = read(); - this.description = read(); - this.onlyDirectImport = read(); - - super.deserialize(context); - } -} - -makeSerializable( - WebAssemblyImportDependency, - "webpack/lib/dependencies/WebAssemblyImportDependency" -); - -module.exports = WebAssemblyImportDependency; diff --git a/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js b/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js deleted file mode 100644 index 0b308734ee7..00000000000 --- a/webpack-lib/lib/dependencies/WebpackIsIncludedDependency.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const Template = require("../Template"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class WebpackIsIncludedDependency extends ModuleDependency { - /** - * @param {string} request the request string - * @param {Range} range location in source code - */ - constructor(request, range) { - super(request); - - this.weak = true; - this.range = range; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - // This doesn't use any export - return Dependency.NO_EXPORTS_REFERENCED; - } - - get type() { - return "__webpack_is_included__"; - } -} - -makeSerializable( - WebpackIsIncludedDependency, - "webpack/lib/dependencies/WebpackIsIncludedDependency" -); - -WebpackIsIncludedDependency.Template = class WebpackIsIncludedDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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, { runtimeTemplate, chunkGraph, moduleGraph }) { - const dep = /** @type {WebpackIsIncludedDependency} */ (dependency); - const connection = moduleGraph.getConnection(dep); - const included = connection - ? chunkGraph.getNumberOfModuleChunks(connection.module) > 0 - : false; - const comment = runtimeTemplate.outputOptions.pathinfo - ? Template.toComment( - `__webpack_is_included__ ${runtimeTemplate.requestShortener.shorten( - dep.request - )}` - ) - : ""; - - source.replace( - dep.range[0], - dep.range[1] - 1, - `${comment}${JSON.stringify(included)}` - ); - } -}; - -module.exports = WebpackIsIncludedDependency; diff --git a/webpack-lib/lib/dependencies/WorkerDependency.js b/webpack-lib/lib/dependencies/WorkerDependency.js deleted file mode 100644 index 16693d1a4cb..00000000000 --- a/webpack-lib/lib/dependencies/WorkerDependency.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const ModuleDependency = require("./ModuleDependency"); - -/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ -/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../Entrypoint")} Entrypoint */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -class WorkerDependency extends ModuleDependency { - /** - * @param {string} request request - * @param {Range} range range - * @param {object} workerDependencyOptions options - * @param {string=} workerDependencyOptions.publicPath public path for the worker - */ - constructor(request, range, workerDependencyOptions) { - super(request); - this.range = range; - // If options are updated, don't forget to update the hash and serialization functions - this.options = workerDependencyOptions; - /** Cache the hash */ - this._hashUpdate = undefined; - } - - /** - * 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 {(string[] | ReferencedExport)[]} referenced exports - */ - getReferencedExports(moduleGraph, runtime) { - return Dependency.NO_EXPORTS_REFERENCED; - } - - get type() { - return "new Worker()"; - } - - get category() { - return "worker"; - } - - /** - * Update the hash - * @param {Hash} hash hash to be updated - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - if (this._hashUpdate === undefined) { - this._hashUpdate = JSON.stringify(this.options); - } - hash.update(this._hashUpdate); - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.options); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.options = read(); - super.deserialize(context); - } -} - -WorkerDependency.Template = class WorkerDependencyTemplate extends ( - ModuleDependency.Template -) { - /** - * @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 { chunkGraph, moduleGraph, runtimeRequirements } = templateContext; - const dep = /** @type {WorkerDependency} */ (dependency); - const block = /** @type {AsyncDependenciesBlock} */ ( - moduleGraph.getParentBlock(dependency) - ); - const entrypoint = /** @type {Entrypoint} */ ( - chunkGraph.getBlockChunkGroup(block) - ); - const chunk = entrypoint.getEntrypointChunk(); - // We use the workerPublicPath option if provided, else we fallback to the RuntimeGlobal publicPath - const workerImportBaseUrl = dep.options.publicPath - ? `"${dep.options.publicPath}"` - : RuntimeGlobals.publicPath; - - runtimeRequirements.add(RuntimeGlobals.publicPath); - runtimeRequirements.add(RuntimeGlobals.baseURI); - runtimeRequirements.add(RuntimeGlobals.getChunkScriptFilename); - - source.replace( - dep.range[0], - dep.range[1] - 1, - `/* worker import */ ${workerImportBaseUrl} + ${ - RuntimeGlobals.getChunkScriptFilename - }(${JSON.stringify(chunk.id)}), ${RuntimeGlobals.baseURI}` - ); - } -}; - -makeSerializable(WorkerDependency, "webpack/lib/dependencies/WorkerDependency"); - -module.exports = WorkerDependency; diff --git a/webpack-lib/lib/dependencies/WorkerPlugin.js b/webpack-lib/lib/dependencies/WorkerPlugin.js deleted file mode 100644 index 4da16c5c40b..00000000000 --- a/webpack-lib/lib/dependencies/WorkerPlugin.js +++ /dev/null @@ -1,500 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { pathToFileURL } = require("url"); -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const CommentCompilationWarning = require("../CommentCompilationWarning"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); -const { equals } = require("../util/ArrayHelpers"); -const createHash = require("../util/createHash"); -const { contextify } = require("../util/identifier"); -const EnableWasmLoadingPlugin = require("../wasm/EnableWasmLoadingPlugin"); -const ConstDependency = require("./ConstDependency"); -const CreateScriptUrlDependency = require("./CreateScriptUrlDependency"); -const { - harmonySpecifierTag -} = require("./HarmonyImportDependencyParserPlugin"); -const WorkerDependency = require("./WorkerDependency"); - -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").ObjectExpression} ObjectExpression */ -/** @typedef {import("estree").Pattern} Pattern */ -/** @typedef {import("estree").Property} Property */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ -/** @typedef {import("../../declarations/WebpackOptions").ChunkLoading} ChunkLoading */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../../declarations/WebpackOptions").OutputModule} OutputModule */ -/** @typedef {import("../../declarations/WebpackOptions").WasmLoading} WasmLoading */ -/** @typedef {import("../../declarations/WebpackOptions").WorkerPublicPath} WorkerPublicPath */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser")} Parser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../util/createHash").Algorithm} Algorithm */ -/** @typedef {import("./HarmonyImportDependencyParserPlugin").HarmonySettings} HarmonySettings */ - -/** - * @param {NormalModule} module module - * @returns {string} url - */ -const getUrl = module => pathToFileURL(module.resource).toString(); - -const WorkerSpecifierTag = Symbol("worker specifier tag"); - -const DEFAULT_SYNTAX = [ - "Worker", - "SharedWorker", - "navigator.serviceWorker.register()", - "Worker from worker_threads" -]; - -/** @type {WeakMap} */ -const workerIndexMap = new WeakMap(); - -const PLUGIN_NAME = "WorkerPlugin"; - -class WorkerPlugin { - /** - * @param {ChunkLoading=} chunkLoading chunk loading - * @param {WasmLoading=} wasmLoading wasm loading - * @param {OutputModule=} module output module - * @param {WorkerPublicPath=} workerPublicPath worker public path - */ - constructor(chunkLoading, wasmLoading, module, workerPublicPath) { - this._chunkLoading = chunkLoading; - this._wasmLoading = wasmLoading; - this._module = module; - this._workerPublicPath = workerPublicPath; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - if (this._chunkLoading) { - new EnableChunkLoadingPlugin(this._chunkLoading).apply(compiler); - } - if (this._wasmLoading) { - new EnableWasmLoadingPlugin(this._wasmLoading).apply(compiler); - } - const cachedContextify = contextify.bindContextCache( - compiler.context, - compiler.root - ); - compiler.hooks.thisCompilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - WorkerDependency, - normalModuleFactory - ); - compilation.dependencyTemplates.set( - WorkerDependency, - new WorkerDependency.Template() - ); - compilation.dependencyTemplates.set( - CreateScriptUrlDependency, - new CreateScriptUrlDependency.Template() - ); - - /** - * @param {JavascriptParser} parser the parser - * @param {Expression} expr expression - * @returns {[BasicEvaluatedExpression, [number, number]] | void} parsed - */ - const parseModuleUrl = (parser, expr) => { - if ( - expr.type !== "NewExpression" || - expr.callee.type === "Super" || - expr.arguments.length !== 2 - ) - return; - const [arg1, arg2] = expr.arguments; - if (arg1.type === "SpreadElement") return; - if (arg2.type === "SpreadElement") return; - const callee = parser.evaluateExpression(expr.callee); - if (!callee.isIdentifier() || callee.identifier !== "URL") return; - const arg2Value = parser.evaluateExpression(arg2); - if ( - !arg2Value.isString() || - !(/** @type {string} */ (arg2Value.string).startsWith("file://")) || - arg2Value.string !== getUrl(parser.state.module) - ) { - return; - } - const arg1Value = parser.evaluateExpression(arg1); - return [ - arg1Value, - [ - /** @type {Range} */ (arg1.range)[0], - /** @type {Range} */ (arg2.range)[1] - ] - ]; - }; - - /** - * @param {JavascriptParser} parser the parser - * @param {ObjectExpression} expr expression - * @returns {{ expressions: Record, otherElements: (Property | SpreadElement)[], values: Record, spread: boolean, insertType: "comma" | "single", insertLocation: number }} parsed object - */ - const parseObjectExpression = (parser, expr) => { - /** @type {Record} */ - const values = {}; - /** @type {Record} */ - const expressions = {}; - /** @type {(Property | SpreadElement)[]} */ - const otherElements = []; - let spread = false; - for (const prop of expr.properties) { - if (prop.type === "SpreadElement") { - spread = true; - } else if ( - prop.type === "Property" && - !prop.method && - !prop.computed && - prop.key.type === "Identifier" - ) { - expressions[prop.key.name] = prop.value; - if (!prop.shorthand && !prop.value.type.endsWith("Pattern")) { - const value = parser.evaluateExpression( - /** @type {Expression} */ (prop.value) - ); - if (value.isCompileTimeValue()) - values[prop.key.name] = value.asCompileTimeValue(); - } - } else { - otherElements.push(prop); - } - } - const insertType = expr.properties.length > 0 ? "comma" : "single"; - const insertLocation = /** @type {Range} */ ( - expr.properties[expr.properties.length - 1].range - )[1]; - return { - expressions, - otherElements, - values, - spread, - insertType, - insertLocation - }; - }; - - /** - * @param {Parser} parser parser parser - * @param {JavascriptParserOptions} parserOptions parserOptions - * @returns {void} - */ - const parserPlugin = (parser, parserOptions) => { - if (parserOptions.worker === false) return; - const options = !Array.isArray(parserOptions.worker) - ? ["..."] - : parserOptions.worker; - /** - * @param {CallExpression} expr expression - * @returns {boolean | void} true when handled - */ - const handleNewWorker = expr => { - if (expr.arguments.length === 0 || expr.arguments.length > 2) - return; - const [arg1, arg2] = expr.arguments; - if (arg1.type === "SpreadElement") return; - if (arg2 && arg2.type === "SpreadElement") return; - const parsedUrl = parseModuleUrl(parser, arg1); - if (!parsedUrl) return; - const [url, range] = parsedUrl; - if (!url.isString()) return; - const { - expressions, - otherElements, - values: options, - spread: hasSpreadInOptions, - insertType, - insertLocation - } = arg2 && arg2.type === "ObjectExpression" - ? parseObjectExpression(parser, arg2) - : { - /** @type {Record} */ - expressions: {}, - otherElements: [], - /** @type {Record} */ - values: {}, - spread: false, - insertType: arg2 ? "spread" : "argument", - insertLocation: arg2 - ? /** @type {Range} */ (arg2.range) - : /** @type {Range} */ (arg1.range)[1] - }; - const { options: importOptions, errors: commentErrors } = - parser.parseCommentOptions(/** @type {Range} */ (expr.range)); - - if (commentErrors) { - for (const e of commentErrors) { - const { comment } = e; - parser.state.module.addWarning( - new CommentCompilationWarning( - `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`, - /** @type {DependencyLocation} */ (comment.loc) - ) - ); - } - } - - /** @type {EntryOptions} */ - const entryOptions = {}; - - if (importOptions) { - if (importOptions.webpackIgnore !== undefined) { - if (typeof importOptions.webpackIgnore !== "boolean") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackIgnore\` expected a boolean, but received: ${importOptions.webpackIgnore}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else if (importOptions.webpackIgnore) { - return false; - } - } - if (importOptions.webpackEntryOptions !== undefined) { - if ( - typeof importOptions.webpackEntryOptions !== "object" || - importOptions.webpackEntryOptions === null - ) { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackEntryOptions\` expected a object, but received: ${importOptions.webpackEntryOptions}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - Object.assign( - entryOptions, - importOptions.webpackEntryOptions - ); - } - } - if (importOptions.webpackChunkName !== undefined) { - if (typeof importOptions.webpackChunkName !== "string") { - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - `\`webpackChunkName\` expected a string, but received: ${importOptions.webpackChunkName}.`, - /** @type {DependencyLocation} */ (expr.loc) - ) - ); - } else { - entryOptions.name = importOptions.webpackChunkName; - } - } - } - - if ( - !Object.prototype.hasOwnProperty.call(entryOptions, "name") && - options && - typeof options.name === "string" - ) { - entryOptions.name = options.name; - } - - if (entryOptions.runtime === undefined) { - const i = workerIndexMap.get(parser.state) || 0; - workerIndexMap.set(parser.state, i + 1); - const name = `${cachedContextify( - parser.state.module.identifier() - )}|${i}`; - const hash = createHash( - /** @type {Algorithm} */ - (compilation.outputOptions.hashFunction) - ); - hash.update(name); - const digest = - /** @type {string} */ - (hash.digest(compilation.outputOptions.hashDigest)); - entryOptions.runtime = digest.slice( - 0, - compilation.outputOptions.hashDigestLength - ); - } - - const block = new AsyncDependenciesBlock({ - name: entryOptions.name, - entryOptions: { - chunkLoading: this._chunkLoading, - wasmLoading: this._wasmLoading, - ...entryOptions - } - }); - block.loc = expr.loc; - const dep = new WorkerDependency( - /** @type {string} */ (url.string), - range, - { - publicPath: this._workerPublicPath - } - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - block.addDependency(dep); - parser.state.module.addBlock(block); - - if (compilation.outputOptions.trustedTypes) { - const dep = new CreateScriptUrlDependency( - /** @type {Range} */ (expr.arguments[0].range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addDependency(dep); - } - - if (expressions.type) { - const expr = expressions.type; - if (options.type !== false) { - const dep = new ConstDependency( - this._module ? '"module"' : "undefined", - /** @type {Range} */ (expr.range) - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - /** @type {TODO} */ - (expressions).type = undefined; - } - } else if (insertType === "comma") { - if (this._module || hasSpreadInOptions) { - const dep = new ConstDependency( - `, type: ${this._module ? '"module"' : "undefined"}`, - insertLocation - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } - } else if (insertType === "spread") { - const dep1 = new ConstDependency( - "Object.assign({}, ", - /** @type {Range} */ (insertLocation)[0] - ); - const dep2 = new ConstDependency( - `, { type: ${this._module ? '"module"' : "undefined"} })`, - /** @type {Range} */ (insertLocation)[1] - ); - dep1.loc = /** @type {DependencyLocation} */ (expr.loc); - dep2.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep1); - parser.state.module.addPresentationalDependency(dep2); - } else if (insertType === "argument" && this._module) { - const dep = new ConstDependency( - ', { type: "module" }', - insertLocation - ); - dep.loc = /** @type {DependencyLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - } - - parser.walkExpression(expr.callee); - for (const key of Object.keys(expressions)) { - if (expressions[key]) parser.walkExpression(expressions[key]); - } - for (const prop of otherElements) { - parser.walkProperty(prop); - } - if (insertType === "spread") { - parser.walkExpression(arg2); - } - - return true; - }; - /** - * @param {string} item item - */ - const processItem = item => { - if ( - item.startsWith("*") && - item.includes(".") && - item.endsWith("()") - ) { - const firstDot = item.indexOf("."); - const pattern = item.slice(1, firstDot); - const itemMembers = item.slice(firstDot + 1, -2); - - parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => { - if (decl.id.type === "Identifier" && decl.id.name === pattern) { - parser.tagVariable(decl.id.name, WorkerSpecifierTag); - return true; - } - }); - parser.hooks.pattern.for(pattern).tap(PLUGIN_NAME, pattern => { - parser.tagVariable(pattern.name, WorkerSpecifierTag); - return true; - }); - parser.hooks.callMemberChain - .for(WorkerSpecifierTag) - .tap(PLUGIN_NAME, (expression, members) => { - if (itemMembers !== members.join(".")) { - return; - } - - return handleNewWorker(expression); - }); - } else if (item.endsWith("()")) { - parser.hooks.call - .for(item.slice(0, -2)) - .tap(PLUGIN_NAME, handleNewWorker); - } else { - const match = /^(.+?)(\(\))?\s+from\s+(.+)$/.exec(item); - if (match) { - const ids = match[1].split("."); - const call = match[2]; - const source = match[3]; - (call ? parser.hooks.call : parser.hooks.new) - .for(harmonySpecifierTag) - .tap(PLUGIN_NAME, expr => { - const settings = /** @type {HarmonySettings} */ ( - parser.currentTagData - ); - if ( - !settings || - settings.source !== source || - !equals(settings.ids, ids) - ) { - return; - } - return handleNewWorker(expr); - }); - } else { - parser.hooks.new.for(item).tap(PLUGIN_NAME, handleNewWorker); - } - } - }; - for (const item of options) { - if (item === "...") { - for (const itemFromDefault of DEFAULT_SYNTAX) { - processItem(itemFromDefault); - } - } else processItem(item); - } - }; - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, parserPlugin); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, parserPlugin); - } - ); - } -} -module.exports = WorkerPlugin; diff --git a/webpack-lib/lib/dependencies/getFunctionExpression.js b/webpack-lib/lib/dependencies/getFunctionExpression.js deleted file mode 100644 index f4495b500ff..00000000000 --- a/webpack-lib/lib/dependencies/getFunctionExpression.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").FunctionExpression} FunctionExpression */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ - -/** - * @param {Expression | SpreadElement} expr expressions - * @returns {{fn: FunctionExpression | ArrowFunctionExpression, expressions: (Expression | SpreadElement)[], needThis: boolean | undefined } | undefined} function expression with additional information - */ -module.exports = expr => { - // - if ( - expr.type === "FunctionExpression" || - expr.type === "ArrowFunctionExpression" - ) { - return { - fn: expr, - expressions: [], - needThis: false - }; - } - - // .bind() - if ( - expr.type === "CallExpression" && - expr.callee.type === "MemberExpression" && - expr.callee.object.type === "FunctionExpression" && - expr.callee.property.type === "Identifier" && - expr.callee.property.name === "bind" && - expr.arguments.length === 1 - ) { - return { - fn: expr.callee.object, - expressions: [expr.arguments[0]], - needThis: undefined - }; - } - // (function(_this) {return })(this) (Coffeescript) - if ( - expr.type === "CallExpression" && - expr.callee.type === "FunctionExpression" && - expr.callee.body.type === "BlockStatement" && - expr.arguments.length === 1 && - expr.arguments[0].type === "ThisExpression" && - expr.callee.body.body && - expr.callee.body.body.length === 1 && - expr.callee.body.body[0].type === "ReturnStatement" && - expr.callee.body.body[0].argument && - expr.callee.body.body[0].argument.type === "FunctionExpression" - ) { - return { - fn: expr.callee.body.body[0].argument, - expressions: [], - needThis: true - }; - } -}; diff --git a/webpack-lib/lib/dependencies/processExportInfo.js b/webpack-lib/lib/dependencies/processExportInfo.js deleted file mode 100644 index 9bafcae635a..00000000000 --- a/webpack-lib/lib/dependencies/processExportInfo.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); - -/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** @typedef {string[][]} ReferencedExports */ - -/** - * @param {RuntimeSpec} runtime the runtime - * @param {ReferencedExports} referencedExports list of referenced exports, will be added to - * @param {string[]} prefix export prefix - * @param {ExportInfo=} exportInfo the export info - * @param {boolean} defaultPointsToSelf when true, using default will reference itself - * @param {Set} alreadyVisited already visited export info (to handle circular reexports) - */ -const processExportInfo = ( - runtime, - referencedExports, - prefix, - exportInfo, - defaultPointsToSelf = false, - alreadyVisited = new Set() -) => { - if (!exportInfo) { - referencedExports.push(prefix); - return; - } - const used = exportInfo.getUsed(runtime); - if (used === UsageState.Unused) return; - if (alreadyVisited.has(exportInfo)) { - referencedExports.push(prefix); - return; - } - alreadyVisited.add(exportInfo); - if ( - used !== UsageState.OnlyPropertiesUsed || - !exportInfo.exportsInfo || - exportInfo.exportsInfo.otherExportsInfo.getUsed(runtime) !== - UsageState.Unused - ) { - alreadyVisited.delete(exportInfo); - referencedExports.push(prefix); - return; - } - const exportsInfo = exportInfo.exportsInfo; - for (const exportInfo of exportsInfo.orderedExports) { - processExportInfo( - runtime, - referencedExports, - defaultPointsToSelf && exportInfo.name === "default" - ? prefix - : prefix.concat(exportInfo.name), - exportInfo, - false, - alreadyVisited - ); - } - alreadyVisited.delete(exportInfo); -}; -module.exports = processExportInfo; diff --git a/webpack-lib/lib/electron/ElectronTargetPlugin.js b/webpack-lib/lib/electron/ElectronTargetPlugin.js deleted file mode 100644 index e8c4e844a87..00000000000 --- a/webpack-lib/lib/electron/ElectronTargetPlugin.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ExternalsPlugin = require("../ExternalsPlugin"); - -/** @typedef {import("../Compiler")} Compiler */ - -class ElectronTargetPlugin { - /** - * @param {"main" | "preload" | "renderer"=} context in main, preload or renderer context? - */ - constructor(context) { - this._context = context; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - new ExternalsPlugin("node-commonjs", [ - "clipboard", - "crash-reporter", - "electron", - "ipc", - "native-image", - "original-fs", - "screen", - "shell" - ]).apply(compiler); - switch (this._context) { - case "main": - new ExternalsPlugin("node-commonjs", [ - "app", - "auto-updater", - "browser-window", - "content-tracing", - "dialog", - "global-shortcut", - "ipc-main", - "menu", - "menu-item", - "power-monitor", - "power-save-blocker", - "protocol", - "session", - "tray", - "web-contents" - ]).apply(compiler); - break; - case "preload": - case "renderer": - new ExternalsPlugin("node-commonjs", [ - "desktop-capturer", - "ipc-renderer", - "remote", - "web-frame" - ]).apply(compiler); - break; - } - } -} - -module.exports = ElectronTargetPlugin; diff --git a/webpack-lib/lib/errors/BuildCycleError.js b/webpack-lib/lib/errors/BuildCycleError.js deleted file mode 100644 index a235fcebbe4..00000000000 --- a/webpack-lib/lib/errors/BuildCycleError.js +++ /dev/null @@ -1,27 +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 BuildCycleError extends WebpackError { - /** - * Creates an instance of ModuleDependencyError. - * @param {Module} module the module starting the cycle - */ - constructor(module) { - super( - "There is a circular build dependency, which makes it impossible to create this module" - ); - - this.name = "BuildCycleError"; - this.module = module; - } -} - -module.exports = BuildCycleError; diff --git a/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js b/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js deleted file mode 100644 index 30a275fa432..00000000000 --- a/webpack-lib/lib/esm/ExportWebpackRequireRuntimeModule.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -class ExportWebpackRequireRuntimeModule extends RuntimeModule { - constructor() { - super("export webpack runtime", RuntimeModule.STAGE_ATTACH); - } - - /** - * @returns {boolean} true, if the runtime module should get it's own scope - */ - shouldIsolate() { - return false; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return `export default ${RuntimeGlobals.require};`; - } -} - -module.exports = ExportWebpackRequireRuntimeModule; diff --git a/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js b/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js deleted file mode 100644 index abf6481351b..00000000000 --- a/webpack-lib/lib/esm/ModuleChunkFormatPlugin.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const { RuntimeGlobals } = require(".."); -const HotUpdateChunk = require("../HotUpdateChunk"); -const Template = require("../Template"); -const { getAllChunks } = require("../javascript/ChunkHelpers"); -const { - chunkHasJs, - getCompilationHooks, - getChunkFilenameTemplate -} = require("../javascript/JavascriptModulesPlugin"); -const { updateHashForEntryStartup } = require("../javascript/StartupHelpers"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Entrypoint")} Entrypoint */ - -class ModuleChunkFormatPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "ModuleChunkFormatPlugin", - compilation => { - compilation.hooks.additionalChunkRuntimeRequirements.tap( - "ModuleChunkFormatPlugin", - (chunk, set) => { - if (chunk.hasRuntime()) return; - if (compilation.chunkGraph.getNumberOfEntryModules(chunk) > 0) { - set.add(RuntimeGlobals.require); - set.add(RuntimeGlobals.startupEntrypoint); - set.add(RuntimeGlobals.externalInstallChunk); - } - } - ); - const hooks = getCompilationHooks(compilation); - hooks.renderChunk.tap( - "ModuleChunkFormatPlugin", - (modules, renderContext) => { - const { chunk, chunkGraph, runtimeTemplate } = renderContext; - const hotUpdateChunk = - chunk instanceof HotUpdateChunk ? chunk : null; - const source = new ConcatSource(); - if (hotUpdateChunk) { - throw new Error( - "HMR is not implemented for module chunk format yet" - ); - } else { - source.add( - `export const __webpack_id__ = ${JSON.stringify(chunk.id)};\n` - ); - source.add( - `export const __webpack_ids__ = ${JSON.stringify(chunk.ids)};\n` - ); - source.add("export const __webpack_modules__ = "); - source.add(modules); - source.add(";\n"); - const runtimeModules = - chunkGraph.getChunkRuntimeModulesInOrder(chunk); - if (runtimeModules.length > 0) { - source.add("export const __webpack_runtime__ =\n"); - source.add( - Template.renderChunkRuntimeModules( - runtimeModules, - renderContext - ) - ); - } - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - if (entries.length > 0) { - const runtimeChunk = - /** @type {Entrypoint[][]} */ - (entries)[0][1].getRuntimeChunk(); - const currentOutputName = compilation - .getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ) - .replace(/^\/+/g, "") - .split("/"); - - /** - * @param {Chunk} chunk the chunk - * @returns {string} the relative path - */ - const getRelativePath = chunk => { - const baseOutputName = currentOutputName.slice(); - const chunkOutputName = compilation - .getPath( - getChunkFilenameTemplate( - chunk, - compilation.outputOptions - ), - { - chunk, - contentHashType: "javascript" - } - ) - .replace(/^\/+/g, "") - .split("/"); - - // remove common parts except filename - while ( - baseOutputName.length > 1 && - chunkOutputName.length > 1 && - baseOutputName[0] === chunkOutputName[0] - ) { - baseOutputName.shift(); - chunkOutputName.shift(); - } - const last = chunkOutputName.join("/"); - // create final path - return ( - getUndoPath(baseOutputName.join("/"), last, true) + last - ); - }; - - const entrySource = new ConcatSource(); - entrySource.add(source); - entrySource.add(";\n\n// load runtime\n"); - entrySource.add( - `import ${RuntimeGlobals.require} from ${JSON.stringify( - getRelativePath(/** @type {Chunk} */ (runtimeChunk)) - )};\n` - ); - - const startupSource = new ConcatSource(); - startupSource.add( - `var __webpack_exec__ = ${runtimeTemplate.returningFunction( - `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`, - "moduleId" - )}\n` - ); - - const loadedChunks = new Set(); - let index = 0; - for (let i = 0; i < entries.length; i++) { - const [module, entrypoint] = entries[i]; - const final = i + 1 === entries.length; - const moduleId = chunkGraph.getModuleId(module); - const chunks = getAllChunks( - /** @type {Entrypoint} */ (entrypoint), - /** @type {Chunk} */ (runtimeChunk), - undefined - ); - for (const chunk of chunks) { - if ( - loadedChunks.has(chunk) || - !chunkHasJs(chunk, chunkGraph) - ) - continue; - loadedChunks.add(chunk); - startupSource.add( - `import * as __webpack_chunk_${index}__ from ${JSON.stringify( - getRelativePath(chunk) - )};\n` - ); - startupSource.add( - `${RuntimeGlobals.externalInstallChunk}(__webpack_chunk_${index}__);\n` - ); - index++; - } - startupSource.add( - `${ - final ? `var ${RuntimeGlobals.exports} = ` : "" - }__webpack_exec__(${JSON.stringify(moduleId)});\n` - ); - } - - entrySource.add( - hooks.renderStartup.call( - startupSource, - entries[entries.length - 1][0], - { - ...renderContext, - inlined: false - } - ) - ); - return entrySource; - } - } - return source; - } - ); - hooks.chunkHash.tap( - "ModuleChunkFormatPlugin", - (chunk, hash, { chunkGraph, runtimeTemplate }) => { - if (chunk.hasRuntime()) return; - hash.update("ModuleChunkFormatPlugin"); - hash.update("1"); - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - updateHashForEntryStartup(hash, chunkGraph, entries, chunk); - } - ); - } - ); - } -} - -module.exports = ModuleChunkFormatPlugin; diff --git a/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js b/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js deleted file mode 100644 index b88533a8363..00000000000 --- a/webpack-lib/lib/esm/ModuleChunkLoadingPlugin.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const ExportWebpackRequireRuntimeModule = require("./ExportWebpackRequireRuntimeModule"); -const ModuleChunkLoadingRuntimeModule = require("./ModuleChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -class ModuleChunkLoadingPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "ModuleChunkLoadingPlugin", - compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk chunk to check - * @returns {boolean} true, when the plugin is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === "import"; - }; - const onceForChunkSet = new WeakSet(); - /** - * @param {Chunk} chunk chunk to check - * @param {Set} set runtime requirements - */ - const handler = (chunk, set) => { - if (onceForChunkSet.has(chunk)) return; - onceForChunkSet.add(chunk); - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.hasOwnProperty); - compilation.addRuntimeModule( - chunk, - new ModuleChunkLoadingRuntimeModule(set) - ); - }; - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("ModuleChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.baseURI) - .tap("ModuleChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.externalInstallChunk) - .tap("ModuleChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.onChunksLoaded) - .tap("ModuleChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.externalInstallChunk) - .tap("ModuleChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - compilation.addRuntimeModule( - chunk, - new ExportWebpackRequireRuntimeModule() - ); - }); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("ModuleChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.getChunkScriptFilename); - }); - } - ); - } -} - -module.exports = ModuleChunkLoadingPlugin; diff --git a/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js b/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js deleted file mode 100644 index 69dd231f97f..00000000000 --- a/webpack-lib/lib/esm/ModuleChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,351 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const Compilation = require("../Compilation"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - getChunkFilenameTemplate, - chunkHasJs -} = require("../javascript/JavascriptModulesPlugin"); -const { getInitialChunkIds } = require("../javascript/StartupHelpers"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../../declarations/WebpackOptions").Environment} Environment */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -/** - * @typedef {object} JsonpCompilationPluginHooks - * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload - * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class ModuleChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {Compilation} compilation the compilation - * @returns {JsonpCompilationPluginHooks} 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 = { - linkPreload: new SyncWaterfallHook(["source", "chunk"]), - linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("import chunk loading", RuntimeModule.STAGE_ATTACH); - this._runtimeRequirements = runtimeRequirements; - } - - /** - * @private - * @param {Chunk} chunk chunk - * @param {string} rootOutputDir root output directory - * @returns {string} generated code - */ - _generateBaseUri(chunk, rootOutputDir) { - const options = chunk.getEntryOptions(); - if (options && options.baseUri) { - return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; - } - const compilation = /** @type {Compilation} */ (this.compilation); - const { - outputOptions: { importMetaName } - } = compilation; - return `${RuntimeGlobals.baseURI} = new URL(${JSON.stringify( - rootOutputDir - )}, ${importMetaName}.url);`; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const environment = - /** @type {Environment} */ - (compilation.outputOptions.environment); - const { - runtimeTemplate, - outputOptions: { importFunctionName, crossOriginLoading } - } = compilation; - const fn = RuntimeGlobals.ensureChunkHandlers; - const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI); - const withExternalInstallChunk = this._runtimeRequirements.has( - RuntimeGlobals.externalInstallChunk - ); - const withLoading = this._runtimeRequirements.has( - RuntimeGlobals.ensureChunkHandlers - ); - const withOnChunkLoad = this._runtimeRequirements.has( - RuntimeGlobals.onChunksLoaded - ); - const withHmr = this._runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const { linkPreload, linkPrefetch } = - ModuleChunkLoadingRuntimeModule.getCompilationHooks(compilation); - const isNeutralPlatform = runtimeTemplate.isNeutralPlatform(); - const withPrefetch = - (environment.document || isNeutralPlatform) && - this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && - chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs); - const withPreload = - (environment.document || isNeutralPlatform) && - this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && - chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs); - const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); - const hasJsMatcher = compileBooleanMatcher(conditionMap); - const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); - - const outputName = compilation.getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ); - const rootOutputDir = getUndoPath( - outputName, - /** @type {string} */ (compilation.outputOptions.path), - true - ); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_module` - : undefined; - - return Template.asString([ - withBaseURI - ? this._generateBaseUri(chunk, rootOutputDir) - : "// no baseURI", - "", - "// object to store loaded and loading chunks", - "// undefined = chunk not loaded, null = chunk preloaded/prefetched", - "// [resolve, Promise] = chunk loading, 0 = chunk loaded", - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( - ",\n" - ) - ), - "};", - "", - withLoading || withExternalInstallChunk - ? `var installChunk = ${runtimeTemplate.basicFunction("data", [ - runtimeTemplate.destructureObject( - ["__webpack_ids__", "__webpack_modules__", "__webpack_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_modules__) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(__webpack_modules__, moduleId)) {`, - Template.indent( - `${RuntimeGlobals.moduleFactories}[moduleId] = __webpack_modules__[moduleId];` - ), - "}" - ]), - "}", - `if(__webpack_runtime__) __webpack_runtime__(${RuntimeGlobals.require});`, - "for(;i < __webpack_ids__.length; i++) {", - Template.indent([ - "chunkId = __webpack_ids__[i];", - `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`, - Template.indent("installedChunks[chunkId][0]();"), - "}", - "installedChunks[__webpack_ids__[i]] = 0;" - ]), - "}", - withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" - ])}` - : "// no install chunk", - "", - withLoading - ? Template.asString([ - `${fn}.j = ${runtimeTemplate.basicFunction( - "chunkId, promises", - hasJsMatcher !== false - ? Template.indent([ - "// import() chunk loading for javascript", - `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, - 'if(installedChunkData !== 0) { // 0 means "already installed".', - Template.indent([ - "", - '// a Promise means "currently loading".', - "if(installedChunkData) {", - Template.indent([ - "promises.push(installedChunkData[1]);" - ]), - "} else {", - Template.indent([ - hasJsMatcher === true - ? "if(true) { // all chunks have JS" - : `if(${hasJsMatcher("chunkId")}) {`, - Template.indent([ - "// setup Promise in chunk cache", - `var promise = ${importFunctionName}(${JSON.stringify( - rootOutputDir - )} + ${ - RuntimeGlobals.getChunkScriptFilename - }(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction( - "e", - [ - "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;", - "throw e;" - ] - )});`, - `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction( - "installedChunkData = installedChunks[chunkId] = [resolve]", - "resolve" - )})])`, - "promises.push(installedChunkData[1] = promise);" - ]), - hasJsMatcher === true - ? "}" - : "} else installedChunks[chunkId] = 0;" - ]), - "}" - ]), - "}" - ]) - : Template.indent(["installedChunks[chunkId] = 0;"]) - )};` - ]) - : "// no chunk on demand loading", - "", - withPrefetch && hasJsMatcher !== false - ? `${ - RuntimeGlobals.prefetchChunkHandlers - }.j = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - isNeutralPlatform - ? "if (typeof document === 'undefined') return;" - : "", - linkPrefetch.call( - Template.asString([ - "var link = document.createElement('link');", - crossOriginLoading - ? `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - : "", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - 'link.rel = "prefetch";', - 'link.as = "script";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);` - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no prefetching", - "", - withPreload && hasJsMatcher !== false - ? `${ - RuntimeGlobals.preloadChunkHandlers - }.j = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - isNeutralPlatform - ? "if (typeof document === 'undefined') return;" - : "", - linkPreload.call( - Template.asString([ - "var link = document.createElement('link');", - "link.charset = 'utf-8';", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - 'link.rel = "modulepreload";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, - crossOriginLoading - ? crossOriginLoading === "use-credentials" - ? 'link.crossOrigin = "use-credentials";' - : Template.asString([ - "if (link.href.indexOf(window.location.origin + '/') !== 0) {", - Template.indent( - `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - ), - "}" - ]) - : "" - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no preloaded", - "", - withExternalInstallChunk - ? Template.asString([ - `${RuntimeGlobals.externalInstallChunk} = installChunk;` - ]) - : "// no external install chunk", - "", - withOnChunkLoad - ? `${ - RuntimeGlobals.onChunksLoaded - }.j = ${runtimeTemplate.returningFunction( - "installedChunks[chunkId] === 0", - "chunkId" - )};` - : "// no on chunks loaded" - ]); - } -} - -module.exports = ModuleChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/formatLocation.js b/webpack-lib/lib/formatLocation.js deleted file mode 100644 index 780d4a475ca..00000000000 --- a/webpack-lib/lib/formatLocation.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("./Dependency").SourcePosition} SourcePosition */ - -/** - * @param {SourcePosition} pos position - * @returns {string} formatted position - */ -const formatPosition = pos => { - if (pos && typeof pos === "object") { - if ("line" in pos && "column" in pos) { - return `${pos.line}:${pos.column}`; - } else if ("line" in pos) { - return `${pos.line}:?`; - } - } - return ""; -}; - -/** - * @param {DependencyLocation} loc location - * @returns {string} formatted location - */ -const formatLocation = loc => { - if (loc && typeof loc === "object") { - if ("start" in loc && loc.start && "end" in loc && loc.end) { - if ( - typeof loc.start === "object" && - typeof loc.start.line === "number" && - typeof loc.end === "object" && - typeof loc.end.line === "number" && - typeof loc.end.column === "number" && - loc.start.line === loc.end.line - ) { - return `${formatPosition(loc.start)}-${loc.end.column}`; - } else if ( - typeof loc.start === "object" && - typeof loc.start.line === "number" && - typeof loc.start.column !== "number" && - typeof loc.end === "object" && - typeof loc.end.line === "number" && - typeof loc.end.column !== "number" - ) { - return `${loc.start.line}-${loc.end.line}`; - } - return `${formatPosition(loc.start)}-${formatPosition(loc.end)}`; - } - if ("start" in loc && loc.start) { - return formatPosition(loc.start); - } - if ("name" in loc && "index" in loc) { - return `${loc.name}[${loc.index}]`; - } - if ("name" in loc) { - return loc.name; - } - } - return ""; -}; - -module.exports = formatLocation; diff --git a/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js b/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js deleted file mode 100644 index e9fec891700..00000000000 --- a/webpack-lib/lib/hmr/HotModuleReplacement.runtime.js +++ /dev/null @@ -1,407 +0,0 @@ -// @ts-nocheck -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -var $interceptModuleExecution$ = undefined; -var $moduleCache$ = undefined; -// eslint-disable-next-line no-unused-vars -var $hmrModuleData$ = undefined; -/** @type {() => Promise} */ -var $hmrDownloadManifest$ = undefined; -var $hmrDownloadUpdateHandlers$ = undefined; -var $hmrInvalidateModuleHandlers$ = undefined; -var __webpack_require__ = undefined; - -module.exports = function () { - var currentModuleData = {}; - var installedModules = $moduleCache$; - - // module and require creation - var currentChildModule; - var currentParents = []; - - // status - var registeredStatusHandlers = []; - var currentStatus = "idle"; - - // while downloading - var blockingPromises = 0; - var blockingPromisesWaiting = []; - - // The update info - var currentUpdateApplyHandlers; - var queuedInvalidatedModules; - - $hmrModuleData$ = currentModuleData; - - $interceptModuleExecution$.push(function (options) { - var module = options.module; - var require = createRequire(options.require, options.id); - module.hot = createModuleHotObject(options.id, module); - module.parents = currentParents; - module.children = []; - currentParents = []; - options.require = require; - }); - - $hmrDownloadUpdateHandlers$ = {}; - $hmrInvalidateModuleHandlers$ = {}; - - function createRequire(require, moduleId) { - var me = installedModules[moduleId]; - if (!me) return require; - var fn = function (request) { - if (me.hot.active) { - if (installedModules[request]) { - var parents = installedModules[request].parents; - if (parents.indexOf(moduleId) === -1) { - parents.push(moduleId); - } - } else { - currentParents = [moduleId]; - currentChildModule = request; - } - if (me.children.indexOf(request) === -1) { - me.children.push(request); - } - } else { - console.warn( - "[HMR] unexpected require(" + - request + - ") from disposed module " + - moduleId - ); - currentParents = []; - } - return require(request); - }; - var createPropertyDescriptor = function (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") { - Object.defineProperty(fn, name, createPropertyDescriptor(name)); - } - } - fn.e = function (chunkId, fetchPriority) { - return trackBlockingPromise(require.e(chunkId, fetchPriority)); - }; - return fn; - } - - function createModuleHotObject(moduleId, me) { - var _main = currentChildModule !== moduleId; - var hot = { - // private stuff - _acceptedDependencies: {}, - _acceptedErrorHandlers: {}, - _declinedDependencies: {}, - _selfAccepted: false, - _selfDeclined: false, - _selfInvalidated: false, - _disposeHandlers: [], - _main: _main, - _requireSelf: function () { - currentParents = me.parents.slice(); - currentChildModule = _main ? undefined : moduleId; - __webpack_require__(moduleId); - }, - - // Module API - active: true, - accept: function (dep, callback, errorHandler) { - if (dep === undefined) hot._selfAccepted = true; - else if (typeof dep === "function") hot._selfAccepted = dep; - else if (typeof dep === "object" && dep !== null) { - for (var i = 0; i < dep.length; i++) { - hot._acceptedDependencies[dep[i]] = callback || function () {}; - hot._acceptedErrorHandlers[dep[i]] = errorHandler; - } - } else { - hot._acceptedDependencies[dep] = callback || function () {}; - hot._acceptedErrorHandlers[dep] = errorHandler; - } - }, - decline: function (dep) { - if (dep === undefined) hot._selfDeclined = true; - else if (typeof dep === "object" && dep !== null) - 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); - }, - invalidate: function () { - this._selfInvalidated = true; - switch (currentStatus) { - case "idle": - currentUpdateApplyHandlers = []; - Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { - $hmrInvalidateModuleHandlers$[key]( - moduleId, - currentUpdateApplyHandlers - ); - }); - setStatus("ready"); - break; - case "ready": - Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { - $hmrInvalidateModuleHandlers$[key]( - moduleId, - currentUpdateApplyHandlers - ); - }); - break; - case "prepare": - case "check": - case "dispose": - case "apply": - (queuedInvalidatedModules = queuedInvalidatedModules || []).push( - moduleId - ); - break; - default: - // ignore requests in error states - break; - } - }, - - // Management API - check: hotCheck, - apply: hotApply, - status: function (l) { - if (!l) return currentStatus; - registeredStatusHandlers.push(l); - }, - addStatusHandler: function (l) { - registeredStatusHandlers.push(l); - }, - removeStatusHandler: function (l) { - var idx = registeredStatusHandlers.indexOf(l); - if (idx >= 0) registeredStatusHandlers.splice(idx, 1); - }, - - // inherit from previous dispose call - data: currentModuleData[moduleId] - }; - currentChildModule = undefined; - return hot; - } - - function setStatus(newStatus) { - currentStatus = newStatus; - var results = []; - - for (var i = 0; i < registeredStatusHandlers.length; i++) - results[i] = registeredStatusHandlers[i].call(null, newStatus); - - return Promise.all(results).then(function () {}); - } - - function unblock() { - if (--blockingPromises === 0) { - setStatus("ready").then(function () { - if (blockingPromises === 0) { - var list = blockingPromisesWaiting; - blockingPromisesWaiting = []; - for (var i = 0; i < list.length; i++) { - list[i](); - } - } - }); - } - } - - function trackBlockingPromise(promise) { - switch (currentStatus) { - case "ready": - setStatus("prepare"); - /* fallthrough */ - case "prepare": - blockingPromises++; - promise.then(unblock, unblock); - return promise; - default: - return promise; - } - } - - function waitForBlockingPromises(fn) { - if (blockingPromises === 0) return fn(); - return new Promise(function (resolve) { - blockingPromisesWaiting.push(function () { - resolve(fn()); - }); - }); - } - - function hotCheck(applyOnUpdate) { - if (currentStatus !== "idle") { - throw new Error("check() is only allowed in idle status"); - } - return setStatus("check") - .then($hmrDownloadManifest$) - .then(function (update) { - if (!update) { - return setStatus(applyInvalidatedModules() ? "ready" : "idle").then( - function () { - return null; - } - ); - } - - return setStatus("prepare").then(function () { - var updatedModules = []; - currentUpdateApplyHandlers = []; - - return Promise.all( - Object.keys($hmrDownloadUpdateHandlers$).reduce(function ( - promises, - key - ) { - $hmrDownloadUpdateHandlers$[key]( - update.c, - update.r, - update.m, - promises, - currentUpdateApplyHandlers, - updatedModules - ); - return promises; - }, []) - ).then(function () { - return waitForBlockingPromises(function () { - if (applyOnUpdate) { - return internalApply(applyOnUpdate); - } - return setStatus("ready").then(function () { - return updatedModules; - }); - }); - }); - }); - }); - } - - function hotApply(options) { - if (currentStatus !== "ready") { - return Promise.resolve().then(function () { - throw new Error( - "apply() is only allowed in ready status (state: " + - currentStatus + - ")" - ); - }); - } - return internalApply(options); - } - - function internalApply(options) { - options = options || {}; - - applyInvalidatedModules(); - - var results = currentUpdateApplyHandlers.map(function (handler) { - return handler(options); - }); - currentUpdateApplyHandlers = undefined; - - var errors = results - .map(function (r) { - return r.error; - }) - .filter(Boolean); - - if (errors.length > 0) { - return setStatus("abort").then(function () { - throw errors[0]; - }); - } - - // Now in "dispose" phase - var disposePromise = setStatus("dispose"); - - results.forEach(function (result) { - if (result.dispose) result.dispose(); - }); - - // Now in "apply" phase - var applyPromise = setStatus("apply"); - - var error; - var reportError = function (err) { - if (!error) error = err; - }; - - var outdatedModules = []; - results.forEach(function (result) { - if (result.apply) { - var modules = result.apply(reportError); - if (modules) { - for (var i = 0; i < modules.length; i++) { - outdatedModules.push(modules[i]); - } - } - } - }); - - return Promise.all([disposePromise, applyPromise]).then(function () { - // handle errors in accept handlers and self accepted module load - if (error) { - return setStatus("fail").then(function () { - throw error; - }); - } - - if (queuedInvalidatedModules) { - return internalApply(options).then(function (list) { - outdatedModules.forEach(function (moduleId) { - if (list.indexOf(moduleId) < 0) list.push(moduleId); - }); - return list; - }); - } - - return setStatus("idle").then(function () { - return outdatedModules; - }); - }); - } - - function applyInvalidatedModules() { - if (queuedInvalidatedModules) { - if (!currentUpdateApplyHandlers) currentUpdateApplyHandlers = []; - Object.keys($hmrInvalidateModuleHandlers$).forEach(function (key) { - queuedInvalidatedModules.forEach(function (moduleId) { - $hmrInvalidateModuleHandlers$[key]( - moduleId, - currentUpdateApplyHandlers - ); - }); - }); - queuedInvalidatedModules = undefined; - return true; - } - } -}; diff --git a/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js b/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js deleted file mode 100644 index b1a254b6db9..00000000000 --- a/webpack-lib/lib/hmr/HotModuleReplacementRuntimeModule.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -class HotModuleReplacementRuntimeModule extends RuntimeModule { - constructor() { - super("hot module replacement", RuntimeModule.STAGE_BASIC); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return Template.getFunctionContent( - require("./HotModuleReplacement.runtime.js") - ) - .replace( - /\$interceptModuleExecution\$/g, - RuntimeGlobals.interceptModuleExecution - ) - .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) - .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) - .replace(/\$hmrDownloadManifest\$/g, RuntimeGlobals.hmrDownloadManifest) - .replace( - /\$hmrInvalidateModuleHandlers\$/g, - RuntimeGlobals.hmrInvalidateModuleHandlers - ) - .replace( - /\$hmrDownloadUpdateHandlers\$/g, - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - } -} - -module.exports = HotModuleReplacementRuntimeModule; diff --git a/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js b/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js deleted file mode 100644 index ad26d8772c1..00000000000 --- a/webpack-lib/lib/hmr/JavascriptHotModuleReplacement.runtime.js +++ /dev/null @@ -1,461 +0,0 @@ -// @ts-nocheck -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -var $installedChunks$ = undefined; -var $loadUpdateChunk$ = undefined; -var $moduleCache$ = undefined; -var $moduleFactories$ = undefined; -var $ensureChunkHandlers$ = undefined; -var $hasOwnProperty$ = undefined; -var $hmrModuleData$ = undefined; -var $hmrDownloadUpdateHandlers$ = undefined; -var $hmrInvalidateModuleHandlers$ = undefined; -var __webpack_require__ = undefined; - -module.exports = function () { - var currentUpdateChunks; - var currentUpdate; - var currentUpdateRemovedChunks; - var currentUpdateRuntime; - function applyHandler(options) { - if ($ensureChunkHandlers$) delete $ensureChunkHandlers$.$key$Hmr; - currentUpdateChunks = undefined; - function getAffectedModuleEffects(updateModuleId) { - var outdatedModules = [updateModuleId]; - var outdatedDependencies = {}; - - var queue = outdatedModules.map(function (id) { - return { - chain: [id], - id: id - }; - }); - while (queue.length > 0) { - var queueItem = queue.pop(); - var moduleId = queueItem.id; - var chain = queueItem.chain; - var module = $moduleCache$[moduleId]; - if ( - !module || - (module.hot._selfAccepted && !module.hot._selfInvalidated) - ) - 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 = $moduleCache$[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(module) { - console.warn( - "[HMR] unexpected require(" + module.id + ") to disposed module" - ); - }; - - for (var moduleId in currentUpdate) { - if ($hasOwnProperty$(currentUpdate, moduleId)) { - var newModuleFactory = currentUpdate[moduleId]; - /** @type {TODO} */ - var result = newModuleFactory - ? getAffectedModuleEffects(moduleId) - : { - type: "disposed", - moduleId: moduleId - }; - /** @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) { - return { - error: abortError - }; - } - if (doApply) { - appliedUpdate[moduleId] = newModuleFactory; - addAllToSet(outdatedModules, result.outdatedModules); - for (moduleId in result.outdatedDependencies) { - if ($hasOwnProperty$(result.outdatedDependencies, moduleId)) { - if (!outdatedDependencies[moduleId]) - outdatedDependencies[moduleId] = []; - addAllToSet( - outdatedDependencies[moduleId], - result.outdatedDependencies[moduleId] - ); - } - } - } - if (doDispose) { - addAllToSet(outdatedModules, [result.moduleId]); - appliedUpdate[moduleId] = warnUnexpectedRequire; - } - } - } - currentUpdate = undefined; - - // Store self accepted outdated modules to require them later by the module system - var outdatedSelfAcceptedModules = []; - for (var j = 0; j < outdatedModules.length; j++) { - var outdatedModuleId = outdatedModules[j]; - var module = $moduleCache$[outdatedModuleId]; - if ( - module && - (module.hot._selfAccepted || module.hot._main) && - // removed self-accepted modules should not be required - appliedUpdate[outdatedModuleId] !== warnUnexpectedRequire && - // when called invalidate self-accepting is not possible - !module.hot._selfInvalidated - ) { - outdatedSelfAcceptedModules.push({ - module: outdatedModuleId, - require: module.hot._requireSelf, - errorHandler: module.hot._selfAccepted - }); - } - } - - var moduleOutdatedDependencies; - - return { - dispose: function () { - currentUpdateRemovedChunks.forEach(function (chunkId) { - delete $installedChunks$[chunkId]; - }); - currentUpdateRemovedChunks = undefined; - - var idx; - var queue = outdatedModules.slice(); - while (queue.length > 0) { - var moduleId = queue.pop(); - var module = $moduleCache$[moduleId]; - if (!module) continue; - - var data = {}; - - // Call dispose handlers - var disposeHandlers = module.hot._disposeHandlers; - for (j = 0; j < disposeHandlers.length; j++) { - disposeHandlers[j].call(null, data); - } - $hmrModuleData$[moduleId] = data; - - // disable module (this disables requires from this module) - module.hot.active = false; - - // remove module from cache - delete $moduleCache$[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 = $moduleCache$[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; - for (var outdatedModuleId in outdatedDependencies) { - if ($hasOwnProperty$(outdatedDependencies, outdatedModuleId)) { - module = $moduleCache$[outdatedModuleId]; - if (module) { - moduleOutdatedDependencies = - outdatedDependencies[outdatedModuleId]; - for (j = 0; j < moduleOutdatedDependencies.length; j++) { - dependency = moduleOutdatedDependencies[j]; - idx = module.children.indexOf(dependency); - if (idx >= 0) module.children.splice(idx, 1); - } - } - } - } - }, - apply: function (reportError) { - // insert new code - for (var updateModuleId in appliedUpdate) { - if ($hasOwnProperty$(appliedUpdate, updateModuleId)) { - $moduleFactories$[updateModuleId] = appliedUpdate[updateModuleId]; - } - } - - // run new runtime modules - for (var i = 0; i < currentUpdateRuntime.length; i++) { - currentUpdateRuntime[i](__webpack_require__); - } - - // call accept handlers - for (var outdatedModuleId in outdatedDependencies) { - if ($hasOwnProperty$(outdatedDependencies, outdatedModuleId)) { - var module = $moduleCache$[outdatedModuleId]; - if (module) { - moduleOutdatedDependencies = - outdatedDependencies[outdatedModuleId]; - var callbacks = []; - var errorHandlers = []; - var dependenciesForCallbacks = []; - for (var j = 0; j < moduleOutdatedDependencies.length; j++) { - var dependency = moduleOutdatedDependencies[j]; - var acceptCallback = - module.hot._acceptedDependencies[dependency]; - var errorHandler = - module.hot._acceptedErrorHandlers[dependency]; - if (acceptCallback) { - if (callbacks.indexOf(acceptCallback) !== -1) continue; - callbacks.push(acceptCallback); - errorHandlers.push(errorHandler); - dependenciesForCallbacks.push(dependency); - } - } - for (var k = 0; k < callbacks.length; k++) { - try { - callbacks[k].call(null, moduleOutdatedDependencies); - } catch (err) { - if (typeof errorHandlers[k] === "function") { - try { - errorHandlers[k](err, { - moduleId: outdatedModuleId, - dependencyId: dependenciesForCallbacks[k] - }); - } catch (err2) { - if (options.onErrored) { - options.onErrored({ - type: "accept-error-handler-errored", - moduleId: outdatedModuleId, - dependencyId: dependenciesForCallbacks[k], - error: err2, - originalError: err - }); - } - if (!options.ignoreErrored) { - reportError(err2); - reportError(err); - } - } - } else { - if (options.onErrored) { - options.onErrored({ - type: "accept-errored", - moduleId: outdatedModuleId, - dependencyId: dependenciesForCallbacks[k], - error: err - }); - } - if (!options.ignoreErrored) { - reportError(err); - } - } - } - } - } - } - } - - // Load self accepted modules - for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) { - var item = outdatedSelfAcceptedModules[o]; - var moduleId = item.module; - try { - item.require(moduleId); - } catch (err) { - if (typeof item.errorHandler === "function") { - try { - item.errorHandler(err, { - moduleId: moduleId, - module: $moduleCache$[moduleId] - }); - } catch (err1) { - if (options.onErrored) { - options.onErrored({ - type: "self-accept-error-handler-errored", - moduleId: moduleId, - error: err1, - originalError: err - }); - } - if (!options.ignoreErrored) { - reportError(err1); - reportError(err); - } - } - } else { - if (options.onErrored) { - options.onErrored({ - type: "self-accept-errored", - moduleId: moduleId, - error: err - }); - } - if (!options.ignoreErrored) { - reportError(err); - } - } - } - } - - return outdatedModules; - } - }; - } - $hmrInvalidateModuleHandlers$.$key$ = function (moduleId, applyHandlers) { - if (!currentUpdate) { - currentUpdate = {}; - currentUpdateRuntime = []; - currentUpdateRemovedChunks = []; - applyHandlers.push(applyHandler); - } - if (!$hasOwnProperty$(currentUpdate, moduleId)) { - currentUpdate[moduleId] = $moduleFactories$[moduleId]; - } - }; - $hmrDownloadUpdateHandlers$.$key$ = function ( - chunkIds, - removedChunks, - removedModules, - promises, - applyHandlers, - updatedModulesList - ) { - applyHandlers.push(applyHandler); - currentUpdateChunks = {}; - currentUpdateRemovedChunks = removedChunks; - currentUpdate = removedModules.reduce(function (obj, key) { - obj[key] = false; - return obj; - }, {}); - currentUpdateRuntime = []; - chunkIds.forEach(function (chunkId) { - if ( - $hasOwnProperty$($installedChunks$, chunkId) && - $installedChunks$[chunkId] !== undefined - ) { - promises.push($loadUpdateChunk$(chunkId, updatedModulesList)); - currentUpdateChunks[chunkId] = true; - } else { - currentUpdateChunks[chunkId] = false; - } - }); - if ($ensureChunkHandlers$) { - $ensureChunkHandlers$.$key$Hmr = function (chunkId, promises) { - if ( - currentUpdateChunks && - $hasOwnProperty$(currentUpdateChunks, chunkId) && - !currentUpdateChunks[chunkId] - ) { - promises.push($loadUpdateChunk$(chunkId)); - currentUpdateChunks[chunkId] = true; - } - }; - } - }; -}; diff --git a/webpack-lib/lib/hmr/LazyCompilationPlugin.js b/webpack-lib/lib/hmr/LazyCompilationPlugin.js deleted file mode 100644 index 083cefb31fc..00000000000 --- a/webpack-lib/lib/hmr/LazyCompilationPlugin.js +++ /dev/null @@ -1,455 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const Dependency = require("../Dependency"); -const Module = require("../Module"); -const ModuleFactory = require("../ModuleFactory"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const { - WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency"); -const { registerNotSerializable } = require("../util/serialization"); - -/** @typedef {import("../../declarations/WebpackOptions")} WebpackOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../dependencies/HarmonyImportDependency")} HarmonyImportDependency */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ - -/** @typedef {{ client: string, data: string, active: boolean }} ModuleResult */ - -/** - * @typedef {object} BackendApi - * @property {function(function((Error | null)=) : void): void} dispose - * @property {function(Module): ModuleResult} module - */ - -const HMR_DEPENDENCY_TYPES = new Set([ - "import.meta.webpackHot.accept", - "import.meta.webpackHot.decline", - "module.hot.accept", - "module.hot.decline" -]); - -/** - * @param {undefined|string|RegExp|Function} test test option - * @param {Module} module the module - * @returns {boolean | null | string} true, if the module should be selected - */ -const checkTest = (test, module) => { - if (test === undefined) return true; - if (typeof test === "function") { - return test(module); - } - if (typeof test === "string") { - const name = module.nameForCondition(); - return name && name.startsWith(test); - } - if (test instanceof RegExp) { - const name = module.nameForCondition(); - return name && test.test(name); - } - return false; -}; - -class LazyCompilationDependency extends Dependency { - /** - * @param {LazyCompilationProxyModule} proxyModule proxy module - */ - constructor(proxyModule) { - super(); - this.proxyModule = proxyModule; - } - - get category() { - return "esm"; - } - - get type() { - return "lazy import()"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return this.proxyModule.originalModule.identifier(); - } -} - -registerNotSerializable(LazyCompilationDependency); - -class LazyCompilationProxyModule extends Module { - /** - * @param {string} context context - * @param {Module} originalModule an original module - * @param {string} request request - * @param {ModuleResult["client"]} client client - * @param {ModuleResult["data"]} data data - * @param {ModuleResult["active"]} active true when active, otherwise false - */ - constructor(context, originalModule, request, client, data, active) { - super( - WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY, - context, - originalModule.layer - ); - this.originalModule = originalModule; - this.request = request; - this.client = client; - this.data = data; - this.active = active; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY}|${this.originalModule.identifier()}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY} ${this.originalModule.readableIdentifier( - requestShortener - )}`; - } - - /** - * 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) { - super.updateCacheModule(module); - const m = /** @type {LazyCompilationProxyModule} */ (module); - this.originalModule = m.originalModule; - this.request = m.request; - this.client = m.client; - this.data = m.data; - this.active = m.active; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return `${this.originalModule.libIdent( - options - )}!${WEBPACK_MODULE_TYPE_LAZY_COMPILATION_PROXY}`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - callback(null, !this.buildInfo || this.buildInfo.active !== this.active); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildInfo = { - active: this.active - }; - /** @type {BuildMeta} */ - this.buildMeta = {}; - this.clearDependenciesAndBlocks(); - const dep = new CommonJsRequireDependency(this.client); - this.addDependency(dep); - if (this.active) { - const dep = new LazyCompilationDependency(this); - const block = new AsyncDependenciesBlock({}); - block.addDependency(dep); - this.addBlock(block); - } - callback(); - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - /** - * @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 200; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ runtimeTemplate, chunkGraph, moduleGraph }) { - const sources = new Map(); - const runtimeRequirements = new Set(); - runtimeRequirements.add(RuntimeGlobals.module); - const clientDep = /** @type {CommonJsRequireDependency} */ ( - this.dependencies[0] - ); - const clientModule = moduleGraph.getModule(clientDep); - const block = this.blocks[0]; - const client = Template.asString([ - `var client = ${runtimeTemplate.moduleExports({ - module: clientModule, - chunkGraph, - request: clientDep.userRequest, - runtimeRequirements - })}`, - `var data = ${JSON.stringify(this.data)};` - ]); - const keepActive = Template.asString([ - `var dispose = client.keepAlive({ data: data, active: ${JSON.stringify( - Boolean(block) - )}, module: module, onError: onError });` - ]); - let source; - if (block) { - const dep = block.dependencies[0]; - const module = /** @type {Module} */ (moduleGraph.getModule(dep)); - source = Template.asString([ - client, - `module.exports = ${runtimeTemplate.moduleNamespacePromise({ - chunkGraph, - block, - module, - request: this.request, - strict: false, // TODO this should be inherited from the original module - message: "import()", - runtimeRequirements - })};`, - "if (module.hot) {", - Template.indent([ - "module.hot.accept();", - `module.hot.accept(${JSON.stringify( - chunkGraph.getModuleId(module) - )}, function() { module.hot.invalidate(); });`, - "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });", - "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);" - ]), - "}", - "function onError() { /* ignore */ }", - keepActive - ]); - } else { - source = Template.asString([ - client, - "var resolveSelf, onError;", - "module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });", - "if (module.hot) {", - Template.indent([ - "module.hot.accept();", - "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);", - "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });" - ]), - "}", - keepActive - ]); - } - sources.set("javascript", new RawSource(source)); - return { - sources, - runtimeRequirements - }; - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - super.updateHash(hash, context); - hash.update(this.active ? "active" : ""); - hash.update(JSON.stringify(this.data)); - } -} - -registerNotSerializable(LazyCompilationProxyModule); - -class LazyCompilationDependencyFactory extends ModuleFactory { - constructor() { - super(); - } - - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const dependency = /** @type {LazyCompilationDependency} */ ( - data.dependencies[0] - ); - callback(null, { - module: dependency.proxyModule.originalModule - }); - } -} - -/** - * @callback BackendHandler - * @param {Compiler} compiler compiler - * @param {function(Error | null, BackendApi=): void} callback callback - * @returns {void} - */ - -/** - * @callback PromiseBackendHandler - * @param {Compiler} compiler compiler - * @returns {Promise} backend - */ - -class LazyCompilationPlugin { - /** - * @param {object} options options - * @param {BackendHandler | PromiseBackendHandler} options.backend the backend - * @param {boolean} options.entries true, when entries are lazy compiled - * @param {boolean} options.imports true, when import() modules are lazy compiled - * @param {RegExp | string | (function(Module): boolean) | undefined} options.test additional filter for lazy compiled entrypoint modules - */ - constructor({ backend, entries, imports, test }) { - this.backend = backend; - this.entries = entries; - this.imports = imports; - this.test = test; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - /** @type {BackendApi} */ - let backend; - compiler.hooks.beforeCompile.tapAsync( - "LazyCompilationPlugin", - (params, callback) => { - if (backend !== undefined) return callback(); - const promise = this.backend(compiler, (err, result) => { - if (err) return callback(err); - backend = /** @type {BackendApi} */ (result); - callback(); - }); - if (promise && promise.then) { - promise.then(b => { - backend = b; - callback(); - }, callback); - } - } - ); - compiler.hooks.thisCompilation.tap( - "LazyCompilationPlugin", - (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.module.tap( - "LazyCompilationPlugin", - (originalModule, createData, resolveData) => { - if ( - resolveData.dependencies.every(dep => - HMR_DEPENDENCY_TYPES.has(dep.type) - ) - ) { - // for HMR only resolving, try to determine if the HMR accept/decline refers to - // an import() or not - const hmrDep = resolveData.dependencies[0]; - const originModule = - /** @type {Module} */ - (compilation.moduleGraph.getParentModule(hmrDep)); - const isReferringToDynamicImport = originModule.blocks.some( - block => - block.dependencies.some( - dep => - dep.type === "import()" && - /** @type {HarmonyImportDependency} */ (dep).request === - hmrDep.request - ) - ); - if (!isReferringToDynamicImport) return; - } else if ( - !resolveData.dependencies.every( - dep => - HMR_DEPENDENCY_TYPES.has(dep.type) || - (this.imports && - (dep.type === "import()" || - dep.type === "import() context element")) || - (this.entries && dep.type === "entry") - ) - ) - return; - if ( - /webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client|webpack-hot-middleware[/\\]client/.test( - resolveData.request - ) || - !checkTest(this.test, originalModule) - ) - return; - const moduleInfo = backend.module(originalModule); - if (!moduleInfo) return; - const { client, data, active } = moduleInfo; - - return new LazyCompilationProxyModule( - compiler.context, - originalModule, - resolveData.request, - client, - data, - active - ); - } - ); - compilation.dependencyFactories.set( - LazyCompilationDependency, - new LazyCompilationDependencyFactory() - ); - } - ); - compiler.hooks.shutdown.tapAsync("LazyCompilationPlugin", callback => { - backend.dispose(callback); - }); - } -} - -module.exports = LazyCompilationPlugin; diff --git a/webpack-lib/lib/hmr/lazyCompilationBackend.js b/webpack-lib/lib/hmr/lazyCompilationBackend.js deleted file mode 100644 index 383a16dd499..00000000000 --- a/webpack-lib/lib/hmr/lazyCompilationBackend.js +++ /dev/null @@ -1,161 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("http").IncomingMessage} IncomingMessage */ -/** @typedef {import("http").RequestListener} RequestListener */ -/** @typedef {import("http").ServerOptions} HttpServerOptions */ -/** @typedef {import("http").ServerResponse} ServerResponse */ -/** @typedef {import("https").ServerOptions} HttpsServerOptions */ -/** @typedef {import("net").AddressInfo} AddressInfo */ -/** @typedef {import("net").Server} Server */ -/** @typedef {import("../../declarations/WebpackOptions").LazyCompilationDefaultBackendOptions} LazyCompilationDefaultBackendOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("./LazyCompilationPlugin").BackendApi} BackendApi */ -/** @typedef {import("./LazyCompilationPlugin").BackendHandler} BackendHandler */ - -/** - * @param {Omit & { client: NonNullable}} options additional options for the backend - * @returns {BackendHandler} backend - */ -module.exports = options => (compiler, callback) => { - const logger = compiler.getInfrastructureLogger("LazyCompilationBackend"); - const activeModules = new Map(); - const prefix = "/lazy-compilation-using-"; - - const isHttps = - options.protocol === "https" || - (typeof options.server === "object" && - ("key" in options.server || "pfx" in options.server)); - - const createServer = - typeof options.server === "function" - ? options.server - : (() => { - const http = isHttps ? require("https") : require("http"); - return http.createServer.bind( - http, - /** @type {HttpServerOptions | HttpsServerOptions} */ - (options.server) - ); - })(); - /** @type {function(Server): void} */ - const listen = - typeof options.listen === "function" - ? options.listen - : server => { - let listen = options.listen; - if (typeof listen === "object" && !("port" in listen)) - listen = { ...listen, port: undefined }; - server.listen(listen); - }; - - const protocol = options.protocol || (isHttps ? "https" : "http"); - - /** @type {RequestListener} */ - const requestListener = (req, res) => { - if (req.url === undefined) return; - const keys = req.url.slice(prefix.length).split("@"); - req.socket.on("close", () => { - setTimeout(() => { - for (const key of keys) { - const oldValue = activeModules.get(key) || 0; - activeModules.set(key, oldValue - 1); - if (oldValue === 1) { - logger.log( - `${key} is no longer in use. Next compilation will skip this module.` - ); - } - } - }, 120000); - }); - req.socket.setNoDelay(true); - res.writeHead(200, { - "content-type": "text/event-stream", - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "*", - "Access-Control-Allow-Headers": "*" - }); - res.write("\n"); - let moduleActivated = false; - for (const key of keys) { - const oldValue = activeModules.get(key) || 0; - activeModules.set(key, oldValue + 1); - if (oldValue === 0) { - logger.log(`${key} is now in use and will be compiled.`); - moduleActivated = true; - } - } - if (moduleActivated && compiler.watching) compiler.watching.invalidate(); - }; - - const server = /** @type {Server} */ (createServer()); - server.on("request", requestListener); - - let isClosing = false; - /** @type {Set} */ - const sockets = new Set(); - server.on("connection", socket => { - sockets.add(socket); - socket.on("close", () => { - sockets.delete(socket); - }); - if (isClosing) socket.destroy(); - }); - server.on("clientError", e => { - if (e.message !== "Server is disposing") logger.warn(e); - }); - - server.on( - "listening", - /** - * @param {Error} err error - * @returns {void} - */ - err => { - if (err) return callback(err); - const _addr = server.address(); - if (typeof _addr === "string") - throw new Error("addr must not be a string"); - const addr = /** @type {AddressInfo} */ (_addr); - const urlBase = - addr.address === "::" || addr.address === "0.0.0.0" - ? `${protocol}://localhost:${addr.port}` - : addr.family === "IPv6" - ? `${protocol}://[${addr.address}]:${addr.port}` - : `${protocol}://${addr.address}:${addr.port}`; - logger.log( - `Server-Sent-Events server for lazy compilation open at ${urlBase}.` - ); - callback(null, { - dispose(callback) { - isClosing = true; - // Removing the listener is a workaround for a memory leak in node.js - server.off("request", requestListener); - server.close(err => { - callback(err); - }); - for (const socket of sockets) { - socket.destroy(new Error("Server is disposing")); - } - }, - module(originalModule) { - const key = `${encodeURIComponent( - originalModule.identifier().replace(/\\/g, "/").replace(/@/g, "_") - ).replace(/%(2F|3A|24|26|2B|2C|3B|3D)/g, decodeURIComponent)}`; - const active = activeModules.get(key) > 0; - return { - client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`, - data: key, - active - }; - } - }); - } - ); - listen(server); -}; diff --git a/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js b/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js deleted file mode 100644 index e0922b43be8..00000000000 --- a/webpack-lib/lib/ids/ChunkModuleIdRangePlugin.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { find } = require("../util/SetHelpers"); -const { - compareModulesByPreOrderIndexOrIdentifier, - compareModulesByPostOrderIndexOrIdentifier -} = require("../util/comparators"); - -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} ChunkModuleIdRangePluginOptions - * @property {string} name the chunk name - * @property {("index" | "index2" | "preOrderIndex" | "postOrderIndex")=} order order - * @property {number=} start start id - * @property {number=} end end id - */ - -class ChunkModuleIdRangePlugin { - /** - * @param {ChunkModuleIdRangePluginOptions} options options object - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap("ChunkModuleIdRangePlugin", compilation => { - const moduleGraph = compilation.moduleGraph; - compilation.hooks.moduleIds.tap("ChunkModuleIdRangePlugin", modules => { - const chunkGraph = compilation.chunkGraph; - const chunk = find( - compilation.chunks, - chunk => chunk.name === options.name - ); - if (!chunk) { - throw new Error( - `ChunkModuleIdRangePlugin: Chunk with name '${options.name}"' was not found` - ); - } - - let chunkModules; - if (options.order) { - let cmpFn; - switch (options.order) { - case "index": - case "preOrderIndex": - cmpFn = compareModulesByPreOrderIndexOrIdentifier(moduleGraph); - break; - case "index2": - case "postOrderIndex": - cmpFn = compareModulesByPostOrderIndexOrIdentifier(moduleGraph); - break; - default: - throw new Error( - "ChunkModuleIdRangePlugin: unexpected value of order" - ); - } - chunkModules = chunkGraph.getOrderedChunkModules(chunk, cmpFn); - } else { - chunkModules = Array.from(modules) - .filter(m => chunkGraph.isModuleInChunk(m, chunk)) - .sort(compareModulesByPreOrderIndexOrIdentifier(moduleGraph)); - } - - let currentId = options.start || 0; - for (let i = 0; i < chunkModules.length; i++) { - const m = chunkModules[i]; - if (m.needId && chunkGraph.getModuleId(m) === null) { - chunkGraph.setModuleId(m, currentId++); - } - if (options.end && currentId > options.end) break; - } - }); - }); - } -} -module.exports = ChunkModuleIdRangePlugin; diff --git a/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js b/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js deleted file mode 100644 index 735bc5f166a..00000000000 --- a/webpack-lib/lib/ids/DeterministicChunkIdsPlugin.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const { compareChunksNatural } = require("../util/comparators"); -const { - getFullChunkName, - getUsedChunkIds, - assignDeterministicIds -} = require("./IdHelpers"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -/** - * @typedef {object} DeterministicChunkIdsPluginOptions - * @property {string=} context context for ids - * @property {number=} maxLength maximum length of ids - */ - -class DeterministicChunkIdsPlugin { - /** - * @param {DeterministicChunkIdsPluginOptions} [options] options - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DeterministicChunkIdsPlugin", - compilation => { - compilation.hooks.chunkIds.tap( - "DeterministicChunkIdsPlugin", - chunks => { - const chunkGraph = compilation.chunkGraph; - const context = this.options.context - ? this.options.context - : compiler.context; - const maxLength = this.options.maxLength || 3; - - const compareNatural = compareChunksNatural(chunkGraph); - - const usedIds = getUsedChunkIds(compilation); - assignDeterministicIds( - Array.from(chunks).filter(chunk => chunk.id === null), - chunk => - getFullChunkName(chunk, chunkGraph, context, compiler.root), - compareNatural, - (chunk, id) => { - const size = usedIds.size; - usedIds.add(`${id}`); - if (size === usedIds.size) return false; - chunk.id = id; - chunk.ids = [id]; - return true; - }, - [10 ** maxLength], - 10, - usedIds.size - ); - } - ); - } - ); - } -} - -module.exports = DeterministicChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js b/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js deleted file mode 100644 index 8cf07e082c3..00000000000 --- a/webpack-lib/lib/ids/DeterministicModuleIdsPlugin.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const { - compareModulesByPreOrderIndexOrIdentifier -} = require("../util/comparators"); -const { - getUsedModuleIdsAndModules, - getFullModuleName, - assignDeterministicIds -} = require("./IdHelpers"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -/** - * @typedef {object} DeterministicModuleIdsPluginOptions - * @property {string=} context context relative to which module identifiers are computed - * @property {function(Module): boolean=} test selector function for modules - * @property {number=} maxLength maximum id length in digits (used as starting point) - * @property {number=} salt hash salt for ids - * @property {boolean=} fixedLength do not increase the maxLength to find an optimal id space size - * @property {boolean=} failOnConflict throw an error when id conflicts occur (instead of rehashing) - */ - -class DeterministicModuleIdsPlugin { - /** - * @param {DeterministicModuleIdsPluginOptions} [options] options - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DeterministicModuleIdsPlugin", - compilation => { - compilation.hooks.moduleIds.tap("DeterministicModuleIdsPlugin", () => { - const chunkGraph = compilation.chunkGraph; - const context = this.options.context - ? this.options.context - : compiler.context; - const maxLength = this.options.maxLength || 3; - const failOnConflict = this.options.failOnConflict || false; - const fixedLength = this.options.fixedLength || false; - const salt = this.options.salt || 0; - let conflicts = 0; - - const [usedIds, modules] = getUsedModuleIdsAndModules( - compilation, - this.options.test - ); - assignDeterministicIds( - modules, - module => getFullModuleName(module, context, compiler.root), - failOnConflict - ? () => 0 - : compareModulesByPreOrderIndexOrIdentifier( - compilation.moduleGraph - ), - (module, id) => { - const size = usedIds.size; - usedIds.add(`${id}`); - if (size === usedIds.size) { - conflicts++; - return false; - } - chunkGraph.setModuleId(module, id); - return true; - }, - [10 ** maxLength], - fixedLength ? 0 : 10, - usedIds.size, - salt - ); - if (failOnConflict && conflicts) - throw new Error( - `Assigning deterministic module ids has lead to ${conflicts} conflict${ - conflicts > 1 ? "s" : "" - }.\nIncrease the 'maxLength' to increase the id space and make conflicts less likely (recommended when there are many conflicts or application is expected to grow), or add an 'salt' number to try another hash starting value in the same id space (recommended when there is only a single conflict).` - ); - }); - } - ); - } -} - -module.exports = DeterministicModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/HashedModuleIdsPlugin.js b/webpack-lib/lib/ids/HashedModuleIdsPlugin.js deleted file mode 100644 index e3891a4699e..00000000000 --- a/webpack-lib/lib/ids/HashedModuleIdsPlugin.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { - compareModulesByPreOrderIndexOrIdentifier -} = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const createHash = require("../util/createHash"); -const { - getUsedModuleIdsAndModules, - getFullModuleName -} = require("./IdHelpers"); - -/** @typedef {import("../../declarations/plugins/HashedModuleIdsPlugin").HashedModuleIdsPluginOptions} HashedModuleIdsPluginOptions */ -/** @typedef {import("../Compiler")} Compiler */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/HashedModuleIdsPlugin.check.js"), - () => require("../../schemas/plugins/HashedModuleIdsPlugin.json"), - { - name: "Hashed Module Ids Plugin", - baseDataPath: "options" - } -); - -class HashedModuleIdsPlugin { - /** - * @param {HashedModuleIdsPluginOptions=} options options object - */ - constructor(options = {}) { - validate(options); - - /** @type {HashedModuleIdsPluginOptions} */ - this.options = { - context: undefined, - hashFunction: "md4", - hashDigest: "base64", - hashDigestLength: 4, - ...options - }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap("HashedModuleIdsPlugin", compilation => { - compilation.hooks.moduleIds.tap("HashedModuleIdsPlugin", () => { - const chunkGraph = compilation.chunkGraph; - const context = this.options.context - ? this.options.context - : compiler.context; - - const [usedIds, modules] = getUsedModuleIdsAndModules(compilation); - const modulesInNaturalOrder = modules.sort( - compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph) - ); - for (const module of modulesInNaturalOrder) { - const ident = getFullModuleName(module, context, compiler.root); - const hash = createHash( - /** @type {NonNullable} */ ( - options.hashFunction - ) - ); - hash.update(ident || ""); - const hashId = /** @type {string} */ ( - hash.digest(options.hashDigest) - ); - let len = options.hashDigestLength; - while (usedIds.has(hashId.slice(0, len))) - /** @type {number} */ (len)++; - const moduleId = hashId.slice(0, len); - chunkGraph.setModuleId(module, moduleId); - usedIds.add(moduleId); - } - }); - }); - } -} - -module.exports = HashedModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/IdHelpers.js b/webpack-lib/lib/ids/IdHelpers.js deleted file mode 100644 index 78cdaef0508..00000000000 --- a/webpack-lib/lib/ids/IdHelpers.js +++ /dev/null @@ -1,473 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const createHash = require("../util/createHash"); -const { makePathsRelative } = require("../util/identifier"); -const numberHash = require("../util/numberHash"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {typeof import("../util/Hash")} Hash */ - -/** - * @param {string} str string to hash - * @param {number} len max length of the hash - * @param {string | Hash} hashFunction hash function to use - * @returns {string} hash - */ -const getHash = (str, len, hashFunction) => { - const hash = createHash(hashFunction); - hash.update(str); - const digest = /** @type {string} */ (hash.digest("hex")); - return digest.slice(0, len); -}; - -/** - * @param {string} str the string - * @returns {string} string prefixed by an underscore if it is a number - */ -const avoidNumber = str => { - // max length of a number is 21 chars, bigger numbers a written as "...e+xx" - if (str.length > 21) return str; - const firstChar = str.charCodeAt(0); - // skip everything that doesn't look like a number - // charCodes: "-": 45, "1": 49, "9": 57 - if (firstChar < 49) { - if (firstChar !== 45) return str; - } else if (firstChar > 57) { - return str; - } - if (str === String(Number(str))) { - return `_${str}`; - } - return str; -}; - -/** - * @param {string} request the request - * @returns {string} id representation - */ -const requestToId = request => - request.replace(/^(\.\.?\/)+/, "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_"); -module.exports.requestToId = requestToId; - -/** - * @param {string} string the string - * @param {string} delimiter separator for string and hash - * @param {string | Hash} hashFunction hash function to use - * @returns {string} string with limited max length to 100 chars - */ -const shortenLongString = (string, delimiter, hashFunction) => { - if (string.length < 100) return string; - return ( - string.slice(0, 100 - 6 - delimiter.length) + - delimiter + - getHash(string, 6, hashFunction) - ); -}; - -/** - * @param {Module} module the module - * @param {string} context context directory - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} short module name - */ -const getShortModuleName = (module, context, associatedObjectForCache) => { - const libIdent = module.libIdent({ context, associatedObjectForCache }); - if (libIdent) return avoidNumber(libIdent); - const nameForCondition = module.nameForCondition(); - if (nameForCondition) - return avoidNumber( - makePathsRelative(context, nameForCondition, associatedObjectForCache) - ); - return ""; -}; -module.exports.getShortModuleName = getShortModuleName; - -/** - * @param {string} shortName the short name - * @param {Module} module the module - * @param {string} context context directory - * @param {string | Hash} hashFunction hash function to use - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} long module name - */ -const getLongModuleName = ( - shortName, - module, - context, - hashFunction, - associatedObjectForCache -) => { - const fullName = getFullModuleName(module, context, associatedObjectForCache); - return `${shortName}?${getHash(fullName, 4, hashFunction)}`; -}; -module.exports.getLongModuleName = getLongModuleName; - -/** - * @param {Module} module the module - * @param {string} context context directory - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} full module name - */ -const getFullModuleName = (module, context, associatedObjectForCache) => - makePathsRelative(context, module.identifier(), associatedObjectForCache); -module.exports.getFullModuleName = getFullModuleName; - -/** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {string} context context directory - * @param {string} delimiter delimiter for names - * @param {string | Hash} hashFunction hash function to use - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} short chunk name - */ -const getShortChunkName = ( - chunk, - chunkGraph, - context, - delimiter, - hashFunction, - associatedObjectForCache -) => { - const modules = chunkGraph.getChunkRootModules(chunk); - const shortModuleNames = modules.map(m => - requestToId(getShortModuleName(m, context, associatedObjectForCache)) - ); - chunk.idNameHints.sort(); - const chunkName = Array.from(chunk.idNameHints) - .concat(shortModuleNames) - .filter(Boolean) - .join(delimiter); - return shortenLongString(chunkName, delimiter, hashFunction); -}; -module.exports.getShortChunkName = getShortChunkName; - -/** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {string} context context directory - * @param {string} delimiter delimiter for names - * @param {string | Hash} hashFunction hash function to use - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} short chunk name - */ -const getLongChunkName = ( - chunk, - chunkGraph, - context, - delimiter, - hashFunction, - associatedObjectForCache -) => { - const modules = chunkGraph.getChunkRootModules(chunk); - const shortModuleNames = modules.map(m => - requestToId(getShortModuleName(m, context, associatedObjectForCache)) - ); - const longModuleNames = modules.map(m => - requestToId( - getLongModuleName("", m, context, hashFunction, associatedObjectForCache) - ) - ); - chunk.idNameHints.sort(); - const chunkName = Array.from(chunk.idNameHints) - .concat(shortModuleNames, longModuleNames) - .filter(Boolean) - .join(delimiter); - return shortenLongString(chunkName, delimiter, hashFunction); -}; -module.exports.getLongChunkName = getLongChunkName; - -/** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {string} context context directory - * @param {object=} associatedObjectForCache an object to which the cache will be attached - * @returns {string} full chunk name - */ -const getFullChunkName = ( - chunk, - chunkGraph, - context, - associatedObjectForCache -) => { - if (chunk.name) return chunk.name; - const modules = chunkGraph.getChunkRootModules(chunk); - const fullModuleNames = modules.map(m => - makePathsRelative(context, m.identifier(), associatedObjectForCache) - ); - return fullModuleNames.join(); -}; -module.exports.getFullChunkName = getFullChunkName; - -/** - * @template K - * @template V - * @param {Map} map a map from key to values - * @param {K} key key - * @param {V} value value - * @returns {void} - */ -const addToMapOfItems = (map, key, value) => { - let array = map.get(key); - if (array === undefined) { - array = []; - map.set(key, array); - } - array.push(value); -}; - -/** - * @param {Compilation} compilation the compilation - * @param {function(Module): boolean=} filter filter modules - * @returns {[Set, Module[]]} used module ids as strings and modules without id matching the filter - */ -const getUsedModuleIdsAndModules = (compilation, filter) => { - const chunkGraph = compilation.chunkGraph; - - const modules = []; - - /** @type {Set} */ - const usedIds = new Set(); - if (compilation.usedModuleIds) { - for (const id of compilation.usedModuleIds) { - usedIds.add(String(id)); - } - } - - for (const module of compilation.modules) { - if (!module.needId) continue; - const moduleId = chunkGraph.getModuleId(module); - if (moduleId !== null) { - usedIds.add(String(moduleId)); - } else if ( - (!filter || filter(module)) && - chunkGraph.getNumberOfModuleChunks(module) !== 0 - ) { - modules.push(module); - } - } - - return [usedIds, modules]; -}; -module.exports.getUsedModuleIdsAndModules = getUsedModuleIdsAndModules; - -/** - * @param {Compilation} compilation the compilation - * @returns {Set} used chunk ids as strings - */ -const getUsedChunkIds = compilation => { - /** @type {Set} */ - const usedIds = new Set(); - if (compilation.usedChunkIds) { - for (const id of compilation.usedChunkIds) { - usedIds.add(String(id)); - } - } - - for (const chunk of compilation.chunks) { - const chunkId = chunk.id; - if (chunkId !== null) { - usedIds.add(String(chunkId)); - } - } - - return usedIds; -}; -module.exports.getUsedChunkIds = getUsedChunkIds; - -/** - * @template T - * @param {Iterable} items list of items to be named - * @param {function(T): string} getShortName get a short name for an item - * @param {function(T, string): string} getLongName get a long name for an item - * @param {function(T, T): -1|0|1} comparator order of items - * @param {Set} usedIds already used ids, will not be assigned - * @param {function(T, string): void} assignName assign a name to an item - * @returns {T[]} list of items without a name - */ -const assignNames = ( - items, - getShortName, - getLongName, - comparator, - usedIds, - assignName -) => { - /** @type {Map} */ - const nameToItems = new Map(); - - for (const item of items) { - const name = getShortName(item); - addToMapOfItems(nameToItems, name, item); - } - - /** @type {Map} */ - const nameToItems2 = new Map(); - - for (const [name, items] of nameToItems) { - if (items.length > 1 || !name) { - for (const item of items) { - const longName = getLongName(item, name); - addToMapOfItems(nameToItems2, longName, item); - } - } else { - addToMapOfItems(nameToItems2, name, items[0]); - } - } - - /** @type {T[]} */ - const unnamedItems = []; - - for (const [name, items] of nameToItems2) { - if (!name) { - for (const item of items) { - unnamedItems.push(item); - } - } else if (items.length === 1 && !usedIds.has(name)) { - assignName(items[0], name); - usedIds.add(name); - } else { - items.sort(comparator); - let i = 0; - for (const item of items) { - while (nameToItems2.has(name + i) && usedIds.has(name + i)) i++; - assignName(item, name + i); - usedIds.add(name + i); - i++; - } - } - } - - unnamedItems.sort(comparator); - return unnamedItems; -}; -module.exports.assignNames = assignNames; - -/** - * @template T - * @param {T[]} items list of items to be named - * @param {function(T): string} getName get a name for an item - * @param {function(T, T): -1|0|1} comparator order of items - * @param {function(T, number): boolean} assignId assign an id to an item - * @param {number[]} ranges usable ranges for ids - * @param {number} expandFactor factor to create more ranges - * @param {number} extraSpace extra space to allocate, i. e. when some ids are already used - * @param {number} salt salting number to initialize hashing - * @returns {void} - */ -const assignDeterministicIds = ( - items, - getName, - comparator, - assignId, - ranges = [10], - expandFactor = 10, - extraSpace = 0, - salt = 0 -) => { - items.sort(comparator); - - // max 5% fill rate - const optimalRange = Math.min( - items.length * 20 + extraSpace, - Number.MAX_SAFE_INTEGER - ); - - let i = 0; - let range = ranges[i]; - while (range < optimalRange) { - i++; - if (i < ranges.length) { - range = Math.min(ranges[i], Number.MAX_SAFE_INTEGER); - } else if (expandFactor) { - range = Math.min(range * expandFactor, Number.MAX_SAFE_INTEGER); - } else { - break; - } - } - - for (const item of items) { - const ident = getName(item); - let id; - let i = salt; - do { - id = numberHash(ident + i++, range); - } while (!assignId(item, id)); - } -}; -module.exports.assignDeterministicIds = assignDeterministicIds; - -/** - * @param {Set} usedIds used ids - * @param {Iterable} modules the modules - * @param {Compilation} compilation the compilation - * @returns {void} - */ -const assignAscendingModuleIds = (usedIds, modules, compilation) => { - const chunkGraph = compilation.chunkGraph; - - let nextId = 0; - let assignId; - if (usedIds.size > 0) { - /** - * @param {Module} module the module - */ - assignId = module => { - if (chunkGraph.getModuleId(module) === null) { - while (usedIds.has(String(nextId))) nextId++; - chunkGraph.setModuleId(module, nextId++); - } - }; - } else { - /** - * @param {Module} module the module - */ - assignId = module => { - if (chunkGraph.getModuleId(module) === null) { - chunkGraph.setModuleId(module, nextId++); - } - }; - } - for (const module of modules) { - assignId(module); - } -}; -module.exports.assignAscendingModuleIds = assignAscendingModuleIds; - -/** - * @param {Iterable} chunks the chunks - * @param {Compilation} compilation the compilation - * @returns {void} - */ -const assignAscendingChunkIds = (chunks, compilation) => { - const usedIds = getUsedChunkIds(compilation); - - let nextId = 0; - if (usedIds.size > 0) { - for (const chunk of chunks) { - if (chunk.id === null) { - while (usedIds.has(String(nextId))) nextId++; - chunk.id = nextId; - chunk.ids = [nextId]; - nextId++; - } - } - } else { - for (const chunk of chunks) { - if (chunk.id === null) { - chunk.id = nextId; - chunk.ids = [nextId]; - nextId++; - } - } - } -}; -module.exports.assignAscendingChunkIds = assignAscendingChunkIds; diff --git a/webpack-lib/lib/ids/NamedChunkIdsPlugin.js b/webpack-lib/lib/ids/NamedChunkIdsPlugin.js deleted file mode 100644 index f55a5875a7f..00000000000 --- a/webpack-lib/lib/ids/NamedChunkIdsPlugin.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareChunksNatural } = require("../util/comparators"); -const { - getShortChunkName, - getLongChunkName, - assignNames, - getUsedChunkIds, - assignAscendingChunkIds -} = require("./IdHelpers"); - -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} Output */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -/** - * @typedef {object} NamedChunkIdsPluginOptions - * @property {string} [context] context - * @property {string} [delimiter] delimiter - */ - -class NamedChunkIdsPlugin { - /** - * @param {NamedChunkIdsPluginOptions=} options options - */ - constructor(options) { - this.delimiter = (options && options.delimiter) || "-"; - this.context = options && options.context; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("NamedChunkIdsPlugin", compilation => { - const hashFunction = - /** @type {NonNullable} */ - (compilation.outputOptions.hashFunction); - compilation.hooks.chunkIds.tap("NamedChunkIdsPlugin", chunks => { - const chunkGraph = compilation.chunkGraph; - const context = this.context ? this.context : compiler.context; - const delimiter = this.delimiter; - - const unnamedChunks = assignNames( - Array.from(chunks).filter(chunk => { - if (chunk.name) { - chunk.id = chunk.name; - chunk.ids = [chunk.name]; - } - return chunk.id === null; - }), - chunk => - getShortChunkName( - chunk, - chunkGraph, - context, - delimiter, - hashFunction, - compiler.root - ), - chunk => - getLongChunkName( - chunk, - chunkGraph, - context, - delimiter, - hashFunction, - compiler.root - ), - compareChunksNatural(chunkGraph), - getUsedChunkIds(compilation), - (chunk, name) => { - chunk.id = name; - chunk.ids = [name]; - } - ); - if (unnamedChunks.length > 0) { - assignAscendingChunkIds(unnamedChunks, compilation); - } - }); - }); - } -} - -module.exports = NamedChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/NamedModuleIdsPlugin.js b/webpack-lib/lib/ids/NamedModuleIdsPlugin.js deleted file mode 100644 index 9656b8d917e..00000000000 --- a/webpack-lib/lib/ids/NamedModuleIdsPlugin.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareModulesByIdentifier } = require("../util/comparators"); -const { - getShortModuleName, - getLongModuleName, - assignNames, - getUsedModuleIdsAndModules, - assignAscendingModuleIds -} = require("./IdHelpers"); - -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} Output */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -/** - * @typedef {object} NamedModuleIdsPluginOptions - * @property {string} [context] context - */ - -class NamedModuleIdsPlugin { - /** - * @param {NamedModuleIdsPluginOptions} [options] options - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { root } = compiler; - compiler.hooks.compilation.tap("NamedModuleIdsPlugin", compilation => { - const hashFunction = - /** @type {NonNullable} */ - (compilation.outputOptions.hashFunction); - compilation.hooks.moduleIds.tap("NamedModuleIdsPlugin", () => { - const chunkGraph = compilation.chunkGraph; - const context = this.options.context - ? this.options.context - : compiler.context; - - const [usedIds, modules] = getUsedModuleIdsAndModules(compilation); - const unnamedModules = assignNames( - modules, - m => getShortModuleName(m, context, root), - (m, shortName) => - getLongModuleName(shortName, m, context, hashFunction, root), - compareModulesByIdentifier, - usedIds, - (m, name) => chunkGraph.setModuleId(m, name) - ); - if (unnamedModules.length > 0) { - assignAscendingModuleIds(usedIds, unnamedModules, compilation); - } - }); - }); - } -} - -module.exports = NamedModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js b/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js deleted file mode 100644 index 5329ac51faf..00000000000 --- a/webpack-lib/lib/ids/NaturalChunkIdsPlugin.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareChunksNatural } = require("../util/comparators"); -const { assignAscendingChunkIds } = require("./IdHelpers"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -class NaturalChunkIdsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("NaturalChunkIdsPlugin", compilation => { - compilation.hooks.chunkIds.tap("NaturalChunkIdsPlugin", chunks => { - const chunkGraph = compilation.chunkGraph; - const compareNatural = compareChunksNatural(chunkGraph); - const chunksInNaturalOrder = Array.from(chunks).sort(compareNatural); - assignAscendingChunkIds(chunksInNaturalOrder, compilation); - }); - }); - } -} - -module.exports = NaturalChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js b/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js deleted file mode 100644 index 962bcff38fd..00000000000 --- a/webpack-lib/lib/ids/NaturalModuleIdsPlugin.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Florent Cailhol @ooflorent -*/ - -"use strict"; - -const { - compareModulesByPreOrderIndexOrIdentifier -} = require("../util/comparators"); -const { - assignAscendingModuleIds, - getUsedModuleIdsAndModules -} = require("./IdHelpers"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -class NaturalModuleIdsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("NaturalModuleIdsPlugin", compilation => { - compilation.hooks.moduleIds.tap("NaturalModuleIdsPlugin", modules => { - const [usedIds, modulesInNaturalOrder] = - getUsedModuleIdsAndModules(compilation); - modulesInNaturalOrder.sort( - compareModulesByPreOrderIndexOrIdentifier(compilation.moduleGraph) - ); - assignAscendingModuleIds(usedIds, modulesInNaturalOrder, compilation); - }); - }); - } -} - -module.exports = NaturalModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js b/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js deleted file mode 100644 index b0acac363ec..00000000000 --- a/webpack-lib/lib/ids/OccurrenceChunkIdsPlugin.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareChunksNatural } = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const { assignAscendingChunkIds } = require("./IdHelpers"); - -/** @typedef {import("../../declarations/plugins/ids/OccurrenceChunkIdsPlugin").OccurrenceChunkIdsPluginOptions} OccurrenceChunkIdsPluginOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/ids/OccurrenceChunkIdsPlugin.check.js"), - () => require("../../schemas/plugins/ids/OccurrenceChunkIdsPlugin.json"), - { - name: "Occurrence Order Chunk Ids Plugin", - baseDataPath: "options" - } -); - -class OccurrenceChunkIdsPlugin { - /** - * @param {OccurrenceChunkIdsPluginOptions=} options options object - */ - constructor(options = {}) { - validate(options); - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const prioritiseInitial = this.options.prioritiseInitial; - compiler.hooks.compilation.tap("OccurrenceChunkIdsPlugin", compilation => { - compilation.hooks.chunkIds.tap("OccurrenceChunkIdsPlugin", chunks => { - const chunkGraph = compilation.chunkGraph; - - /** @type {Map} */ - const occursInInitialChunksMap = new Map(); - - const compareNatural = compareChunksNatural(chunkGraph); - - for (const c of chunks) { - let occurs = 0; - for (const chunkGroup of c.groupsIterable) { - for (const parent of chunkGroup.parentsIterable) { - if (parent.isInitial()) occurs++; - } - } - occursInInitialChunksMap.set(c, occurs); - } - - const chunksInOccurrenceOrder = Array.from(chunks).sort((a, b) => { - if (prioritiseInitial) { - const aEntryOccurs = - /** @type {number} */ - (occursInInitialChunksMap.get(a)); - const bEntryOccurs = - /** @type {number} */ - (occursInInitialChunksMap.get(b)); - if (aEntryOccurs > bEntryOccurs) return -1; - if (aEntryOccurs < bEntryOccurs) return 1; - } - const aOccurs = a.getNumberOfGroups(); - const bOccurs = b.getNumberOfGroups(); - if (aOccurs > bOccurs) return -1; - if (aOccurs < bOccurs) return 1; - return compareNatural(a, b); - }); - assignAscendingChunkIds(chunksInOccurrenceOrder, compilation); - }); - }); - } -} - -module.exports = OccurrenceChunkIdsPlugin; diff --git a/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js b/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js deleted file mode 100644 index 71fb2ce047a..00000000000 --- a/webpack-lib/lib/ids/OccurrenceModuleIdsPlugin.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { - compareModulesByPreOrderIndexOrIdentifier -} = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const { - assignAscendingModuleIds, - getUsedModuleIdsAndModules -} = require("./IdHelpers"); - -/** @typedef {import("../../declarations/plugins/ids/OccurrenceModuleIdsPlugin").OccurrenceModuleIdsPluginOptions} OccurrenceModuleIdsPluginOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/ids/OccurrenceModuleIdsPlugin.check.js"), - () => require("../../schemas/plugins/ids/OccurrenceModuleIdsPlugin.json"), - { - name: "Occurrence Order Module Ids Plugin", - baseDataPath: "options" - } -); - -class OccurrenceModuleIdsPlugin { - /** - * @param {OccurrenceModuleIdsPluginOptions=} options options object - */ - constructor(options = {}) { - validate(options); - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const prioritiseInitial = this.options.prioritiseInitial; - compiler.hooks.compilation.tap("OccurrenceModuleIdsPlugin", compilation => { - const moduleGraph = compilation.moduleGraph; - - compilation.hooks.moduleIds.tap("OccurrenceModuleIdsPlugin", () => { - const chunkGraph = compilation.chunkGraph; - - const [usedIds, modulesInOccurrenceOrder] = - getUsedModuleIdsAndModules(compilation); - - const occursInInitialChunksMap = new Map(); - const occursInAllChunksMap = new Map(); - - const initialChunkChunkMap = new Map(); - const entryCountMap = new Map(); - for (const m of modulesInOccurrenceOrder) { - let initial = 0; - let entry = 0; - for (const c of chunkGraph.getModuleChunksIterable(m)) { - if (c.canBeInitial()) initial++; - if (chunkGraph.isEntryModuleInChunk(m, c)) entry++; - } - initialChunkChunkMap.set(m, initial); - entryCountMap.set(m, entry); - } - - /** - * @param {Module} module module - * @returns {number} count of occurs - */ - const countOccursInEntry = module => { - let sum = 0; - for (const [ - originModule, - connections - ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { - if (!originModule) continue; - if (!connections.some(c => c.isTargetActive(undefined))) continue; - sum += initialChunkChunkMap.get(originModule) || 0; - } - return sum; - }; - - /** - * @param {Module} module module - * @returns {number} count of occurs - */ - const countOccurs = module => { - let sum = 0; - for (const [ - originModule, - connections - ] of moduleGraph.getIncomingConnectionsByOriginModule(module)) { - if (!originModule) continue; - const chunkModules = - chunkGraph.getNumberOfModuleChunks(originModule); - for (const c of connections) { - if (!c.isTargetActive(undefined)) continue; - if (!c.dependency) continue; - const factor = c.dependency.getNumberOfIdOccurrences(); - if (factor === 0) continue; - sum += factor * chunkModules; - } - } - return sum; - }; - - if (prioritiseInitial) { - for (const m of modulesInOccurrenceOrder) { - const result = - countOccursInEntry(m) + - initialChunkChunkMap.get(m) + - entryCountMap.get(m); - occursInInitialChunksMap.set(m, result); - } - } - - for (const m of modulesInOccurrenceOrder) { - const result = - countOccurs(m) + - chunkGraph.getNumberOfModuleChunks(m) + - entryCountMap.get(m); - occursInAllChunksMap.set(m, result); - } - - const naturalCompare = compareModulesByPreOrderIndexOrIdentifier( - compilation.moduleGraph - ); - - modulesInOccurrenceOrder.sort((a, b) => { - if (prioritiseInitial) { - const aEntryOccurs = occursInInitialChunksMap.get(a); - const bEntryOccurs = occursInInitialChunksMap.get(b); - if (aEntryOccurs > bEntryOccurs) return -1; - if (aEntryOccurs < bEntryOccurs) return 1; - } - const aOccurs = occursInAllChunksMap.get(a); - const bOccurs = occursInAllChunksMap.get(b); - if (aOccurs > bOccurs) return -1; - if (aOccurs < bOccurs) return 1; - return naturalCompare(a, b); - }); - - assignAscendingModuleIds( - usedIds, - modulesInOccurrenceOrder, - compilation - ); - }); - }); - } -} - -module.exports = OccurrenceModuleIdsPlugin; diff --git a/webpack-lib/lib/ids/SyncModuleIdsPlugin.js b/webpack-lib/lib/ids/SyncModuleIdsPlugin.js deleted file mode 100644 index aa837624e94..00000000000 --- a/webpack-lib/lib/ids/SyncModuleIdsPlugin.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { WebpackError } = require(".."); -const { getUsedModuleIdsAndModules } = require("./IdHelpers"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ - -const plugin = "SyncModuleIdsPlugin"; - -class SyncModuleIdsPlugin { - /** - * @param {object} options options - * @param {string} options.path path to file - * @param {string=} options.context context for module names - * @param {function(Module): boolean} options.test selector for modules - * @param {"read" | "create" | "merge" | "update"=} options.mode operation mode (defaults to merge) - */ - constructor({ path, context, test, mode }) { - this._path = path; - this._context = context; - this._test = test || (() => true); - const readAndWrite = !mode || mode === "merge" || mode === "update"; - this._read = readAndWrite || mode === "read"; - this._write = readAndWrite || mode === "create"; - this._prune = mode === "update"; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - /** @type {Map} */ - let data; - let dataChanged = false; - if (this._read) { - compiler.hooks.readRecords.tapAsync(plugin, callback => { - const fs = - /** @type {IntermediateFileSystem} */ - (compiler.intermediateFileSystem); - fs.readFile(this._path, (err, buffer) => { - if (err) { - if (err.code !== "ENOENT") { - return callback(err); - } - return callback(); - } - const json = JSON.parse(/** @type {Buffer} */ (buffer).toString()); - data = new Map(); - for (const key of Object.keys(json)) { - data.set(key, json[key]); - } - dataChanged = false; - return callback(); - }); - }); - } - if (this._write) { - compiler.hooks.emitRecords.tapAsync(plugin, callback => { - if (!data || !dataChanged) return callback(); - /** @type {{[key: string]: string | number}} */ - const json = {}; - const sorted = Array.from(data).sort(([a], [b]) => (a < b ? -1 : 1)); - for (const [key, value] of sorted) { - json[key] = value; - } - const fs = - /** @type {IntermediateFileSystem} */ - (compiler.intermediateFileSystem); - fs.writeFile(this._path, JSON.stringify(json), callback); - }); - } - compiler.hooks.thisCompilation.tap(plugin, compilation => { - const associatedObjectForCache = compiler.root; - const context = this._context || compiler.context; - if (this._read) { - compilation.hooks.reviveModules.tap(plugin, (_1, _2) => { - if (!data) return; - const { chunkGraph } = compilation; - const [usedIds, modules] = getUsedModuleIdsAndModules( - compilation, - this._test - ); - for (const module of modules) { - const name = module.libIdent({ - context, - associatedObjectForCache - }); - if (!name) continue; - const id = data.get(name); - const idAsString = `${id}`; - if (usedIds.has(idAsString)) { - const err = new WebpackError( - `SyncModuleIdsPlugin: Unable to restore id '${id}' from '${this._path}' as it's already used.` - ); - err.module = module; - compilation.errors.push(err); - } - chunkGraph.setModuleId(module, /** @type {string | number} */ (id)); - usedIds.add(idAsString); - } - }); - } - if (this._write) { - compilation.hooks.recordModules.tap(plugin, modules => { - const { chunkGraph } = compilation; - let oldData = data; - if (!oldData) { - oldData = data = new Map(); - } else if (this._prune) { - data = new Map(); - } - for (const module of modules) { - if (this._test(module)) { - const name = module.libIdent({ - context, - associatedObjectForCache - }); - if (!name) continue; - const id = chunkGraph.getModuleId(module); - if (id === null) continue; - const oldId = oldData.get(name); - if (oldId !== id) { - dataChanged = true; - } else if (data === oldData) { - continue; - } - data.set(name, id); - } - } - if (data.size !== oldData.size) dataChanged = true; - }); - } - }); - } -} - -module.exports = SyncModuleIdsPlugin; diff --git a/webpack-lib/lib/index.js b/webpack-lib/lib/index.js deleted file mode 100644 index 1e6b8bfd4c7..00000000000 --- a/webpack-lib/lib/index.js +++ /dev/null @@ -1,641 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const memoize = require("./util/memoize"); - -/** @typedef {import("../declarations/WebpackOptions").Entry} Entry */ -/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} EntryNormalized */ -/** @typedef {import("../declarations/WebpackOptions").EntryObject} EntryObject */ -/** @typedef {import("../declarations/WebpackOptions").ExternalItemFunctionData} ExternalItemFunctionData */ -/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectKnown} ExternalItemObjectKnown */ -/** @typedef {import("../declarations/WebpackOptions").ExternalItemObjectUnknown} ExternalItemObjectUnknown */ -/** @typedef {import("../declarations/WebpackOptions").ExternalItemValue} ExternalItemValue */ -/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */ -/** @typedef {import("../declarations/WebpackOptions").FileCacheOptions} FileCacheOptions */ -/** @typedef {import("../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../declarations/WebpackOptions").MemoryCacheOptions} MemoryCacheOptions */ -/** @typedef {import("../declarations/WebpackOptions").ModuleOptions} ModuleOptions */ -/** @typedef {import("../declarations/WebpackOptions").ResolveOptions} ResolveOptions */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetCondition} RuleSetCondition */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetConditionAbsolute} RuleSetConditionAbsolute */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetUse} RuleSetUse */ -/** @typedef {import("../declarations/WebpackOptions").RuleSetUseItem} RuleSetUseItem */ -/** @typedef {import("../declarations/WebpackOptions").StatsOptions} StatsOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} Configuration */ -/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptionsNormalized */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */ -/** @typedef {import("./ChunkGroup")} ChunkGroup */ -/** @typedef {import("./Compilation").Asset} Asset */ -/** @typedef {import("./Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("./Compilation").EntryOptions} EntryOptions */ -/** @typedef {import("./Compilation").PathData} PathData */ -/** @typedef {import("./Compiler").AssetEmittedInfo} AssetEmittedInfo */ -/** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ -/** @typedef {import("./MultiStats")} MultiStats */ -/** @typedef {import("./NormalModuleFactory").ResolveData} ResolveData */ -/** @typedef {import("./Parser").ParserState} ParserState */ -/** @typedef {import("./ResolverFactory").ResolvePluginInstance} ResolvePluginInstance */ -/** @typedef {import("./ResolverFactory").Resolver} Resolver */ -/** @typedef {import("./Watching")} Watching */ -/** @typedef {import("./cli").Argument} Argument */ -/** @typedef {import("./cli").Problem} Problem */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsChunkOrigin} StatsChunkOrigin */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsError} StatsError */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsLogging} StatsLogging */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsLoggingEntry} StatsLoggingEntry */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModule} StatsModule */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */ -/** @typedef {import("./stats/DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */ -/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */ - -/** - * @template {Function} T - * @param {function(): T} factory factory function - * @returns {T} function - */ -const lazyFunction = factory => { - const fac = memoize(factory); - const f = /** @type {any} */ ( - /** - * @param {...any} args args - * @returns {T} result - */ - (...args) => fac()(...args) - ); - return /** @type {T} */ (f); -}; - -/** - * @template A - * @template B - * @param {A} obj input a - * @param {B} exports input b - * @returns {A & B} merged - */ -const mergeExports = (obj, exports) => { - const descriptors = Object.getOwnPropertyDescriptors(exports); - for (const name of Object.keys(descriptors)) { - const descriptor = descriptors[name]; - if (descriptor.get) { - const fn = descriptor.get; - Object.defineProperty(obj, name, { - configurable: false, - enumerable: true, - get: memoize(fn) - }); - } else if (typeof descriptor.value === "object") { - Object.defineProperty(obj, name, { - configurable: false, - enumerable: true, - writable: false, - value: mergeExports({}, descriptor.value) - }); - } else { - throw new Error( - "Exposed values must be either a getter or an nested object" - ); - } - } - return /** @type {A & B} */ (Object.freeze(obj)); -}; - -const fn = lazyFunction(() => require("./webpack")); -module.exports = mergeExports(fn, { - get webpack() { - return require("./webpack"); - }, - /** - * @returns {function(Configuration | Configuration[]): void} validate fn - */ - get validate() { - const webpackOptionsSchemaCheck = require("../schemas/WebpackOptions.check.js"); - const getRealValidate = memoize( - /** - * @returns {function(Configuration | Configuration[]): void} validate fn - */ - () => { - const validateSchema = require("./validateSchema"); - const webpackOptionsSchema = require("../schemas/WebpackOptions.json"); - return options => validateSchema(webpackOptionsSchema, options); - } - ); - return options => { - if (!webpackOptionsSchemaCheck(/** @type {TODO} */ (options))) - getRealValidate()(options); - }; - }, - get validateSchema() { - const validateSchema = require("./validateSchema"); - return validateSchema; - }, - get version() { - return /** @type {string} */ (require("../package.json").version); - }, - - get cli() { - return require("./cli"); - }, - get AutomaticPrefetchPlugin() { - return require("./AutomaticPrefetchPlugin"); - }, - get AsyncDependenciesBlock() { - return require("./AsyncDependenciesBlock"); - }, - get BannerPlugin() { - return require("./BannerPlugin"); - }, - get Cache() { - return require("./Cache"); - }, - get Chunk() { - return require("./Chunk"); - }, - get ChunkGraph() { - return require("./ChunkGraph"); - }, - get CleanPlugin() { - return require("./CleanPlugin"); - }, - get Compilation() { - return require("./Compilation"); - }, - get Compiler() { - return require("./Compiler"); - }, - get ConcatenationScope() { - return require("./ConcatenationScope"); - }, - get ContextExclusionPlugin() { - return require("./ContextExclusionPlugin"); - }, - get ContextReplacementPlugin() { - return require("./ContextReplacementPlugin"); - }, - get DefinePlugin() { - return require("./DefinePlugin"); - }, - get DelegatedPlugin() { - return require("./DelegatedPlugin"); - }, - get Dependency() { - return require("./Dependency"); - }, - get DllPlugin() { - return require("./DllPlugin"); - }, - get DllReferencePlugin() { - return require("./DllReferencePlugin"); - }, - get DynamicEntryPlugin() { - return require("./DynamicEntryPlugin"); - }, - get EntryOptionPlugin() { - return require("./EntryOptionPlugin"); - }, - get EntryPlugin() { - return require("./EntryPlugin"); - }, - get EnvironmentPlugin() { - return require("./EnvironmentPlugin"); - }, - get EvalDevToolModulePlugin() { - return require("./EvalDevToolModulePlugin"); - }, - get EvalSourceMapDevToolPlugin() { - return require("./EvalSourceMapDevToolPlugin"); - }, - get ExternalModule() { - return require("./ExternalModule"); - }, - get ExternalsPlugin() { - return require("./ExternalsPlugin"); - }, - get Generator() { - return require("./Generator"); - }, - get HotUpdateChunk() { - return require("./HotUpdateChunk"); - }, - get HotModuleReplacementPlugin() { - return require("./HotModuleReplacementPlugin"); - }, - get InitFragment() { - return require("./InitFragment"); - }, - get IgnorePlugin() { - return require("./IgnorePlugin"); - }, - get JavascriptModulesPlugin() { - return util.deprecate( - () => require("./javascript/JavascriptModulesPlugin"), - "webpack.JavascriptModulesPlugin has moved to webpack.javascript.JavascriptModulesPlugin", - "DEP_WEBPACK_JAVASCRIPT_MODULES_PLUGIN" - )(); - }, - get LibManifestPlugin() { - return require("./LibManifestPlugin"); - }, - get LibraryTemplatePlugin() { - return util.deprecate( - () => require("./LibraryTemplatePlugin"), - "webpack.LibraryTemplatePlugin is deprecated and has been replaced by compilation.outputOptions.library or compilation.addEntry + passing a library option", - "DEP_WEBPACK_LIBRARY_TEMPLATE_PLUGIN" - )(); - }, - get LoaderOptionsPlugin() { - return require("./LoaderOptionsPlugin"); - }, - get LoaderTargetPlugin() { - return require("./LoaderTargetPlugin"); - }, - get Module() { - return require("./Module"); - }, - get ModuleFilenameHelpers() { - return require("./ModuleFilenameHelpers"); - }, - get ModuleGraph() { - return require("./ModuleGraph"); - }, - get ModuleGraphConnection() { - return require("./ModuleGraphConnection"); - }, - get NoEmitOnErrorsPlugin() { - return require("./NoEmitOnErrorsPlugin"); - }, - get NormalModule() { - return require("./NormalModule"); - }, - get NormalModuleReplacementPlugin() { - return require("./NormalModuleReplacementPlugin"); - }, - get MultiCompiler() { - return require("./MultiCompiler"); - }, - get OptimizationStages() { - return require("./OptimizationStages"); - }, - get Parser() { - return require("./Parser"); - }, - get PlatformPlugin() { - return require("./PlatformPlugin"); - }, - get PrefetchPlugin() { - return require("./PrefetchPlugin"); - }, - get ProgressPlugin() { - return require("./ProgressPlugin"); - }, - get ProvidePlugin() { - return require("./ProvidePlugin"); - }, - get RuntimeGlobals() { - return require("./RuntimeGlobals"); - }, - get RuntimeModule() { - return require("./RuntimeModule"); - }, - get SingleEntryPlugin() { - return util.deprecate( - () => require("./EntryPlugin"), - "SingleEntryPlugin was renamed to EntryPlugin", - "DEP_WEBPACK_SINGLE_ENTRY_PLUGIN" - )(); - }, - get SourceMapDevToolPlugin() { - return require("./SourceMapDevToolPlugin"); - }, - get Stats() { - return require("./Stats"); - }, - get Template() { - return require("./Template"); - }, - get UsageState() { - return require("./ExportsInfo").UsageState; - }, - get WatchIgnorePlugin() { - return require("./WatchIgnorePlugin"); - }, - get WebpackError() { - return require("./WebpackError"); - }, - get WebpackOptionsApply() { - return require("./WebpackOptionsApply"); - }, - get WebpackOptionsDefaulter() { - return util.deprecate( - () => require("./WebpackOptionsDefaulter"), - "webpack.WebpackOptionsDefaulter is deprecated and has been replaced by webpack.config.getNormalizedWebpackOptions and webpack.config.applyWebpackOptionsDefaults", - "DEP_WEBPACK_OPTIONS_DEFAULTER" - )(); - }, - // TODO webpack 6 deprecate - get WebpackOptionsValidationError() { - return require("schema-utils").ValidationError; - }, - get ValidationError() { - return require("schema-utils").ValidationError; - }, - - cache: { - get MemoryCachePlugin() { - return require("./cache/MemoryCachePlugin"); - } - }, - - config: { - get getNormalizedWebpackOptions() { - return require("./config/normalization").getNormalizedWebpackOptions; - }, - get applyWebpackOptionsDefaults() { - return require("./config/defaults").applyWebpackOptionsDefaults; - } - }, - - dependencies: { - get ModuleDependency() { - return require("./dependencies/ModuleDependency"); - }, - get HarmonyImportDependency() { - return require("./dependencies/HarmonyImportDependency"); - }, - get ConstDependency() { - return require("./dependencies/ConstDependency"); - }, - get NullDependency() { - return require("./dependencies/NullDependency"); - } - }, - - ids: { - get ChunkModuleIdRangePlugin() { - return require("./ids/ChunkModuleIdRangePlugin"); - }, - get NaturalModuleIdsPlugin() { - return require("./ids/NaturalModuleIdsPlugin"); - }, - get OccurrenceModuleIdsPlugin() { - return require("./ids/OccurrenceModuleIdsPlugin"); - }, - get NamedModuleIdsPlugin() { - return require("./ids/NamedModuleIdsPlugin"); - }, - get DeterministicChunkIdsPlugin() { - return require("./ids/DeterministicChunkIdsPlugin"); - }, - get DeterministicModuleIdsPlugin() { - return require("./ids/DeterministicModuleIdsPlugin"); - }, - get NamedChunkIdsPlugin() { - return require("./ids/NamedChunkIdsPlugin"); - }, - get OccurrenceChunkIdsPlugin() { - return require("./ids/OccurrenceChunkIdsPlugin"); - }, - get HashedModuleIdsPlugin() { - return require("./ids/HashedModuleIdsPlugin"); - } - }, - - javascript: { - get EnableChunkLoadingPlugin() { - return require("./javascript/EnableChunkLoadingPlugin"); - }, - get JavascriptModulesPlugin() { - return require("./javascript/JavascriptModulesPlugin"); - }, - get JavascriptParser() { - return require("./javascript/JavascriptParser"); - } - }, - - optimize: { - get AggressiveMergingPlugin() { - return require("./optimize/AggressiveMergingPlugin"); - }, - get AggressiveSplittingPlugin() { - return util.deprecate( - () => require("./optimize/AggressiveSplittingPlugin"), - "AggressiveSplittingPlugin is deprecated in favor of SplitChunksPlugin", - "DEP_WEBPACK_AGGRESSIVE_SPLITTING_PLUGIN" - )(); - }, - get InnerGraph() { - return require("./optimize/InnerGraph"); - }, - get LimitChunkCountPlugin() { - return require("./optimize/LimitChunkCountPlugin"); - }, - get MergeDuplicateChunksPlugin() { - return require("./optimize/MergeDuplicateChunksPlugin.js"); - }, - get MinChunkSizePlugin() { - return require("./optimize/MinChunkSizePlugin"); - }, - get ModuleConcatenationPlugin() { - return require("./optimize/ModuleConcatenationPlugin"); - }, - get RealContentHashPlugin() { - return require("./optimize/RealContentHashPlugin"); - }, - get RuntimeChunkPlugin() { - return require("./optimize/RuntimeChunkPlugin"); - }, - get SideEffectsFlagPlugin() { - return require("./optimize/SideEffectsFlagPlugin"); - }, - get SplitChunksPlugin() { - return require("./optimize/SplitChunksPlugin"); - } - }, - - runtime: { - get GetChunkFilenameRuntimeModule() { - return require("./runtime/GetChunkFilenameRuntimeModule"); - }, - get LoadScriptRuntimeModule() { - return require("./runtime/LoadScriptRuntimeModule"); - } - }, - - prefetch: { - get ChunkPrefetchPreloadPlugin() { - return require("./prefetch/ChunkPrefetchPreloadPlugin"); - } - }, - - web: { - get FetchCompileWasmPlugin() { - return require("./web/FetchCompileWasmPlugin"); - }, - get FetchCompileAsyncWasmPlugin() { - return require("./web/FetchCompileAsyncWasmPlugin"); - }, - get JsonpChunkLoadingRuntimeModule() { - return require("./web/JsonpChunkLoadingRuntimeModule"); - }, - get JsonpTemplatePlugin() { - return require("./web/JsonpTemplatePlugin"); - }, - get CssLoadingRuntimeModule() { - return require("./css/CssLoadingRuntimeModule"); - } - }, - - esm: { - get ModuleChunkLoadingRuntimeModule() { - return require("./esm/ModuleChunkLoadingRuntimeModule"); - } - }, - - webworker: { - get WebWorkerTemplatePlugin() { - return require("./webworker/WebWorkerTemplatePlugin"); - } - }, - - node: { - get NodeEnvironmentPlugin() { - return require("./node/NodeEnvironmentPlugin"); - }, - get NodeSourcePlugin() { - return require("./node/NodeSourcePlugin"); - }, - get NodeTargetPlugin() { - return require("./node/NodeTargetPlugin"); - }, - get NodeTemplatePlugin() { - return require("./node/NodeTemplatePlugin"); - }, - get ReadFileCompileWasmPlugin() { - return require("./node/ReadFileCompileWasmPlugin"); - }, - get ReadFileCompileAsyncWasmPlugin() { - return require("./node/ReadFileCompileAsyncWasmPlugin"); - } - }, - - electron: { - get ElectronTargetPlugin() { - return require("./electron/ElectronTargetPlugin"); - } - }, - - wasm: { - get AsyncWebAssemblyModulesPlugin() { - return require("./wasm-async/AsyncWebAssemblyModulesPlugin"); - }, - get EnableWasmLoadingPlugin() { - return require("./wasm/EnableWasmLoadingPlugin"); - } - }, - - css: { - get CssModulesPlugin() { - return require("./css/CssModulesPlugin"); - } - }, - - library: { - get AbstractLibraryPlugin() { - return require("./library/AbstractLibraryPlugin"); - }, - get EnableLibraryPlugin() { - return require("./library/EnableLibraryPlugin"); - } - }, - - container: { - get ContainerPlugin() { - return require("./container/ContainerPlugin"); - }, - get ContainerReferencePlugin() { - return require("./container/ContainerReferencePlugin"); - }, - get ModuleFederationPlugin() { - return require("./container/ModuleFederationPlugin"); - }, - get scope() { - return require("./container/options").scope; - } - }, - - sharing: { - get ConsumeSharedPlugin() { - return require("./sharing/ConsumeSharedPlugin"); - }, - get ProvideSharedPlugin() { - return require("./sharing/ProvideSharedPlugin"); - }, - get SharePlugin() { - return require("./sharing/SharePlugin"); - }, - get scope() { - return require("./container/options").scope; - } - }, - - debug: { - get ProfilingPlugin() { - return require("./debug/ProfilingPlugin"); - } - }, - - util: { - get createHash() { - return require("./util/createHash"); - }, - get comparators() { - return require("./util/comparators"); - }, - get runtime() { - return require("./util/runtime"); - }, - get serialization() { - return require("./util/serialization"); - }, - get cleverMerge() { - return require("./util/cleverMerge").cachedCleverMerge; - }, - get LazySet() { - return require("./util/LazySet"); - }, - get compileBooleanMatcher() { - return require("./util/compileBooleanMatcher"); - } - }, - - get sources() { - return require("webpack-sources"); - }, - - experiments: { - schemes: { - get HttpUriPlugin() { - return require("./schemes/HttpUriPlugin"); - } - }, - ids: { - get SyncModuleIdsPlugin() { - return require("./ids/SyncModuleIdsPlugin"); - } - } - } -}); diff --git a/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js b/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js deleted file mode 100644 index 1bb04abaffb..00000000000 --- a/webpack-lib/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +++ /dev/null @@ -1,156 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource, PrefixSource, RawSource } = require("webpack-sources"); -const { RuntimeGlobals } = require(".."); -const HotUpdateChunk = require("../HotUpdateChunk"); -const Template = require("../Template"); -const { getCompilationHooks } = require("./JavascriptModulesPlugin"); -const { - generateEntryStartup, - updateHashForEntryStartup -} = require("./StartupHelpers"); - -/** @typedef {import("../Compiler")} Compiler */ - -class ArrayPushCallbackChunkFormatPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "ArrayPushCallbackChunkFormatPlugin", - compilation => { - compilation.hooks.additionalChunkRuntimeRequirements.tap( - "ArrayPushCallbackChunkFormatPlugin", - (chunk, set, { chunkGraph }) => { - if (chunk.hasRuntime()) return; - if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { - set.add(RuntimeGlobals.onChunksLoaded); - set.add(RuntimeGlobals.exports); - set.add(RuntimeGlobals.require); - } - set.add(RuntimeGlobals.chunkCallback); - } - ); - const hooks = getCompilationHooks(compilation); - hooks.renderChunk.tap( - "ArrayPushCallbackChunkFormatPlugin", - (modules, renderContext) => { - const { chunk, chunkGraph, runtimeTemplate } = renderContext; - const hotUpdateChunk = - chunk instanceof HotUpdateChunk ? chunk : null; - const globalObject = runtimeTemplate.globalObject; - const source = new ConcatSource(); - const runtimeModules = - chunkGraph.getChunkRuntimeModulesInOrder(chunk); - if (hotUpdateChunk) { - const hotUpdateGlobal = - runtimeTemplate.outputOptions.hotUpdateGlobal; - source.add( - `${globalObject}[${JSON.stringify(hotUpdateGlobal)}](` - ); - source.add(`${JSON.stringify(chunk.id)},`); - source.add(modules); - if (runtimeModules.length > 0) { - source.add(",\n"); - const runtimePart = Template.renderChunkRuntimeModules( - runtimeModules, - renderContext - ); - source.add(runtimePart); - } - source.add(")"); - } else { - const chunkLoadingGlobal = - runtimeTemplate.outputOptions.chunkLoadingGlobal; - source.add( - `(${globalObject}[${JSON.stringify( - chunkLoadingGlobal - )}] = ${globalObject}[${JSON.stringify( - chunkLoadingGlobal - )}] || []).push([` - ); - source.add(`${JSON.stringify(chunk.ids)},`); - source.add(modules); - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - if (runtimeModules.length > 0 || entries.length > 0) { - const runtime = new ConcatSource( - `${ - runtimeTemplate.supportsArrowFunction() - ? `${RuntimeGlobals.require} =>` - : `function(${RuntimeGlobals.require})` - } { // webpackRuntimeModules\n` - ); - if (runtimeModules.length > 0) { - runtime.add( - Template.renderRuntimeModules(runtimeModules, { - ...renderContext, - codeGenerationResults: compilation.codeGenerationResults - }) - ); - } - if (entries.length > 0) { - const startupSource = new RawSource( - generateEntryStartup( - chunkGraph, - runtimeTemplate, - entries, - chunk, - true - ) - ); - runtime.add( - hooks.renderStartup.call( - startupSource, - entries[entries.length - 1][0], - { - ...renderContext, - inlined: false - } - ) - ); - if ( - chunkGraph - .getChunkRuntimeRequirements(chunk) - .has(RuntimeGlobals.returnExportsFromRuntime) - ) { - runtime.add(`return ${RuntimeGlobals.exports};\n`); - } - } - runtime.add("}\n"); - source.add(",\n"); - source.add(new PrefixSource("/******/ ", runtime)); - } - source.add("])"); - } - return source; - } - ); - hooks.chunkHash.tap( - "ArrayPushCallbackChunkFormatPlugin", - (chunk, hash, { chunkGraph, runtimeTemplate }) => { - if (chunk.hasRuntime()) return; - hash.update( - `ArrayPushCallbackChunkFormatPlugin1${runtimeTemplate.outputOptions.chunkLoadingGlobal}${runtimeTemplate.outputOptions.hotUpdateGlobal}${runtimeTemplate.globalObject}` - ); - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - updateHashForEntryStartup(hash, chunkGraph, entries, chunk); - } - ); - } - ); - } -} - -module.exports = ArrayPushCallbackChunkFormatPlugin; diff --git a/webpack-lib/lib/javascript/BasicEvaluatedExpression.js b/webpack-lib/lib/javascript/BasicEvaluatedExpression.js deleted file mode 100644 index 05dd14cd194..00000000000 --- a/webpack-lib/lib/javascript/BasicEvaluatedExpression.js +++ /dev/null @@ -1,594 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("estree").Node} Node */ -/** @typedef {import("./JavascriptParser").Range} Range */ -/** @typedef {import("./JavascriptParser").VariableInfoInterface} VariableInfoInterface */ - -const TypeUnknown = 0; -const TypeUndefined = 1; -const TypeNull = 2; -const TypeString = 3; -const TypeNumber = 4; -const TypeBoolean = 5; -const TypeRegExp = 6; -const TypeConditional = 7; -const TypeArray = 8; -const TypeConstArray = 9; -const TypeIdentifier = 10; -const TypeWrapped = 11; -const TypeTemplateString = 12; -const TypeBigInt = 13; - -class BasicEvaluatedExpression { - constructor() { - this.type = TypeUnknown; - /** @type {Range | undefined} */ - this.range = undefined; - /** @type {boolean} */ - this.falsy = false; - /** @type {boolean} */ - this.truthy = false; - /** @type {boolean | undefined} */ - this.nullish = undefined; - /** @type {boolean} */ - this.sideEffects = true; - /** @type {boolean | undefined} */ - this.bool = undefined; - /** @type {number | undefined} */ - this.number = undefined; - /** @type {bigint | undefined} */ - this.bigint = undefined; - /** @type {RegExp | undefined} */ - this.regExp = undefined; - /** @type {string | undefined} */ - this.string = undefined; - /** @type {BasicEvaluatedExpression[] | undefined} */ - this.quasis = undefined; - /** @type {BasicEvaluatedExpression[] | undefined} */ - this.parts = undefined; - /** @type {any[] | undefined} */ - this.array = undefined; - /** @type {BasicEvaluatedExpression[] | undefined} */ - this.items = undefined; - /** @type {BasicEvaluatedExpression[] | undefined} */ - this.options = undefined; - /** @type {BasicEvaluatedExpression | undefined | null} */ - this.prefix = undefined; - /** @type {BasicEvaluatedExpression | undefined | null} */ - this.postfix = undefined; - /** @type {BasicEvaluatedExpression[] | undefined} */ - this.wrappedInnerExpressions = undefined; - /** @type {string | VariableInfoInterface | undefined} */ - this.identifier = undefined; - /** @type {string | VariableInfoInterface | undefined} */ - this.rootInfo = undefined; - /** @type {(() => string[]) | undefined} */ - this.getMembers = undefined; - /** @type {(() => boolean[]) | undefined} */ - this.getMembersOptionals = undefined; - /** @type {(() => Range[]) | undefined} */ - this.getMemberRanges = undefined; - /** @type {Node | undefined} */ - this.expression = undefined; - } - - isUnknown() { - return this.type === TypeUnknown; - } - - isNull() { - return this.type === TypeNull; - } - - isUndefined() { - return this.type === TypeUndefined; - } - - isString() { - return this.type === TypeString; - } - - isNumber() { - return this.type === TypeNumber; - } - - isBigInt() { - return this.type === TypeBigInt; - } - - 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; - } - - /** - * Is expression a primitive or an object type value? - * @returns {boolean | undefined} true: primitive type, false: object type, undefined: unknown/runtime-defined - */ - isPrimitiveType() { - switch (this.type) { - case TypeUndefined: - case TypeNull: - case TypeString: - case TypeNumber: - case TypeBoolean: - case TypeBigInt: - case TypeWrapped: - case TypeTemplateString: - return true; - case TypeRegExp: - case TypeArray: - case TypeConstArray: - return false; - default: - return undefined; - } - } - - /** - * Is expression a runtime or compile-time value? - * @returns {boolean} true: compile time value, false: runtime value - */ - isCompileTimeValue() { - switch (this.type) { - case TypeUndefined: - case TypeNull: - case TypeString: - case TypeNumber: - case TypeBoolean: - case TypeRegExp: - case TypeConstArray: - case TypeBigInt: - return true; - default: - return false; - } - } - - /** - * Gets the compile-time value of the expression - * @returns {any} the javascript value - */ - asCompileTimeValue() { - switch (this.type) { - case TypeUndefined: - return; - case TypeNull: - return null; - case TypeString: - return this.string; - case TypeNumber: - return this.number; - case TypeBoolean: - return this.bool; - case TypeRegExp: - return this.regExp; - case TypeConstArray: - return this.array; - case TypeBigInt: - return this.bigint; - default: - throw new Error( - "asCompileTimeValue must only be called for compile-time values" - ); - } - } - - isTruthy() { - return this.truthy; - } - - isFalsy() { - return this.falsy; - } - - isNullish() { - return this.nullish; - } - - /** - * Can this expression have side effects? - * @returns {boolean} false: never has side effects - */ - couldHaveSideEffects() { - return this.sideEffects; - } - - /** - * Creates a boolean representation of this evaluated expression. - * @returns {boolean | undefined} true: truthy, false: falsy, undefined: unknown - */ - asBool() { - if (this.truthy) return true; - if (this.falsy || this.nullish) return false; - if (this.isBoolean()) return this.bool; - if (this.isNull()) return false; - if (this.isUndefined()) return false; - if (this.isString()) return this.string !== ""; - if (this.isNumber()) return this.number !== 0; - if (this.isBigInt()) return this.bigint !== BigInt(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()) { - const str = this.asString(); - if (typeof str === "string") return str !== ""; - } - } - - /** - * Creates a nullish coalescing representation of this evaluated expression. - * @returns {boolean | undefined} true: nullish, false: not nullish, undefined: unknown - */ - asNullish() { - const nullish = this.isNullish(); - - if (nullish === true || this.isNull() || this.isUndefined()) return true; - - if (nullish === false) return false; - if (this.isTruthy()) return false; - if (this.isBoolean()) return false; - if (this.isString()) return false; - if (this.isNumber()) return false; - if (this.isBigInt()) return false; - if (this.isRegExp()) return false; - if (this.isArray()) return false; - if (this.isConstArray()) return false; - if (this.isTemplateString()) return false; - if (this.isRegExp()) return false; - } - - /** - * Creates a string representation of this evaluated expression. - * @returns {string | undefined} the string representation or undefined if not possible - */ - asString() { - if (this.isBoolean()) return `${this.bool}`; - if (this.isNull()) return "null"; - if (this.isUndefined()) return "undefined"; - if (this.isString()) return this.string; - if (this.isNumber()) return `${this.number}`; - if (this.isBigInt()) return `${this.bigint}`; - if (this.isRegExp()) return `${this.regExp}`; - if (this.isArray()) { - const array = []; - for (const item of /** @type {BasicEvaluatedExpression[]} */ ( - this.items - )) { - const itemStr = item.asString(); - if (itemStr === undefined) return; - array.push(itemStr); - } - return `${array}`; - } - if (this.isConstArray()) return `${this.array}`; - if (this.isTemplateString()) { - let str = ""; - for (const part of /** @type {BasicEvaluatedExpression[]} */ ( - this.parts - )) { - const partStr = part.asString(); - if (partStr === undefined) return; - str += partStr; - } - return str; - } - } - - /** - * @param {string} string value - * @returns {BasicEvaluatedExpression} basic evaluated expression - */ - setString(string) { - this.type = TypeString; - this.string = string; - this.sideEffects = false; - return this; - } - - setUndefined() { - this.type = TypeUndefined; - this.sideEffects = false; - return this; - } - - setNull() { - this.type = TypeNull; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a number - * @param {number} number number to set - * @returns {this} this - */ - setNumber(number) { - this.type = TypeNumber; - this.number = number; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a BigInt - * @param {bigint} bigint bigint to set - * @returns {this} this - */ - setBigInt(bigint) { - this.type = TypeBigInt; - this.bigint = bigint; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a boolean - * @param {boolean} bool boolean to set - * @returns {this} this - */ - setBoolean(bool) { - this.type = TypeBoolean; - this.bool = bool; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a regular expression - * @param {RegExp} regExp regular expression to set - * @returns {this} this - */ - setRegExp(regExp) { - this.type = TypeRegExp; - this.regExp = regExp; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a particular identifier and its members. - * @param {string | VariableInfoInterface} identifier identifier to set - * @param {string | VariableInfoInterface} rootInfo root info - * @param {() => string[]} getMembers members - * @param {() => boolean[]=} getMembersOptionals optional members - * @param {() => Range[]=} getMemberRanges ranges of progressively increasing sub-expressions - * @returns {this} this - */ - setIdentifier( - identifier, - rootInfo, - getMembers, - getMembersOptionals, - getMemberRanges - ) { - this.type = TypeIdentifier; - this.identifier = identifier; - this.rootInfo = rootInfo; - this.getMembers = getMembers; - this.getMembersOptionals = getMembersOptionals; - this.getMemberRanges = getMemberRanges; - this.sideEffects = true; - return this; - } - - /** - * Wraps an array of expressions with a prefix and postfix expression. - * @param {BasicEvaluatedExpression | null | undefined} prefix Expression to be added before the innerExpressions - * @param {BasicEvaluatedExpression | null | undefined} postfix Expression to be added after the innerExpressions - * @param {BasicEvaluatedExpression[] | undefined} innerExpressions Expressions to be wrapped - * @returns {this} this - */ - setWrapped(prefix, postfix, innerExpressions) { - this.type = TypeWrapped; - this.prefix = prefix; - this.postfix = postfix; - this.wrappedInnerExpressions = innerExpressions; - this.sideEffects = true; - return this; - } - - /** - * Stores the options of a conditional expression. - * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be set - * @returns {this} this - */ - setOptions(options) { - this.type = TypeConditional; - this.options = options; - this.sideEffects = true; - return this; - } - - /** - * Adds options to a conditional expression. - * @param {BasicEvaluatedExpression[]} options optional (consequent/alternate) expressions to be added - * @returns {this} this - */ - addOptions(options) { - if (!this.options) { - this.type = TypeConditional; - this.options = []; - this.sideEffects = true; - } - for (const item of options) { - this.options.push(item); - } - return this; - } - - /** - * Set's the value of this expression to an array of expressions. - * @param {BasicEvaluatedExpression[]} items expressions to set - * @returns {this} this - */ - setItems(items) { - this.type = TypeArray; - this.items = items; - this.sideEffects = items.some(i => i.couldHaveSideEffects()); - return this; - } - - /** - * Set's the value of this expression to an array of strings. - * @param {string[]} array array to set - * @returns {this} this - */ - setArray(array) { - this.type = TypeConstArray; - this.array = array; - this.sideEffects = false; - return this; - } - - /** - * Set's the value of this expression to a processed/unprocessed template string. Used - * for evaluating TemplateLiteral expressions in the JavaScript Parser. - * @param {BasicEvaluatedExpression[]} quasis template string quasis - * @param {BasicEvaluatedExpression[]} parts template string parts - * @param {"cooked" | "raw"} kind template string kind - * @returns {this} this - */ - setTemplateString(quasis, parts, kind) { - this.type = TypeTemplateString; - this.quasis = quasis; - this.parts = parts; - this.templateStringKind = kind; - this.sideEffects = parts.some(p => p.sideEffects); - return this; - } - - setTruthy() { - this.falsy = false; - this.truthy = true; - this.nullish = false; - return this; - } - - setFalsy() { - this.falsy = true; - this.truthy = false; - return this; - } - - /** - * Set's the value of the expression to nullish. - * @param {boolean} value true, if the expression is nullish - * @returns {this} this - */ - setNullish(value) { - this.nullish = value; - - if (value) return this.setFalsy(); - - return this; - } - - /** - * Set's the range for the expression. - * @param {[number, number]} range range to set - * @returns {this} this - */ - setRange(range) { - this.range = range; - return this; - } - - /** - * Set whether or not the expression has side effects. - * @param {boolean} sideEffects true, if the expression has side effects - * @returns {this} this - */ - setSideEffects(sideEffects = true) { - this.sideEffects = sideEffects; - return this; - } - - /** - * Set the expression node for the expression. - * @param {Node | undefined} expression expression - * @returns {this} this - */ - setExpression(expression) { - this.expression = expression; - return this; - } -} - -/** - * @param {string} flags regexp flags - * @returns {boolean} is valid flags - */ -BasicEvaluatedExpression.isValidRegExpFlags = flags => { - const len = flags.length; - - if (len === 0) return true; - if (len > 4) return false; - - // cspell:word gimy - let remaining = 0b0000; // bit per RegExp flag: gimy - - for (let i = 0; i < len; i++) - switch (flags.charCodeAt(i)) { - case 103 /* g */: - if (remaining & 0b1000) return false; - remaining |= 0b1000; - break; - case 105 /* i */: - if (remaining & 0b0100) return false; - remaining |= 0b0100; - break; - case 109 /* m */: - if (remaining & 0b0010) return false; - remaining |= 0b0010; - break; - case 121 /* y */: - if (remaining & 0b0001) return false; - remaining |= 0b0001; - break; - default: - return false; - } - - return true; -}; - -module.exports = BasicEvaluatedExpression; diff --git a/webpack-lib/lib/javascript/ChunkHelpers.js b/webpack-lib/lib/javascript/ChunkHelpers.js deleted file mode 100644 index f2e8a12a996..00000000000 --- a/webpack-lib/lib/javascript/ChunkHelpers.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Entrypoint = require("../Entrypoint"); - -/** @typedef {import("../Chunk")} Chunk */ - -/** - * @param {Entrypoint} entrypoint a chunk group - * @param {(Chunk | null)=} excludedChunk1 current chunk which is excluded - * @param {(Chunk | null)=} excludedChunk2 runtime chunk which is excluded - * @returns {Set} chunks - */ -const getAllChunks = (entrypoint, excludedChunk1, excludedChunk2) => { - const queue = new Set([entrypoint]); - const chunks = new Set(); - for (const entrypoint of queue) { - for (const chunk of entrypoint.chunks) { - if (chunk === excludedChunk1) continue; - if (chunk === excludedChunk2) continue; - chunks.add(chunk); - } - for (const parent of entrypoint.parentsIterable) { - if (parent instanceof Entrypoint) queue.add(parent); - } - } - return chunks; -}; -module.exports.getAllChunks = getAllChunks; diff --git a/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js b/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js deleted file mode 100644 index 75384ab9a50..00000000000 --- a/webpack-lib/lib/javascript/CommonJsChunkFormatPlugin.js +++ /dev/null @@ -1,175 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource, RawSource } = require("webpack-sources"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const { getUndoPath } = require("../util/identifier"); -const { - getChunkFilenameTemplate, - getCompilationHooks -} = require("./JavascriptModulesPlugin"); -const { - generateEntryStartup, - updateHashForEntryStartup -} = require("./StartupHelpers"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Entrypoint")} Entrypoint */ - -class CommonJsChunkFormatPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "CommonJsChunkFormatPlugin", - compilation => { - compilation.hooks.additionalChunkRuntimeRequirements.tap( - "CommonJsChunkFormatPlugin", - (chunk, set, { chunkGraph }) => { - if (chunk.hasRuntime()) return; - if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { - set.add(RuntimeGlobals.require); - set.add(RuntimeGlobals.startupEntrypoint); - set.add(RuntimeGlobals.externalInstallChunk); - } - } - ); - const hooks = getCompilationHooks(compilation); - hooks.renderChunk.tap( - "CommonJsChunkFormatPlugin", - (modules, renderContext) => { - const { chunk, chunkGraph, runtimeTemplate } = renderContext; - const source = new ConcatSource(); - source.add(`exports.id = ${JSON.stringify(chunk.id)};\n`); - source.add(`exports.ids = ${JSON.stringify(chunk.ids)};\n`); - source.add("exports.modules = "); - source.add(modules); - source.add(";\n"); - const runtimeModules = - chunkGraph.getChunkRuntimeModulesInOrder(chunk); - if (runtimeModules.length > 0) { - source.add("exports.runtime =\n"); - source.add( - Template.renderChunkRuntimeModules( - runtimeModules, - renderContext - ) - ); - } - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - if (entries.length > 0) { - const runtimeChunk = - /** @type {Entrypoint} */ - (entries[0][1]).getRuntimeChunk(); - const currentOutputName = compilation - .getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ) - .replace(/^\/+/g, "") - .split("/"); - const runtimeOutputName = compilation - .getPath( - getChunkFilenameTemplate( - /** @type {Chunk} */ - (runtimeChunk), - compilation.outputOptions - ), - { - chunk: /** @type {Chunk} */ (runtimeChunk), - contentHashType: "javascript" - } - ) - .replace(/^\/+/g, "") - .split("/"); - - // remove common parts - while ( - currentOutputName.length > 1 && - runtimeOutputName.length > 1 && - currentOutputName[0] === runtimeOutputName[0] - ) { - currentOutputName.shift(); - runtimeOutputName.shift(); - } - const last = runtimeOutputName.join("/"); - // create final path - const runtimePath = - getUndoPath(currentOutputName.join("/"), last, true) + last; - - const entrySource = new ConcatSource(); - entrySource.add( - `(${ - runtimeTemplate.supportsArrowFunction() - ? "() => " - : "function() " - }{\n` - ); - entrySource.add("var exports = {};\n"); - entrySource.add(source); - entrySource.add(";\n\n// load runtime\n"); - entrySource.add( - `var ${RuntimeGlobals.require} = require(${JSON.stringify( - runtimePath - )});\n` - ); - entrySource.add( - `${RuntimeGlobals.externalInstallChunk}(exports);\n` - ); - const startupSource = new RawSource( - generateEntryStartup( - chunkGraph, - runtimeTemplate, - entries, - chunk, - false - ) - ); - entrySource.add( - hooks.renderStartup.call( - startupSource, - entries[entries.length - 1][0], - { - ...renderContext, - inlined: false - } - ) - ); - entrySource.add("\n})()"); - return entrySource; - } - return source; - } - ); - hooks.chunkHash.tap( - "CommonJsChunkFormatPlugin", - (chunk, hash, { chunkGraph }) => { - if (chunk.hasRuntime()) return; - hash.update("CommonJsChunkFormatPlugin"); - hash.update("1"); - const entries = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk) - ); - updateHashForEntryStartup(hash, chunkGraph, entries, chunk); - } - ); - } - ); - } -} - -module.exports = CommonJsChunkFormatPlugin; diff --git a/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js b/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js deleted file mode 100644 index 4e7263a5309..00000000000 --- a/webpack-lib/lib/javascript/EnableChunkLoadingPlugin.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").ChunkLoadingType} ChunkLoadingType */ -/** @typedef {import("../Compiler")} Compiler */ - -/** @type {WeakMap>} */ -const enabledTypes = new WeakMap(); - -/** - * @param {Compiler} compiler compiler - * @returns {Set} enabled types - */ -const getEnabledTypes = compiler => { - let set = enabledTypes.get(compiler); - if (set === undefined) { - set = new Set(); - enabledTypes.set(compiler, set); - } - return set; -}; - -class EnableChunkLoadingPlugin { - /** - * @param {ChunkLoadingType} type library type that should be available - */ - constructor(type) { - this.type = type; - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {ChunkLoadingType} type type of library - * @returns {void} - */ - static setEnabled(compiler, type) { - getEnabledTypes(compiler).add(type); - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {ChunkLoadingType} type type of library - * @returns {void} - */ - static checkEnabled(compiler, type) { - if (!getEnabledTypes(compiler).has(type)) { - throw new Error( - `Chunk loading type "${type}" is not enabled. ` + - "EnableChunkLoadingPlugin need to be used to enable this type of chunk loading. " + - 'This usually happens through the "output.enabledChunkLoadingTypes" option. ' + - 'If you are using a function as entry which sets "chunkLoading", you need to add all potential chunk loading types to "output.enabledChunkLoadingTypes". ' + - `These types are enabled: ${Array.from( - getEnabledTypes(compiler) - ).join(", ")}` - ); - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { type } = this; - - // Only enable once - const enabled = getEnabledTypes(compiler); - if (enabled.has(type)) return; - enabled.add(type); - - if (typeof type === "string") { - switch (type) { - case "jsonp": { - const JsonpChunkLoadingPlugin = require("../web/JsonpChunkLoadingPlugin"); - new JsonpChunkLoadingPlugin().apply(compiler); - break; - } - case "import-scripts": { - const ImportScriptsChunkLoadingPlugin = require("../webworker/ImportScriptsChunkLoadingPlugin"); - new ImportScriptsChunkLoadingPlugin().apply(compiler); - break; - } - case "require": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin"); - new CommonJsChunkLoadingPlugin({ - asyncChunkLoading: false - }).apply(compiler); - break; - } - case "async-node": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const CommonJsChunkLoadingPlugin = require("../node/CommonJsChunkLoadingPlugin"); - new CommonJsChunkLoadingPlugin({ - asyncChunkLoading: true - }).apply(compiler); - break; - } - case "import": - case "universal": { - const ModuleChunkLoadingPlugin = require("../esm/ModuleChunkLoadingPlugin"); - new ModuleChunkLoadingPlugin().apply(compiler); - break; - } - default: - throw new Error(`Unsupported chunk loading type ${type}. -Plugins which provide custom chunk loading types must call EnableChunkLoadingPlugin.setEnabled(compiler, type) to disable this error.`); - } - } else { - // TODO support plugin instances here - // apply them to the compiler - } - } -} - -module.exports = EnableChunkLoadingPlugin; diff --git a/webpack-lib/lib/javascript/JavascriptGenerator.js b/webpack-lib/lib/javascript/JavascriptGenerator.js deleted file mode 100644 index 6bba9756b39..00000000000 --- a/webpack-lib/lib/javascript/JavascriptGenerator.js +++ /dev/null @@ -1,255 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const { RawSource, ReplaceSource } = require("webpack-sources"); -const Generator = require("../Generator"); -const InitFragment = require("../InitFragment"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibilityDependency"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../DependenciesBlock")} DependenciesBlock */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplate")} DependencyTemplate */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -// TODO: clean up this file -// replace with newer constructs - -const deprecatedGetInitFragments = util.deprecate( - /** - * @param {DependencyTemplate} template template - * @param {Dependency} dependency dependency - * @param {DependencyTemplateContext} templateContext template context - * @returns {InitFragment[]} init fragments - */ - (template, dependency, templateContext) => - /** @type {DependencyTemplate & { getInitFragments: function(Dependency, DependencyTemplateContext): InitFragment[] }} */ - (template).getInitFragments(dependency, templateContext), - "DependencyTemplate.getInitFragment is deprecated (use apply(dep, source, { initFragments }) instead)", - "DEP_WEBPACK_JAVASCRIPT_GENERATOR_GET_INIT_FRAGMENTS" -); - -class JavascriptGenerator extends Generator { - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return JS_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - const originalSource = module.originalSource(); - if (!originalSource) { - return 39; - } - return originalSource.size(); - } - - /** - * @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) { - // Only harmony modules are valid for optimization - if ( - !module.buildMeta || - module.buildMeta.exportsType !== "namespace" || - module.presentationalDependencies === undefined || - !module.presentationalDependencies.some( - d => d instanceof HarmonyCompatibilityDependency - ) - ) { - return "Module is not an ECMAScript module"; - } - - // Some expressions are not compatible with module concatenation - // because they may produce unexpected results. The plugin bails out - // if some were detected upfront. - if (module.buildInfo && module.buildInfo.moduleConcatenationBailout) { - return `Module uses ${module.buildInfo.moduleConcatenationBailout}`; - } - } - - /** - * @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 originalSource = module.originalSource(); - if (!originalSource) { - return new RawSource("throw new Error('No source available');"); - } - - const source = new ReplaceSource(originalSource); - /** @type {InitFragment[]} */ - const initFragments = []; - - this.sourceModule(module, initFragments, source, generateContext); - - return InitFragment.addToSource(source, initFragments, generateContext); - } - - /** - * @param {Module} module the module to generate - * @param {InitFragment[]} initFragments mutable list of init fragments - * @param {ReplaceSource} source the current replace source which can be modified - * @param {GenerateContext} generateContext the generateContext - * @returns {void} - */ - sourceModule(module, initFragments, source, generateContext) { - for (const dependency of module.dependencies) { - this.sourceDependency( - module, - dependency, - initFragments, - source, - generateContext - ); - } - - if (module.presentationalDependencies !== undefined) { - for (const dependency of module.presentationalDependencies) { - this.sourceDependency( - module, - dependency, - initFragments, - source, - generateContext - ); - } - } - - for (const childBlock of module.blocks) { - this.sourceBlock( - module, - childBlock, - initFragments, - source, - generateContext - ); - } - } - - /** - * @param {Module} module the module to generate - * @param {DependenciesBlock} block the dependencies block which will be processed - * @param {InitFragment[]} initFragments mutable list of init fragments - * @param {ReplaceSource} source the current replace source which can be modified - * @param {GenerateContext} generateContext the generateContext - * @returns {void} - */ - sourceBlock(module, block, initFragments, source, generateContext) { - for (const dependency of block.dependencies) { - this.sourceDependency( - module, - dependency, - initFragments, - source, - generateContext - ); - } - - for (const childBlock of block.blocks) { - this.sourceBlock( - module, - childBlock, - initFragments, - source, - generateContext - ); - } - } - - /** - * @param {Module} module the current module - * @param {Dependency} dependency the dependency to generate - * @param {InitFragment[]} initFragments mutable list of init fragments - * @param {ReplaceSource} source the current replace source which can be modified - * @param {GenerateContext} generateContext the render context - * @returns {void} - */ - sourceDependency(module, dependency, initFragments, source, generateContext) { - const constructor = - /** @type {new (...args: EXPECTED_ANY[]) => Dependency} */ - (dependency.constructor); - const template = generateContext.dependencyTemplates.get(constructor); - if (!template) { - throw new Error( - `No template for dependency: ${dependency.constructor.name}` - ); - } - - /** @type {InitFragment[] | undefined} */ - let chunkInitFragments; - - /** @type {DependencyTemplateContext} */ - const templateContext = { - runtimeTemplate: generateContext.runtimeTemplate, - dependencyTemplates: generateContext.dependencyTemplates, - moduleGraph: generateContext.moduleGraph, - chunkGraph: generateContext.chunkGraph, - module, - runtime: generateContext.runtime, - runtimeRequirements: generateContext.runtimeRequirements, - concatenationScope: generateContext.concatenationScope, - codeGenerationResults: - /** @type {NonNullable} */ - (generateContext.codeGenerationResults), - initFragments, - get chunkInitFragments() { - if (!chunkInitFragments) { - const data = - /** @type {NonNullable} */ - (generateContext.getData)(); - chunkInitFragments = data.get("chunkInitFragments"); - if (!chunkInitFragments) { - chunkInitFragments = []; - data.set("chunkInitFragments", chunkInitFragments); - } - } - - return chunkInitFragments; - } - }; - - template.apply(dependency, source, templateContext); - - // TODO remove in webpack 6 - if ("getInitFragments" in template) { - const fragments = deprecatedGetInitFragments( - template, - dependency, - templateContext - ); - - if (fragments) { - for (const fragment of fragments) { - initFragments.push(fragment); - } - } - } - } -} - -module.exports = JavascriptGenerator; diff --git a/webpack-lib/lib/javascript/JavascriptModulesPlugin.js b/webpack-lib/lib/javascript/JavascriptModulesPlugin.js deleted file mode 100644 index 6b4046c4e5b..00000000000 --- a/webpack-lib/lib/javascript/JavascriptModulesPlugin.js +++ /dev/null @@ -1,1651 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const eslintScope = require("eslint-scope"); -const { SyncWaterfallHook, SyncHook, SyncBailHook } = require("tapable"); -const vm = require("vm"); -const { - ConcatSource, - OriginalSource, - PrefixSource, - RawSource, - CachedSource, - ReplaceSource -} = require("webpack-sources"); -const Compilation = require("../Compilation"); -const { tryRunOrWebpackError } = require("../HookWebpackError"); -const HotUpdateChunk = require("../HotUpdateChunk"); -const InitFragment = require("../InitFragment"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_DYNAMIC, - JAVASCRIPT_MODULE_TYPE_ESM, - WEBPACK_MODULE_TYPE_RUNTIME -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const { last, someInIterable } = require("../util/IterableHelpers"); -const StringXor = require("../util/StringXor"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const { - getPathInAst, - getAllReferences, - RESERVED_NAMES, - findNewName, - addScopeSymbols, - getUsedNamesInScopeInfo -} = require("../util/concatenate"); -const createHash = require("../util/createHash"); -const nonNumericOnlyHash = require("../util/nonNumericOnlyHash"); -const { intersectRuntime } = require("../util/runtime"); -const JavascriptGenerator = require("./JavascriptGenerator"); -const JavascriptParser = require("./JavascriptParser"); - -/** @typedef {import("eslint-scope").Reference} Reference */ -/** @typedef {import("eslint-scope").Scope} Scope */ -/** @typedef {import("eslint-scope").Variable} Variable */ -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compilation").ModuleObject} ModuleObject */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Entrypoint")} Entrypoint */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../util/createHash").Algorithm} Algorithm */ - -/** - * @param {Chunk} chunk a chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {boolean} true, when a JS file is needed for this chunk - */ -const chunkHasJs = (chunk, chunkGraph) => { - if (chunkGraph.getNumberOfEntryModules(chunk) > 0) return true; - - return Boolean( - chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript") - ); -}; - -/** - * @param {Chunk} chunk a chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {boolean} true, when a JS file is needed for this chunk - */ -const chunkHasRuntimeOrJs = (chunk, chunkGraph) => { - if ( - chunkGraph.getChunkModulesIterableBySourceType( - chunk, - WEBPACK_MODULE_TYPE_RUNTIME - ) - ) - return true; - - return Boolean( - chunkGraph.getChunkModulesIterableBySourceType(chunk, "javascript") - ); -}; - -/** - * @param {Module} module a module - * @param {string} code the code - * @returns {string} generated code for the stack - */ -const printGeneratedCodeForStack = (module, code) => { - const lines = code.split("\n"); - const n = `${lines.length}`.length; - return `\n\nGenerated code for ${module.identifier()}\n${lines - .map( - /** - * @param {string} line the line - * @param {number} i the index - * @param {string[]} lines the lines - * @returns {string} the line with line number - */ - (line, i, lines) => { - const iStr = `${i + 1}`; - return `${" ".repeat(n - iStr.length)}${iStr} | ${line}`; - } - ) - .join("\n")}`; -}; - -/** - * @typedef {object} RenderContext - * @property {Chunk} chunk the chunk - * @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 {CodeGenerationResults} codeGenerationResults results of code generation - * @property {boolean | undefined} strictMode rendering in strict context - */ - -/** - * @typedef {object} MainRenderContext - * @property {Chunk} chunk the chunk - * @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 {CodeGenerationResults} codeGenerationResults results of code generation - * @property {string} hash hash to be used for render call - * @property {boolean | undefined} strictMode rendering in strict context - */ - -/** - * @typedef {object} ChunkRenderContext - * @property {Chunk} chunk the chunk - * @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 {CodeGenerationResults} codeGenerationResults results of code generation - * @property {InitFragment[]} chunkInitFragments init fragments for the chunk - * @property {boolean | undefined} strictMode rendering in strict context - */ - -/** - * @typedef {object} RenderBootstrapContext - * @property {Chunk} chunk the chunk - * @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 - * @property {string} hash hash to be used for render call - */ - -/** @typedef {RenderContext & { inlined: boolean }} StartupRenderContext */ - -/** - * @typedef {object} CompilationHooks - * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModuleContent - * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModuleContainer - * @property {SyncWaterfallHook<[Source, Module, ChunkRenderContext]>} renderModulePackage - * @property {SyncWaterfallHook<[Source, RenderContext]>} renderChunk - * @property {SyncWaterfallHook<[Source, RenderContext]>} renderMain - * @property {SyncWaterfallHook<[Source, RenderContext]>} renderContent - * @property {SyncWaterfallHook<[Source, RenderContext]>} render - * @property {SyncWaterfallHook<[Source, Module, StartupRenderContext]>} renderStartup - * @property {SyncWaterfallHook<[string, RenderBootstrapContext]>} renderRequire - * @property {SyncBailHook<[Module, RenderBootstrapContext], string | void>} inlineInRuntimeBailout - * @property {SyncBailHook<[Module, RenderContext], string | void>} embedInRuntimeBailout - * @property {SyncBailHook<[RenderContext], string | void>} strictRuntimeBailout - * @property {SyncHook<[Chunk, Hash, ChunkHashContext]>} chunkHash - * @property {SyncBailHook<[Chunk, RenderContext], boolean | void>} useSourceMap - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -const PLUGIN_NAME = "JavascriptModulesPlugin"; - -/** @typedef {{ header: string[], beforeStartup: string[], startup: string[], afterStartup: string[], allowInlineStartup: boolean }} Bootstrap */ - -class JavascriptModulesPlugin { - /** - * @param {Compilation} compilation the compilation - * @returns {CompilationHooks} 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 = { - renderModuleContent: new SyncWaterfallHook([ - "source", - "module", - "renderContext" - ]), - renderModuleContainer: new SyncWaterfallHook([ - "source", - "module", - "renderContext" - ]), - renderModulePackage: new SyncWaterfallHook([ - "source", - "module", - "renderContext" - ]), - render: new SyncWaterfallHook(["source", "renderContext"]), - renderContent: new SyncWaterfallHook(["source", "renderContext"]), - renderStartup: new SyncWaterfallHook([ - "source", - "module", - "startupRenderContext" - ]), - renderChunk: new SyncWaterfallHook(["source", "renderContext"]), - renderMain: new SyncWaterfallHook(["source", "renderContext"]), - renderRequire: new SyncWaterfallHook(["code", "renderContext"]), - inlineInRuntimeBailout: new SyncBailHook(["module", "renderContext"]), - embedInRuntimeBailout: new SyncBailHook(["module", "renderContext"]), - strictRuntimeBailout: new SyncBailHook(["renderContext"]), - chunkHash: new SyncHook(["chunk", "hash", "context"]), - useSourceMap: new SyncBailHook(["chunk", "renderContext"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - constructor(options = {}) { - this.options = options; - /** @type {WeakMap} */ - this._moduleFactoryCache = new WeakMap(); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); - normalModuleFactory.hooks.createParser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, options => new JavascriptParser("auto")); - normalModuleFactory.hooks.createParser - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, options => new JavascriptParser("script")); - normalModuleFactory.hooks.createParser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, options => new JavascriptParser("module")); - normalModuleFactory.hooks.createGenerator - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, () => new JavascriptGenerator()); - normalModuleFactory.hooks.createGenerator - .for(JAVASCRIPT_MODULE_TYPE_DYNAMIC) - .tap(PLUGIN_NAME, () => new JavascriptGenerator()); - normalModuleFactory.hooks.createGenerator - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, () => new JavascriptGenerator()); - compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { - const { - hash, - chunk, - chunkGraph, - moduleGraph, - runtimeTemplate, - dependencyTemplates, - outputOptions, - codeGenerationResults - } = options; - - const hotUpdateChunk = chunk instanceof HotUpdateChunk ? chunk : null; - const filenameTemplate = - JavascriptModulesPlugin.getChunkFilenameTemplate( - chunk, - outputOptions - ); - - let render; - - if (hotUpdateChunk) { - render = () => - this.renderChunk( - { - chunk, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - codeGenerationResults, - strictMode: runtimeTemplate.isModule() - }, - hooks - ); - } else if (chunk.hasRuntime()) { - if (!chunkHasRuntimeOrJs(chunk, chunkGraph)) { - return result; - } - - render = () => - this.renderMain( - { - hash, - chunk, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - codeGenerationResults, - strictMode: runtimeTemplate.isModule() - }, - hooks, - compilation - ); - } else { - if (!chunkHasJs(chunk, chunkGraph)) { - return result; - } - - render = () => - this.renderChunk( - { - chunk, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - codeGenerationResults, - strictMode: runtimeTemplate.isModule() - }, - hooks - ); - } - - result.push({ - render, - filenameTemplate, - pathOptions: { - hash, - runtime: chunk.runtime, - chunk, - contentHashType: "javascript" - }, - info: { - javascriptModule: compilation.runtimeTemplate.isModule() - }, - identifier: hotUpdateChunk - ? `hotupdatechunk${chunk.id}` - : `chunk${chunk.id}`, - hash: chunk.contentHash.javascript - }); - - return result; - }); - compilation.hooks.chunkHash.tap(PLUGIN_NAME, (chunk, hash, context) => { - hooks.chunkHash.call(chunk, hash, context); - if (chunk.hasRuntime()) { - this.updateHashWithBootstrap( - hash, - { - hash: "0000", - chunk, - codeGenerationResults: context.codeGenerationResults, - chunkGraph: context.chunkGraph, - moduleGraph: context.moduleGraph, - runtimeTemplate: context.runtimeTemplate - }, - hooks - ); - } - }); - compilation.hooks.contentHash.tap(PLUGIN_NAME, chunk => { - const { - chunkGraph, - codeGenerationResults, - moduleGraph, - runtimeTemplate, - outputOptions: { - hashSalt, - hashDigest, - hashDigestLength, - hashFunction - } - } = compilation; - const hash = createHash(/** @type {Algorithm} */ (hashFunction)); - if (hashSalt) hash.update(hashSalt); - if (chunk.hasRuntime()) { - this.updateHashWithBootstrap( - hash, - { - hash: "0000", - chunk, - codeGenerationResults, - chunkGraph: compilation.chunkGraph, - moduleGraph: compilation.moduleGraph, - runtimeTemplate: compilation.runtimeTemplate - }, - hooks - ); - } else { - hash.update(`${chunk.id} `); - hash.update(chunk.ids ? chunk.ids.join(",") : ""); - } - hooks.chunkHash.call(chunk, hash, { - chunkGraph, - codeGenerationResults, - moduleGraph, - runtimeTemplate - }); - const modules = chunkGraph.getChunkModulesIterableBySourceType( - chunk, - "javascript" - ); - if (modules) { - const xor = new StringXor(); - for (const m of modules) { - xor.add(chunkGraph.getModuleHash(m, chunk.runtime)); - } - xor.updateHash(hash); - } - const runtimeModules = chunkGraph.getChunkModulesIterableBySourceType( - chunk, - WEBPACK_MODULE_TYPE_RUNTIME - ); - if (runtimeModules) { - const xor = new StringXor(); - for (const m of runtimeModules) { - xor.add(chunkGraph.getModuleHash(m, chunk.runtime)); - } - xor.updateHash(hash); - } - const digest = /** @type {string} */ (hash.digest(hashDigest)); - chunk.contentHash.javascript = nonNumericOnlyHash( - digest, - /** @type {number} */ - (hashDigestLength) - ); - }); - compilation.hooks.additionalTreeRuntimeRequirements.tap( - PLUGIN_NAME, - (chunk, set, { chunkGraph }) => { - if ( - !set.has(RuntimeGlobals.startupNoDefault) && - chunkGraph.hasChunkEntryDependentChunks(chunk) - ) { - set.add(RuntimeGlobals.onChunksLoaded); - set.add(RuntimeGlobals.exports); - set.add(RuntimeGlobals.require); - } - } - ); - compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => { - const source = options.codeGenerationResult.sources.get("javascript"); - if (source === undefined) return; - const { module } = options; - const code = source.source(); - - const fn = vm.runInThisContext( - `(function(${module.moduleArgument}, ${module.exportsArgument}, ${RuntimeGlobals.require}) {\n${code}\n/**/})`, - { - filename: module.identifier(), - lineOffset: -1 - } - ); - - const moduleObject = - /** @type {ModuleObject} */ - (options.moduleObject); - - try { - fn.call( - moduleObject.exports, - moduleObject, - moduleObject.exports, - context.__webpack_require__ - ); - } catch (err) { - /** @type {Error} */ - (err).stack += printGeneratedCodeForStack( - options.module, - /** @type {string} */ (code) - ); - throw err; - } - }); - compilation.hooks.executeModule.tap(PLUGIN_NAME, (options, context) => { - const source = options.codeGenerationResult.sources.get("runtime"); - if (source === undefined) return; - let code = source.source(); - if (typeof code !== "string") code = code.toString(); - - const fn = vm.runInThisContext( - `(function(${RuntimeGlobals.require}) {\n${code}\n/**/})`, - { - filename: options.module.identifier(), - lineOffset: -1 - } - ); - try { - // eslint-disable-next-line no-useless-call - fn.call(null, context.__webpack_require__); - } catch (err) { - /** @type {Error} */ - (err).stack += printGeneratedCodeForStack(options.module, code); - throw err; - } - }); - } - ); - } - - /** - * @param {Chunk} chunk chunk - * @param {OutputOptions} outputOptions output options - * @returns {TemplatePath} used filename template - */ - static getChunkFilenameTemplate(chunk, outputOptions) { - if (chunk.filenameTemplate) { - return chunk.filenameTemplate; - } else if (chunk instanceof HotUpdateChunk) { - return /** @type {TemplatePath} */ (outputOptions.hotUpdateChunkFilename); - } else if (chunk.canBeInitial()) { - return /** @type {TemplatePath} */ (outputOptions.filename); - } - return /** @type {TemplatePath} */ (outputOptions.chunkFilename); - } - - /** - * @param {Module} module the rendered module - * @param {ChunkRenderContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @param {boolean} factory true: renders as factory method, false: pure module content - * @returns {Source | null} the newly generated source from rendering - */ - renderModule(module, renderContext, hooks, factory) { - const { - chunk, - chunkGraph, - runtimeTemplate, - codeGenerationResults, - strictMode - } = renderContext; - try { - const codeGenResult = codeGenerationResults.get(module, chunk.runtime); - const moduleSource = codeGenResult.sources.get("javascript"); - if (!moduleSource) return null; - if (codeGenResult.data !== undefined) { - const chunkInitFragments = codeGenResult.data.get("chunkInitFragments"); - if (chunkInitFragments) { - for (const i of chunkInitFragments) - renderContext.chunkInitFragments.push(i); - } - } - const moduleSourcePostContent = tryRunOrWebpackError( - () => - hooks.renderModuleContent.call(moduleSource, module, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderModuleContent" - ); - let moduleSourcePostContainer; - if (factory) { - const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements( - module, - chunk.runtime - ); - const needModule = runtimeRequirements.has(RuntimeGlobals.module); - const needExports = runtimeRequirements.has(RuntimeGlobals.exports); - const needRequire = - runtimeRequirements.has(RuntimeGlobals.require) || - runtimeRequirements.has(RuntimeGlobals.requireScope); - const needThisAsExports = runtimeRequirements.has( - RuntimeGlobals.thisAsExports - ); - const needStrict = - /** @type {BuildInfo} */ - (module.buildInfo).strict && !strictMode; - const cacheEntry = this._moduleFactoryCache.get( - moduleSourcePostContent - ); - let source; - if ( - cacheEntry && - cacheEntry.needModule === needModule && - cacheEntry.needExports === needExports && - cacheEntry.needRequire === needRequire && - cacheEntry.needThisAsExports === needThisAsExports && - cacheEntry.needStrict === needStrict - ) { - source = cacheEntry.source; - } else { - const factorySource = new ConcatSource(); - const args = []; - if (needExports || needRequire || needModule) - args.push( - needModule - ? module.moduleArgument - : `__unused_webpack_${module.moduleArgument}` - ); - if (needExports || needRequire) - args.push( - needExports - ? module.exportsArgument - : `__unused_webpack_${module.exportsArgument}` - ); - if (needRequire) args.push(RuntimeGlobals.require); - if (!needThisAsExports && runtimeTemplate.supportsArrowFunction()) { - factorySource.add(`/***/ ((${args.join(", ")}) => {\n\n`); - } else { - factorySource.add(`/***/ (function(${args.join(", ")}) {\n\n`); - } - if (needStrict) { - factorySource.add('"use strict";\n'); - } - factorySource.add(moduleSourcePostContent); - factorySource.add("\n\n/***/ })"); - source = new CachedSource(factorySource); - this._moduleFactoryCache.set(moduleSourcePostContent, { - source, - needModule, - needExports, - needRequire, - needThisAsExports, - needStrict - }); - } - moduleSourcePostContainer = tryRunOrWebpackError( - () => hooks.renderModuleContainer.call(source, module, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderModuleContainer" - ); - } else { - moduleSourcePostContainer = moduleSourcePostContent; - } - return tryRunOrWebpackError( - () => - hooks.renderModulePackage.call( - moduleSourcePostContainer, - module, - renderContext - ), - "JavascriptModulesPlugin.getCompilationHooks().renderModulePackage" - ); - } catch (err) { - /** @type {WebpackError} */ - (err).module = module; - throw err; - } - } - - /** - * @param {RenderContext} renderContext the render context - * @param {CompilationHooks} hooks hooks - * @returns {Source} the rendered source - */ - renderChunk(renderContext, hooks) { - const { chunk, chunkGraph } = renderContext; - const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - "javascript", - compareModulesByIdentifier - ); - const allModules = modules ? Array.from(modules) : []; - let strictHeader; - let allStrict = renderContext.strictMode; - if ( - !allStrict && - allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict) - ) { - const strictBailout = hooks.strictRuntimeBailout.call(renderContext); - strictHeader = strictBailout - ? `// runtime can't be in strict mode because ${strictBailout}.\n` - : '"use strict";\n'; - if (!strictBailout) allStrict = true; - } - /** @type {ChunkRenderContext} */ - const chunkRenderContext = { - ...renderContext, - chunkInitFragments: [], - strictMode: allStrict - }; - const moduleSources = - Template.renderChunkModules(chunkRenderContext, allModules, module => - this.renderModule(module, chunkRenderContext, hooks, true) - ) || new RawSource("{}"); - let source = tryRunOrWebpackError( - () => hooks.renderChunk.call(moduleSources, chunkRenderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderChunk" - ); - source = tryRunOrWebpackError( - () => hooks.renderContent.call(source, chunkRenderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderContent" - ); - if (!source) { - throw new Error( - "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something" - ); - } - source = InitFragment.addToSource( - source, - chunkRenderContext.chunkInitFragments, - chunkRenderContext - ); - source = tryRunOrWebpackError( - () => hooks.render.call(source, chunkRenderContext), - "JavascriptModulesPlugin.getCompilationHooks().render" - ); - if (!source) { - throw new Error( - "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something" - ); - } - chunk.rendered = true; - return strictHeader - ? new ConcatSource(strictHeader, source, ";") - : renderContext.runtimeTemplate.isModule() - ? source - : new ConcatSource(source, ";"); - } - - /** - * @param {MainRenderContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @param {Compilation} compilation the compilation - * @returns {Source} the newly generated source from rendering - */ - renderMain(renderContext, hooks, compilation) { - const { chunk, chunkGraph, runtimeTemplate } = renderContext; - - const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - const iife = runtimeTemplate.isIIFE(); - - const bootstrap = this.renderBootstrap(renderContext, hooks); - const useSourceMap = hooks.useSourceMap.call(chunk, renderContext); - - const allModules = Array.from( - chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - "javascript", - compareModulesByIdentifier - ) || [] - ); - - const hasEntryModules = chunkGraph.getNumberOfEntryModules(chunk) > 0; - /** @type {Set | undefined} */ - let inlinedModules; - if (bootstrap.allowInlineStartup && hasEntryModules) { - inlinedModules = new Set(chunkGraph.getChunkEntryModulesIterable(chunk)); - } - - const source = new ConcatSource(); - let prefix; - if (iife) { - if (runtimeTemplate.supportsArrowFunction()) { - source.add("/******/ (() => { // webpackBootstrap\n"); - } else { - source.add("/******/ (function() { // webpackBootstrap\n"); - } - prefix = "/******/ \t"; - } else { - prefix = "/******/ "; - } - let allStrict = renderContext.strictMode; - if ( - !allStrict && - allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict) - ) { - const strictBailout = hooks.strictRuntimeBailout.call(renderContext); - if (strictBailout) { - source.add( - `${ - prefix - }// runtime can't be in strict mode because ${strictBailout}.\n` - ); - } else { - allStrict = true; - source.add(`${prefix}"use strict";\n`); - } - } - - /** @type {ChunkRenderContext} */ - const chunkRenderContext = { - ...renderContext, - chunkInitFragments: [], - strictMode: allStrict - }; - - const chunkModules = Template.renderChunkModules( - chunkRenderContext, - inlinedModules - ? allModules.filter( - m => !(/** @type {Set} */ (inlinedModules).has(m)) - ) - : allModules, - module => this.renderModule(module, chunkRenderContext, hooks, true), - prefix - ); - if ( - chunkModules || - runtimeRequirements.has(RuntimeGlobals.moduleFactories) || - runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly) || - runtimeRequirements.has(RuntimeGlobals.require) - ) { - source.add(`${prefix}var __webpack_modules__ = (`); - source.add(chunkModules || "{}"); - source.add(");\n"); - source.add( - "/************************************************************************/\n" - ); - } - - if (bootstrap.header.length > 0) { - const header = `${Template.asString(bootstrap.header)}\n`; - source.add( - new PrefixSource( - prefix, - useSourceMap - ? new OriginalSource(header, "webpack/bootstrap") - : new RawSource(header) - ) - ); - source.add( - "/************************************************************************/\n" - ); - } - - const runtimeModules = - renderContext.chunkGraph.getChunkRuntimeModulesInOrder(chunk); - - if (runtimeModules.length > 0) { - source.add( - new PrefixSource( - prefix, - Template.renderRuntimeModules(runtimeModules, chunkRenderContext) - ) - ); - source.add( - "/************************************************************************/\n" - ); - // runtimeRuntimeModules calls codeGeneration - for (const module of runtimeModules) { - compilation.codeGeneratedModules.add(module); - } - } - if (inlinedModules) { - if (bootstrap.beforeStartup.length > 0) { - const beforeStartup = `${Template.asString(bootstrap.beforeStartup)}\n`; - source.add( - new PrefixSource( - prefix, - useSourceMap - ? new OriginalSource(beforeStartup, "webpack/before-startup") - : new RawSource(beforeStartup) - ) - ); - } - const lastInlinedModule = /** @type {Module} */ (last(inlinedModules)); - const startupSource = new ConcatSource(); - - if (runtimeRequirements.has(RuntimeGlobals.exports)) { - startupSource.add(`var ${RuntimeGlobals.exports} = {};\n`); - } - - const avoidEntryIife = compilation.options.optimization.avoidEntryIife; - /** @type {Map | false} */ - let renamedInlinedModule = false; - if (avoidEntryIife) { - renamedInlinedModule = this.getRenamedInlineModule( - allModules, - renderContext, - inlinedModules, - chunkRenderContext, - hooks, - allStrict, - Boolean(chunkModules) - ); - } - - for (const m of inlinedModules) { - const renderedModule = renamedInlinedModule - ? renamedInlinedModule.get(m) - : this.renderModule(m, chunkRenderContext, hooks, false); - - if (renderedModule) { - const innerStrict = - !allStrict && /** @type {BuildInfo} */ (m.buildInfo).strict; - const runtimeRequirements = chunkGraph.getModuleRuntimeRequirements( - m, - chunk.runtime - ); - const exports = runtimeRequirements.has(RuntimeGlobals.exports); - const webpackExports = - exports && m.exportsArgument === RuntimeGlobals.exports; - const iife = innerStrict - ? "it needs to be in strict mode." - : inlinedModules.size > 1 - ? // TODO check globals and top-level declarations of other entries and chunk modules - // to make a better decision - "it needs to be isolated against other entry modules." - : chunkModules && !renamedInlinedModule - ? "it needs to be isolated against other modules in the chunk." - : exports && !webpackExports - ? `it uses a non-standard name for the exports (${m.exportsArgument}).` - : hooks.embedInRuntimeBailout.call(m, renderContext); - let footer; - if (iife !== undefined) { - startupSource.add( - `// This entry needs to be wrapped in an IIFE because ${iife}\n` - ); - const arrow = runtimeTemplate.supportsArrowFunction(); - if (arrow) { - startupSource.add("(() => {\n"); - footer = "\n})();\n\n"; - } else { - startupSource.add("!function() {\n"); - footer = "\n}();\n"; - } - if (innerStrict) startupSource.add('"use strict";\n'); - } else { - footer = "\n"; - } - if (exports) { - if (m !== lastInlinedModule) - startupSource.add(`var ${m.exportsArgument} = {};\n`); - else if (m.exportsArgument !== RuntimeGlobals.exports) - startupSource.add( - `var ${m.exportsArgument} = ${RuntimeGlobals.exports};\n` - ); - } - startupSource.add(renderedModule); - startupSource.add(footer); - } - } - if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) { - startupSource.add( - `${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});\n` - ); - } - source.add( - hooks.renderStartup.call(startupSource, lastInlinedModule, { - ...renderContext, - inlined: true - }) - ); - if (bootstrap.afterStartup.length > 0) { - const afterStartup = `${Template.asString(bootstrap.afterStartup)}\n`; - source.add( - new PrefixSource( - prefix, - useSourceMap - ? new OriginalSource(afterStartup, "webpack/after-startup") - : new RawSource(afterStartup) - ) - ); - } - } else { - const lastEntryModule = - /** @type {Module} */ - (last(chunkGraph.getChunkEntryModulesIterable(chunk))); - /** @type {function(string[], string): Source} */ - const toSource = useSourceMap - ? (content, name) => - new OriginalSource(Template.asString(content), name) - : content => new RawSource(Template.asString(content)); - source.add( - new PrefixSource( - prefix, - new ConcatSource( - toSource(bootstrap.beforeStartup, "webpack/before-startup"), - "\n", - hooks.renderStartup.call( - toSource(bootstrap.startup.concat(""), "webpack/startup"), - lastEntryModule, - { - ...renderContext, - inlined: false - } - ), - toSource(bootstrap.afterStartup, "webpack/after-startup"), - "\n" - ) - ) - ); - } - if ( - hasEntryModules && - runtimeRequirements.has(RuntimeGlobals.returnExportsFromRuntime) - ) { - source.add(`${prefix}return ${RuntimeGlobals.exports};\n`); - } - if (iife) { - source.add("/******/ })()\n"); - } - - /** @type {Source} */ - let finalSource = tryRunOrWebpackError( - () => hooks.renderMain.call(source, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderMain" - ); - if (!finalSource) { - throw new Error( - "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderMain plugins should return something" - ); - } - finalSource = tryRunOrWebpackError( - () => hooks.renderContent.call(finalSource, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderContent" - ); - if (!finalSource) { - throw new Error( - "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().renderContent plugins should return something" - ); - } - - finalSource = InitFragment.addToSource( - finalSource, - chunkRenderContext.chunkInitFragments, - chunkRenderContext - ); - finalSource = tryRunOrWebpackError( - () => hooks.render.call(finalSource, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().render" - ); - if (!finalSource) { - throw new Error( - "JavascriptModulesPlugin error: JavascriptModulesPlugin.getCompilationHooks().render plugins should return something" - ); - } - chunk.rendered = true; - return iife ? new ConcatSource(finalSource, ";") : finalSource; - } - - /** - * @param {Hash} hash the hash to be updated - * @param {RenderBootstrapContext} renderContext options object - * @param {CompilationHooks} hooks hooks - */ - updateHashWithBootstrap(hash, renderContext, hooks) { - const bootstrap = this.renderBootstrap(renderContext, hooks); - for (const _k of Object.keys(bootstrap)) { - const key = /** @type {keyof Bootstrap} */ (_k); - hash.update(key); - if (Array.isArray(bootstrap[key])) { - for (const line of bootstrap[key]) { - hash.update(line); - } - } else { - hash.update(JSON.stringify(bootstrap[key])); - } - } - } - - /** - * @param {RenderBootstrapContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @returns {Bootstrap} the generated source of the bootstrap code - */ - renderBootstrap(renderContext, hooks) { - const { - chunkGraph, - codeGenerationResults, - moduleGraph, - chunk, - runtimeTemplate - } = renderContext; - - const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - - const requireFunction = runtimeRequirements.has(RuntimeGlobals.require); - const moduleCache = runtimeRequirements.has(RuntimeGlobals.moduleCache); - const moduleFactories = runtimeRequirements.has( - RuntimeGlobals.moduleFactories - ); - const moduleUsed = runtimeRequirements.has(RuntimeGlobals.module); - const requireScopeUsed = runtimeRequirements.has( - RuntimeGlobals.requireScope - ); - const interceptModuleExecution = runtimeRequirements.has( - RuntimeGlobals.interceptModuleExecution - ); - - const useRequire = - requireFunction || interceptModuleExecution || moduleUsed; - - /** - * @type {{startup: string[], beforeStartup: string[], header: string[], afterStartup: string[], allowInlineStartup: boolean}} - */ - const result = { - header: [], - beforeStartup: [], - startup: [], - afterStartup: [], - allowInlineStartup: true - }; - - const { header: buf, startup, beforeStartup, afterStartup } = result; - - if (result.allowInlineStartup && moduleFactories) { - startup.push( - "// module factories are used so entry inlining is disabled" - ); - result.allowInlineStartup = false; - } - if (result.allowInlineStartup && moduleCache) { - startup.push("// module cache are used so entry inlining is disabled"); - result.allowInlineStartup = false; - } - if (result.allowInlineStartup && interceptModuleExecution) { - startup.push( - "// module execution is intercepted so entry inlining is disabled" - ); - result.allowInlineStartup = false; - } - - if (useRequire || moduleCache) { - buf.push("// The module cache"); - buf.push("var __webpack_module_cache__ = {};"); - buf.push(""); - } - - if (useRequire) { - buf.push("// The require function"); - buf.push(`function ${RuntimeGlobals.require}(moduleId) {`); - buf.push(Template.indent(this.renderRequire(renderContext, hooks))); - buf.push("}"); - buf.push(""); - } else if (runtimeRequirements.has(RuntimeGlobals.requireScope)) { - buf.push("// The require scope"); - buf.push(`var ${RuntimeGlobals.require} = {};`); - buf.push(""); - } - - if ( - moduleFactories || - runtimeRequirements.has(RuntimeGlobals.moduleFactoriesAddOnly) - ) { - buf.push("// expose the modules object (__webpack_modules__)"); - buf.push(`${RuntimeGlobals.moduleFactories} = __webpack_modules__;`); - buf.push(""); - } - - if (moduleCache) { - buf.push("// expose the module cache"); - buf.push(`${RuntimeGlobals.moduleCache} = __webpack_module_cache__;`); - buf.push(""); - } - - if (interceptModuleExecution) { - buf.push("// expose the module execution interceptor"); - buf.push(`${RuntimeGlobals.interceptModuleExecution} = [];`); - buf.push(""); - } - - if (!runtimeRequirements.has(RuntimeGlobals.startupNoDefault)) { - if (chunkGraph.getNumberOfEntryModules(chunk) > 0) { - /** @type {string[]} */ - const buf2 = []; - const runtimeRequirements = - chunkGraph.getTreeRuntimeRequirements(chunk); - buf2.push("// Load entry module and return exports"); - let i = chunkGraph.getNumberOfEntryModules(chunk); - for (const [ - entryModule, - entrypoint - ] of chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk)) { - if (!chunkGraph.getModuleSourceTypes(entryModule).has("javascript")) { - i--; - continue; - } - const chunks = - /** @type {Entrypoint} */ - (entrypoint).chunks.filter(c => c !== chunk); - if (result.allowInlineStartup && chunks.length > 0) { - buf2.push( - "// This entry module depends on other loaded chunks and execution need to be delayed" - ); - result.allowInlineStartup = false; - } - if ( - result.allowInlineStartup && - someInIterable( - moduleGraph.getIncomingConnectionsByOriginModule(entryModule), - ([originModule, connections]) => - originModule && - connections.some(c => c.isTargetActive(chunk.runtime)) && - someInIterable( - chunkGraph.getModuleRuntimes(originModule), - runtime => - intersectRuntime(runtime, chunk.runtime) !== undefined - ) - ) - ) { - buf2.push( - "// This entry module is referenced by other modules so it can't be inlined" - ); - result.allowInlineStartup = false; - } - - let data; - if (codeGenerationResults.has(entryModule, chunk.runtime)) { - const result = codeGenerationResults.get( - entryModule, - chunk.runtime - ); - data = result.data; - } - if ( - result.allowInlineStartup && - (!data || !data.get("topLevelDeclarations")) && - (!entryModule.buildInfo || - !entryModule.buildInfo.topLevelDeclarations) - ) { - buf2.push( - "// This entry module doesn't tell about it's top-level declarations so it can't be inlined" - ); - result.allowInlineStartup = false; - } - if (result.allowInlineStartup) { - const bailout = hooks.inlineInRuntimeBailout.call( - entryModule, - renderContext - ); - if (bailout !== undefined) { - buf2.push( - `// This entry module can't be inlined because ${bailout}` - ); - result.allowInlineStartup = false; - } - } - i--; - const moduleId = chunkGraph.getModuleId(entryModule); - const entryRuntimeRequirements = - chunkGraph.getModuleRuntimeRequirements(entryModule, chunk.runtime); - let moduleIdExpr = JSON.stringify(moduleId); - if (runtimeRequirements.has(RuntimeGlobals.entryModuleId)) { - moduleIdExpr = `${RuntimeGlobals.entryModuleId} = ${moduleIdExpr}`; - } - if ( - result.allowInlineStartup && - entryRuntimeRequirements.has(RuntimeGlobals.module) - ) { - result.allowInlineStartup = false; - buf2.push( - "// This entry module used 'module' so it can't be inlined" - ); - } - if (chunks.length > 0) { - buf2.push( - `${i === 0 ? `var ${RuntimeGlobals.exports} = ` : ""}${ - RuntimeGlobals.onChunksLoaded - }(undefined, ${JSON.stringify( - chunks.map(c => c.id) - )}, ${runtimeTemplate.returningFunction( - `${RuntimeGlobals.require}(${moduleIdExpr})` - )})` - ); - } else if (useRequire) { - buf2.push( - `${i === 0 ? `var ${RuntimeGlobals.exports} = ` : ""}${ - RuntimeGlobals.require - }(${moduleIdExpr});` - ); - } else { - if (i === 0) buf2.push(`var ${RuntimeGlobals.exports} = {};`); - if (requireScopeUsed) { - buf2.push( - `__webpack_modules__[${moduleIdExpr}](0, ${ - i === 0 ? RuntimeGlobals.exports : "{}" - }, ${RuntimeGlobals.require});` - ); - } else if (entryRuntimeRequirements.has(RuntimeGlobals.exports)) { - buf2.push( - `__webpack_modules__[${moduleIdExpr}](0, ${ - i === 0 ? RuntimeGlobals.exports : "{}" - });` - ); - } else { - buf2.push(`__webpack_modules__[${moduleIdExpr}]();`); - } - } - } - if (runtimeRequirements.has(RuntimeGlobals.onChunksLoaded)) { - buf2.push( - `${RuntimeGlobals.exports} = ${RuntimeGlobals.onChunksLoaded}(${RuntimeGlobals.exports});` - ); - } - if ( - runtimeRequirements.has(RuntimeGlobals.startup) || - (runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) && - runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)) - ) { - result.allowInlineStartup = false; - buf.push("// the startup function"); - buf.push( - `${RuntimeGlobals.startup} = ${runtimeTemplate.basicFunction("", [ - ...buf2, - `return ${RuntimeGlobals.exports};` - ])};` - ); - buf.push(""); - startup.push("// run startup"); - startup.push( - `var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();` - ); - } else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore)) { - buf.push("// the startup function"); - buf.push( - `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` - ); - beforeStartup.push("// run runtime startup"); - beforeStartup.push(`${RuntimeGlobals.startup}();`); - startup.push("// startup"); - startup.push(Template.asString(buf2)); - } else if (runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter)) { - buf.push("// the startup function"); - buf.push( - `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` - ); - startup.push("// startup"); - startup.push(Template.asString(buf2)); - afterStartup.push("// run runtime startup"); - afterStartup.push(`${RuntimeGlobals.startup}();`); - } else { - startup.push("// startup"); - startup.push(Template.asString(buf2)); - } - } else if ( - runtimeRequirements.has(RuntimeGlobals.startup) || - runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) || - runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter) - ) { - buf.push( - "// the startup function", - "// It's empty as no entry modules are in this chunk", - `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};`, - "" - ); - } - } else if ( - runtimeRequirements.has(RuntimeGlobals.startup) || - runtimeRequirements.has(RuntimeGlobals.startupOnlyBefore) || - runtimeRequirements.has(RuntimeGlobals.startupOnlyAfter) - ) { - result.allowInlineStartup = false; - buf.push( - "// the startup function", - "// It's empty as some runtime module handles the default behavior", - `${RuntimeGlobals.startup} = ${runtimeTemplate.emptyFunction()};` - ); - startup.push("// run startup"); - startup.push( - `var ${RuntimeGlobals.exports} = ${RuntimeGlobals.startup}();` - ); - } - return result; - } - - /** - * @param {RenderBootstrapContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @returns {string} the generated source of the require function - */ - renderRequire(renderContext, hooks) { - const { - chunk, - chunkGraph, - runtimeTemplate: { outputOptions } - } = renderContext; - const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - const moduleExecution = runtimeRequirements.has( - RuntimeGlobals.interceptModuleExecution - ) - ? Template.asString([ - `var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: ${RuntimeGlobals.require} };`, - `${RuntimeGlobals.interceptModuleExecution}.forEach(function(handler) { handler(execOptions); });`, - "module = execOptions.module;", - "execOptions.factory.call(module.exports, module, module.exports, execOptions.require);" - ]) - : runtimeRequirements.has(RuntimeGlobals.thisAsExports) - ? Template.asString([ - `__webpack_modules__[moduleId].call(module.exports, module, module.exports, ${RuntimeGlobals.require});` - ]) - : Template.asString([ - `__webpack_modules__[moduleId](module, module.exports, ${RuntimeGlobals.require});` - ]); - const needModuleId = runtimeRequirements.has(RuntimeGlobals.moduleId); - const needModuleLoaded = runtimeRequirements.has( - RuntimeGlobals.moduleLoaded - ); - const content = Template.asString([ - "// Check if module is in cache", - "var cachedModule = __webpack_module_cache__[moduleId];", - "if (cachedModule !== undefined) {", - outputOptions.strictModuleErrorHandling - ? Template.indent([ - "if (cachedModule.error !== undefined) throw cachedModule.error;", - "return cachedModule.exports;" - ]) - : Template.indent("return cachedModule.exports;"), - "}", - "// Create a new module (and put it into the cache)", - "var module = __webpack_module_cache__[moduleId] = {", - Template.indent([ - needModuleId ? "id: moduleId," : "// no module.id needed", - needModuleLoaded ? "loaded: false," : "// no module.loaded needed", - "exports: {}" - ]), - "};", - "", - outputOptions.strictModuleExceptionHandling - ? Template.asString([ - "// Execute the module function", - "var threw = true;", - "try {", - Template.indent([moduleExecution, "threw = false;"]), - "} finally {", - Template.indent([ - "if(threw) delete __webpack_module_cache__[moduleId];" - ]), - "}" - ]) - : outputOptions.strictModuleErrorHandling - ? Template.asString([ - "// Execute the module function", - "try {", - Template.indent(moduleExecution), - "} catch(e) {", - Template.indent(["module.error = e;", "throw e;"]), - "}" - ]) - : Template.asString([ - "// Execute the module function", - moduleExecution - ]), - needModuleLoaded - ? Template.asString([ - "", - "// Flag the module as loaded", - `${RuntimeGlobals.moduleLoaded} = true;`, - "" - ]) - : "", - "// Return the exports of the module", - "return module.exports;" - ]); - return tryRunOrWebpackError( - () => hooks.renderRequire.call(content, renderContext), - "JavascriptModulesPlugin.getCompilationHooks().renderRequire" - ); - } - - /** - * @param {Module[]} allModules allModules - * @param {MainRenderContext} renderContext renderContext - * @param {Set} inlinedModules inlinedModules - * @param {ChunkRenderContext} chunkRenderContext chunkRenderContext - * @param {CompilationHooks} hooks hooks - * @param {boolean | undefined} allStrict allStrict - * @param {boolean} hasChunkModules hasChunkModules - * @returns {Map | false} renamed inlined modules - */ - getRenamedInlineModule( - allModules, - renderContext, - inlinedModules, - chunkRenderContext, - hooks, - allStrict, - hasChunkModules - ) { - const innerStrict = - !allStrict && - allModules.every(m => /** @type {BuildInfo} */ (m.buildInfo).strict); - const isMultipleEntries = inlinedModules.size > 1; - const singleEntryWithModules = inlinedModules.size === 1 && hasChunkModules; - - // TODO: - // This step is before the IIFE reason calculation. Ideally, it should only be executed when this function can optimize the - // IIFE reason. Otherwise, it should directly return false. There are four reasons now, we have skipped two already, the left - // one is 'it uses a non-standard name for the exports'. - if (isMultipleEntries || innerStrict || !singleEntryWithModules) { - return false; - } - - /** @type {Map} */ - const renamedInlinedModules = new Map(); - const { runtimeTemplate } = renderContext; - - /** @typedef {{ source: Source, module: Module, ast: any, variables: Set, through: Set, usedInNonInlined: Set, moduleScope: Scope }} Info */ - /** @type {Map} */ - const inlinedModulesToInfo = new Map(); - /** @type {Set} */ - const nonInlinedModuleThroughIdentifiers = new Set(); - /** @type {Map} */ - - for (const m of allModules) { - const isInlinedModule = inlinedModules && inlinedModules.has(m); - const moduleSource = this.renderModule( - m, - chunkRenderContext, - hooks, - !isInlinedModule - ); - - if (!moduleSource) continue; - const code = /** @type {string} */ (moduleSource.source()); - const ast = JavascriptParser._parse(code, { - sourceType: "auto" - }); - - const scopeManager = eslintScope.analyze(ast, { - ecmaVersion: 6, - sourceType: "module", - optimistic: true, - ignoreEval: true - }); - - const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast)); - if (inlinedModules && inlinedModules.has(m)) { - const moduleScope = globalScope.childScopes[0]; - inlinedModulesToInfo.set(m, { - source: moduleSource, - ast, - module: m, - variables: new Set(moduleScope.variables), - through: new Set(moduleScope.through), - usedInNonInlined: new Set(), - moduleScope - }); - } else { - for (const ref of globalScope.through) { - nonInlinedModuleThroughIdentifiers.add(ref.identifier.name); - } - } - } - - for (const [, { variables, usedInNonInlined }] of inlinedModulesToInfo) { - for (const variable of variables) { - if ( - nonInlinedModuleThroughIdentifiers.has(variable.name) || - RESERVED_NAMES.has(variable.name) - ) { - usedInNonInlined.add(variable); - } - } - } - - for (const [m, moduleInfo] of inlinedModulesToInfo) { - const { ast, source: _source, usedInNonInlined } = moduleInfo; - const source = new ReplaceSource(_source); - if (usedInNonInlined.size === 0) { - renamedInlinedModules.set(m, source); - continue; - } - - const info = /** @type {Info} */ (inlinedModulesToInfo.get(m)); - const allUsedNames = new Set( - Array.from(info.through, v => v.identifier.name) - ); - - for (const variable of usedInNonInlined) { - allUsedNames.add(variable.name); - } - - for (const variable of info.variables) { - allUsedNames.add(variable.name); - const references = getAllReferences(variable); - const allIdentifiers = new Set( - references.map(r => r.identifier).concat(variable.identifiers) - ); - - const usedNamesInScopeInfo = new Map(); - const ignoredScopes = new Set(); - - const name = variable.name; - const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( - usedNamesInScopeInfo, - info.module.identifier(), - name - ); - - if (allUsedNames.has(name) || usedNames.has(name)) { - const references = getAllReferences(variable); - for (const ref of references) { - addScopeSymbols( - ref.from, - usedNames, - alreadyCheckedScopes, - ignoredScopes - ); - } - - const newName = findNewName( - variable.name, - allUsedNames, - usedNames, - m.readableIdentifier(runtimeTemplate.requestShortener) - ); - allUsedNames.add(newName); - for (const identifier of allIdentifiers) { - const r = /** @type {Range} */ (identifier.range); - const path = getPathInAst(ast, identifier); - if (path && path.length > 1) { - const maybeProperty = - path[1].type === "AssignmentPattern" && path[1].left === path[0] - ? path[2] - : path[1]; - if ( - maybeProperty.type === "Property" && - maybeProperty.shorthand - ) { - source.insert(r[1], `: ${newName}`); - continue; - } - } - source.replace(r[0], r[1] - 1, newName); - } - } else { - allUsedNames.add(name); - } - } - - renamedInlinedModules.set(m, source); - } - - return renamedInlinedModules; - } -} - -module.exports = JavascriptModulesPlugin; -module.exports.chunkHasJs = chunkHasJs; diff --git a/webpack-lib/lib/javascript/JavascriptParser.js b/webpack-lib/lib/javascript/JavascriptParser.js deleted file mode 100644 index 084292385ae..00000000000 --- a/webpack-lib/lib/javascript/JavascriptParser.js +++ /dev/null @@ -1,5007 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { Parser: AcornParser, tokTypes } = require("acorn"); -const { SyncBailHook, HookMap } = require("tapable"); -const vm = require("vm"); -const Parser = require("../Parser"); -const StackedMap = require("../util/StackedMap"); -const binarySearchBounds = require("../util/binarySearchBounds"); -const { - webpackCommentRegExp, - createMagicCommentContext -} = require("../util/magicComment"); -const memoize = require("../util/memoize"); -const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); - -/** @typedef {import("acorn").Options} AcornOptions */ -/** @typedef {import("estree").AssignmentExpression} AssignmentExpression */ -/** @typedef {import("estree").BinaryExpression} BinaryExpression */ -/** @typedef {import("estree").BlockStatement} BlockStatement */ -/** @typedef {import("estree").SequenceExpression} SequenceExpression */ -/** @typedef {import("estree").CallExpression} CallExpression */ -/** @typedef {import("estree").BaseCallExpression} BaseCallExpression */ -/** @typedef {import("estree").StaticBlock} StaticBlock */ -/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */ -/** @typedef {import("estree").ForStatement} ForStatement */ -/** @typedef {import("estree").SwitchStatement} SwitchStatement */ -/** @typedef {import("estree").ClassExpression} ClassExpression */ -/** @typedef {import("estree").Comment} Comment */ -/** @typedef {import("estree").ConditionalExpression} ConditionalExpression */ -/** @typedef {import("estree").Declaration} Declaration */ -/** @typedef {import("estree").PrivateIdentifier} PrivateIdentifier */ -/** @typedef {import("estree").PropertyDefinition} PropertyDefinition */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").Identifier} Identifier */ -/** @typedef {import("estree").VariableDeclaration} VariableDeclaration */ -/** @typedef {import("estree").IfStatement} IfStatement */ -/** @typedef {import("estree").LabeledStatement} LabeledStatement */ -/** @typedef {import("estree").Literal} Literal */ -/** @typedef {import("estree").LogicalExpression} LogicalExpression */ -/** @typedef {import("estree").ChainExpression} ChainExpression */ -/** @typedef {import("estree").MemberExpression} MemberExpression */ -/** @typedef {import("estree").YieldExpression} YieldExpression */ -/** @typedef {import("estree").MetaProperty} MetaProperty */ -/** @typedef {import("estree").Property} Property */ -/** @typedef {import("estree").AssignmentPattern} AssignmentPattern */ -/** @typedef {import("estree").ChainElement} ChainElement */ -/** @typedef {import("estree").Pattern} Pattern */ -/** @typedef {import("estree").UpdateExpression} UpdateExpression */ -/** @typedef {import("estree").ObjectExpression} ObjectExpression */ -/** @typedef {import("estree").UnaryExpression} UnaryExpression */ -/** @typedef {import("estree").ArrayExpression} ArrayExpression */ -/** @typedef {import("estree").ArrayPattern} ArrayPattern */ -/** @typedef {import("estree").AwaitExpression} AwaitExpression */ -/** @typedef {import("estree").ThisExpression} ThisExpression */ -/** @typedef {import("estree").RestElement} RestElement */ -/** @typedef {import("estree").ObjectPattern} ObjectPattern */ -/** @typedef {import("estree").SwitchCase} SwitchCase */ -/** @typedef {import("estree").CatchClause} CatchClause */ -/** @typedef {import("estree").VariableDeclarator} VariableDeclarator */ -/** @typedef {import("estree").ForInStatement} ForInStatement */ -/** @typedef {import("estree").ForOfStatement} ForOfStatement */ -/** @typedef {import("estree").ReturnStatement} ReturnStatement */ -/** @typedef {import("estree").WithStatement} WithStatement */ -/** @typedef {import("estree").ThrowStatement} ThrowStatement */ -/** @typedef {import("estree").MethodDefinition} MethodDefinition */ -/** @typedef {import("estree").NewExpression} NewExpression */ -/** @typedef {import("estree").SpreadElement} SpreadElement */ -/** @typedef {import("estree").FunctionExpression} FunctionExpression */ -/** @typedef {import("estree").WhileStatement} WhileStatement */ -/** @typedef {import("estree").ArrowFunctionExpression} ArrowFunctionExpression */ -/** @typedef {import("estree").ExpressionStatement} ExpressionStatement */ -/** @typedef {import("estree").FunctionDeclaration} FunctionDeclaration */ -/** @typedef {import("estree").DoWhileStatement} DoWhileStatement */ -/** @typedef {import("estree").TryStatement} TryStatement */ -/** @typedef {import("estree").Node} Node */ -/** @typedef {import("estree").Program} Program */ -/** @typedef {import("estree").Directive} Directive */ -/** @typedef {import("estree").Statement} Statement */ -/** @typedef {import("estree").ExportDefaultDeclaration} ExportDefaultDeclaration */ -/** @typedef {import("estree").Super} Super */ -/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpression */ -/** @typedef {import("estree").TemplateLiteral} TemplateLiteral */ -/** @typedef {import("estree").AssignmentProperty} AssignmentProperty */ -/** - * @template T - * @typedef {import("tapable").AsArray} AsArray - */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ -/** @typedef {{declaredScope: ScopeInfo, freeName: string | true | undefined, tagInfo: TagInfo | undefined}} VariableInfoInterface */ -/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[] }} GetInfoResult */ -/** @typedef {Statement | ModuleDeclaration | Expression} StatementPathItem */ -/** @typedef {function(string): void} OnIdentString */ -/** @typedef {function(string, Identifier): void} OnIdent */ -/** @typedef {StatementPathItem[]} StatementPath */ - -// TODO remove cast when @types/estree has been updated to import assertions -/** @typedef {import("estree").BaseNode & { type: "ImportAttribute", key: Identifier | Literal, value: Literal }} ImportAttribute */ -/** @typedef {import("estree").ImportDeclaration & { attributes?: Array }} ImportDeclaration */ -/** @typedef {import("estree").ExportNamedDeclaration & { attributes?: Array }} ExportNamedDeclaration */ -/** @typedef {import("estree").ExportAllDeclaration & { attributes?: Array }} ExportAllDeclaration */ -/** @typedef {import("estree").ImportExpression & { options?: Expression | null }} ImportExpression */ -/** @typedef {ImportDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration | ExportAllDeclaration} ModuleDeclaration */ - -/** @type {string[]} */ -const EMPTY_ARRAY = []; -const ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = 0b01; -const ALLOWED_MEMBER_TYPES_EXPRESSION = 0b10; -const ALLOWED_MEMBER_TYPES_ALL = 0b11; - -const LEGACY_ASSERT_ATTRIBUTES = Symbol("assert"); - -/** - * @param {any} Parser parser - * @returns {typeof AcornParser} extender acorn parser - */ -const importAssertions = Parser => - /** @type {typeof AcornParser} */ ( - /** @type {unknown} */ ( - class extends Parser { - parseWithClause() { - const nodes = []; - - const isAssertLegacy = this.value === "assert"; - - if (isAssertLegacy) { - if (!this.eat(tokTypes.name)) { - return nodes; - } - } else if (!this.eat(tokTypes._with)) { - return nodes; - } - - this.expect(tokTypes.braceL); - - const attributeKeys = {}; - let first = true; - - while (!this.eat(tokTypes.braceR)) { - if (!first) { - this.expect(tokTypes.comma); - if (this.afterTrailingComma(tokTypes.braceR)) { - break; - } - } else { - first = false; - } - - const attr = this.parseImportAttribute(); - const keyName = - attr.key.type === "Identifier" ? attr.key.name : attr.key.value; - - if (Object.prototype.hasOwnProperty.call(attributeKeys, keyName)) { - this.raiseRecoverable( - attr.key.start, - `Duplicate attribute key '${keyName}'` - ); - } - - attributeKeys[keyName] = true; - nodes.push(attr); - } - - if (isAssertLegacy) { - nodes[LEGACY_ASSERT_ATTRIBUTES] = true; - } - - return nodes; - } - } - ) - ); - -// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API -const parser = AcornParser.extend(importAssertions); - -/** @typedef {Record & { _isLegacyAssert?: boolean }} ImportAttributes */ - -/** - * @param {ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration | ImportExpression} node node with assertions - * @returns {ImportAttributes | undefined} import attributes - */ -const getImportAttributes = node => { - if (node.type === "ImportExpression") { - if ( - node.options && - node.options.type === "ObjectExpression" && - node.options.properties[0] && - node.options.properties[0].type === "Property" && - node.options.properties[0].key.type === "Identifier" && - (node.options.properties[0].key.name === "with" || - node.options.properties[0].key.name === "assert") && - node.options.properties[0].value.type === "ObjectExpression" && - node.options.properties[0].value.properties.length > 0 - ) { - const properties = - /** @type {Property[]} */ - (node.options.properties[0].value.properties); - const result = /** @type {ImportAttributes} */ ({}); - for (const property of properties) { - const key = - /** @type {string} */ - ( - property.key.type === "Identifier" - ? property.key.name - : /** @type {Literal} */ (property.key).value - ); - result[key] = - /** @type {string} */ - (/** @type {Literal} */ (property.value).value); - } - const key = - node.options.properties[0].key.type === "Identifier" - ? node.options.properties[0].key.name - : /** @type {Literal} */ (node.options.properties[0].key).value; - - if (key === "assert") { - result._isLegacyAssert = true; - } - - return result; - } - - return; - } - - if (node.attributes === undefined || node.attributes.length === 0) { - return; - } - - const result = /** @type {ImportAttributes} */ ({}); - - for (const attribute of node.attributes) { - const key = - /** @type {string} */ - ( - attribute.key.type === "Identifier" - ? attribute.key.name - : attribute.key.value - ); - - result[key] = /** @type {string} */ (attribute.value.value); - } - - if (node.attributes[LEGACY_ASSERT_ATTRIBUTES]) { - result._isLegacyAssert = true; - } - - return result; -}; - -class VariableInfo { - /** - * @param {ScopeInfo} declaredScope scope in which the variable is declared - * @param {string | true | undefined} freeName which free name the variable aliases, or true when none - * @param {TagInfo | undefined} tagInfo info about tags - */ - constructor(declaredScope, freeName, tagInfo) { - this.declaredScope = declaredScope; - this.freeName = freeName; - this.tagInfo = tagInfo; - } -} - -/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */ -/** @typedef {Literal | string | null | undefined} ImportSource */ -/** @typedef {Omit & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */ - -/** - * @typedef {object} TagInfo - * @property {any} tag - * @property {any} data - * @property {TagInfo | undefined} next - */ - -/** - * @typedef {object} ScopeInfo - * @property {StackedMap} definitions - * @property {boolean | "arrow"} topLevelScope - * @property {boolean | string} inShorthand - * @property {boolean} inTaggedTemplateTag - * @property {boolean} inTry - * @property {boolean} isStrict - * @property {boolean} isAsmJs - */ - -/** @typedef {[number, number]} Range */ - -/** - * @typedef {object} DestructuringAssignmentProperty - * @property {string} id - * @property {Range | undefined=} range - * @property {boolean | string} shorthand - */ - -/** - * Helper function for joining two ranges into a single range. This is useful - * when working with AST nodes, as it allows you to combine the ranges of child nodes - * to create the range of the _parent node_. - * @param {[number, number]} startRange start range to join - * @param {[number, number]} endRange end range to join - * @returns {[number, number]} joined range - * @example - * ```js - * const startRange = [0, 5]; - * const endRange = [10, 15]; - * const joinedRange = joinRanges(startRange, endRange); - * console.log(joinedRange); // [0, 15] - * ``` - */ -const joinRanges = (startRange, endRange) => { - if (!endRange) return startRange; - if (!startRange) return endRange; - return [startRange[0], endRange[1]]; -}; - -/** - * Helper function used to generate a string representation of a - * [member expression](https://github.com/estree/estree/blob/master/es5.md#memberexpression). - * @param {string} object object to name - * @param {string[]} membersReversed reversed list of members - * @returns {string} member expression as a string - * @example - * ```js - * const membersReversed = ["property1", "property2", "property3"]; // Members parsed from the AST - * const name = objectAndMembersToName("myObject", membersReversed); - * - * console.log(name); // "myObject.property1.property2.property3" - * ``` - */ -const objectAndMembersToName = (object, membersReversed) => { - let name = object; - for (let i = membersReversed.length - 1; i >= 0; i--) { - name = `${name}.${membersReversed[i]}`; - } - return name; -}; - -/** - * Grabs the name of a given expression and returns it as a string or undefined. Has particular - * handling for [Identifiers](https://github.com/estree/estree/blob/master/es5.md#identifier), - * [ThisExpressions](https://github.com/estree/estree/blob/master/es5.md#identifier), and - * [MetaProperties](https://github.com/estree/estree/blob/master/es2015.md#metaproperty) which is - * specifically for handling the `new.target` meta property. - * @param {Expression | SpreadElement | Super} expression expression - * @returns {string | "this" | undefined} name or variable info - */ -const getRootName = expression => { - switch (expression.type) { - case "Identifier": - return expression.name; - case "ThisExpression": - return "this"; - case "MetaProperty": - return `${expression.meta.name}.${expression.property.name}`; - default: - return undefined; - } -}; - -/** @type {AcornOptions} */ -const defaultParserOptions = { - ranges: true, - locations: true, - ecmaVersion: "latest", - sourceType: "module", - // https://github.com/tc39/proposal-hashbang - allowHashBang: true, - onComment: undefined -}; - -const EMPTY_COMMENT_OPTIONS = { - options: null, - errors: null -}; - -class JavascriptParser extends Parser { - /** - * @param {"module" | "script" | "auto"} sourceType default source type - */ - constructor(sourceType = "auto") { - super(); - this.hooks = Object.freeze({ - /** @type {HookMap>} */ - evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {HookMap>} */ - evaluate: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {HookMap>} */ - evaluateIdentifier: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {HookMap>} */ - evaluateDefinedIdentifier: new HookMap( - () => new SyncBailHook(["expression"]) - ), - /** @type {HookMap>} */ - evaluateNewExpression: new HookMap( - () => new SyncBailHook(["expression"]) - ), - /** @type {HookMap>} */ - evaluateCallExpression: new HookMap( - () => new SyncBailHook(["expression"]) - ), - /** @type {HookMap>} */ - evaluateCallExpressionMember: new HookMap( - () => new SyncBailHook(["expression", "param"]) - ), - /** @type {HookMap>} */ - isPure: new HookMap( - () => new SyncBailHook(["expression", "commentsStartPosition"]) - ), - /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ - preStatement: new SyncBailHook(["statement"]), - - /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ - blockPreStatement: new SyncBailHook(["declaration"]), - /** @type {SyncBailHook<[Statement | ModuleDeclaration], boolean | void>} */ - statement: new SyncBailHook(["statement"]), - /** @type {SyncBailHook<[IfStatement], boolean | void>} */ - statementIf: new SyncBailHook(["statement"]), - /** @type {SyncBailHook<[Expression, ClassExpression | ClassDeclaration], boolean | void>} */ - classExtendsExpression: new SyncBailHook([ - "expression", - "classDefinition" - ]), - /** @type {SyncBailHook<[MethodDefinition | PropertyDefinition | StaticBlock, ClassExpression | ClassDeclaration], boolean | void>} */ - classBodyElement: new SyncBailHook(["element", "classDefinition"]), - /** @type {SyncBailHook<[Expression, MethodDefinition | PropertyDefinition, ClassExpression | ClassDeclaration], boolean | void>} */ - classBodyValue: new SyncBailHook([ - "expression", - "element", - "classDefinition" - ]), - /** @type {HookMap>} */ - label: new HookMap(() => new SyncBailHook(["statement"])), - /** @type {SyncBailHook<[ImportDeclaration, ImportSource], boolean | void>} */ - import: new SyncBailHook(["statement", "source"]), - /** @type {SyncBailHook<[ImportDeclaration, ImportSource, string | null, string], boolean | void>} */ - importSpecifier: new SyncBailHook([ - "statement", - "source", - "exportName", - "identifierName" - ]), - /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration], boolean | void>} */ - export: new SyncBailHook(["statement"]), - /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource], boolean | void>} */ - exportImport: new SyncBailHook(["statement", "source"]), - /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, Declaration], boolean | void>} */ - exportDeclaration: new SyncBailHook(["statement", "declaration"]), - /** @type {SyncBailHook<[ExportDefaultDeclaration, FunctionDeclaration | ClassDeclaration], boolean | void>} */ - exportExpression: new SyncBailHook(["statement", "declaration"]), - /** @type {SyncBailHook<[ExportDefaultDeclaration | ExportNamedDeclaration | ExportAllDeclaration, string, string, number | undefined], boolean | void>} */ - exportSpecifier: new SyncBailHook([ - "statement", - "identifierName", - "exportName", - "index" - ]), - /** @type {SyncBailHook<[ExportNamedDeclaration | ExportAllDeclaration, ImportSource, string | null, string | null, number | undefined], boolean | void>} */ - exportImportSpecifier: new SyncBailHook([ - "statement", - "source", - "identifierName", - "exportName", - "index" - ]), - /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */ - preDeclarator: new SyncBailHook(["declarator", "statement"]), - /** @type {SyncBailHook<[VariableDeclarator, Statement], boolean | void>} */ - declarator: new SyncBailHook(["declarator", "statement"]), - /** @type {HookMap>} */ - varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])), - /** @type {HookMap>} */ - varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])), - /** @type {HookMap>} */ - varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])), - /** @type {HookMap>} */ - varDeclarationVar: new HookMap(() => new SyncBailHook(["declaration"])), - /** @type {HookMap>} */ - pattern: new HookMap(() => new SyncBailHook(["pattern"])), - /** @type {HookMap>} */ - canRename: new HookMap(() => new SyncBailHook(["initExpression"])), - /** @type {HookMap>} */ - rename: new HookMap(() => new SyncBailHook(["initExpression"])), - /** @type {HookMap>} */ - assign: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {HookMap>} */ - assignMemberChain: new HookMap( - () => new SyncBailHook(["expression", "members"]) - ), - /** @type {HookMap>} */ - typeof: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {SyncBailHook<[ImportExpression], boolean | void>} */ - importCall: new SyncBailHook(["expression"]), - /** @type {SyncBailHook<[Expression | ForOfStatement], boolean | void>} */ - topLevelAwait: new SyncBailHook(["expression"]), - /** @type {HookMap>} */ - call: new HookMap(() => new SyncBailHook(["expression"])), - /** Something like "a.b()" */ - /** @type {HookMap>} */ - callMemberChain: new HookMap( - () => - new SyncBailHook([ - "expression", - "members", - "membersOptionals", - "memberRanges" - ]) - ), - /** Something like "a.b().c.d" */ - /** @type {HookMap>} */ - memberChainOfCallMemberChain: new HookMap( - () => - new SyncBailHook([ - "expression", - "calleeMembers", - "callExpression", - "members", - "memberRanges" - ]) - ), - /** Something like "a.b().c.d()"" */ - /** @type {HookMap>} */ - callMemberChainOfCallMemberChain: new HookMap( - () => - new SyncBailHook([ - "expression", - "calleeMembers", - "innerCallExpression", - "members", - "memberRanges" - ]) - ), - /** @type {SyncBailHook<[ChainExpression], boolean | void>} */ - optionalChaining: new SyncBailHook(["optionalChaining"]), - /** @type {HookMap>} */ - new: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {SyncBailHook<[BinaryExpression], boolean | void>} */ - binaryExpression: new SyncBailHook(["binaryExpression"]), - /** @type {HookMap>} */ - expression: new HookMap(() => new SyncBailHook(["expression"])), - /** @type {HookMap>} */ - expressionMemberChain: new HookMap( - () => - new SyncBailHook([ - "expression", - "members", - "membersOptionals", - "memberRanges" - ]) - ), - /** @type {HookMap>} */ - unhandledExpressionMemberChain: new HookMap( - () => new SyncBailHook(["expression", "members"]) - ), - /** @type {SyncBailHook<[ConditionalExpression], boolean | void>} */ - expressionConditionalOperator: new SyncBailHook(["expression"]), - /** @type {SyncBailHook<[LogicalExpression], boolean | void>} */ - expressionLogicalOperator: new SyncBailHook(["expression"]), - /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ - program: new SyncBailHook(["ast", "comments"]), - /** @type {SyncBailHook<[Program, Comment[]], boolean | void>} */ - finish: new SyncBailHook(["ast", "comments"]) - }); - this.sourceType = sourceType; - /** @type {ScopeInfo} */ - this.scope = undefined; - /** @type {ParserState} */ - this.state = undefined; - /** @type {Comment[] | undefined} */ - this.comments = undefined; - /** @type {Set | undefined} */ - this.semicolons = undefined; - /** @type {StatementPath | undefined} */ - this.statementPath = undefined; - /** @type {Statement | ModuleDeclaration | Expression | undefined} */ - this.prevStatement = undefined; - /** @type {WeakMap> | undefined} */ - this.destructuringAssignmentProperties = undefined; - this.currentTagData = undefined; - this.magicCommentContext = createMagicCommentContext(); - this._initializeEvaluating(); - } - - _initializeEvaluating() { - this.hooks.evaluate.for("Literal").tap("JavascriptParser", _expr => { - const expr = /** @type {Literal} */ (_expr); - - switch (typeof expr.value) { - case "number": - return new BasicEvaluatedExpression() - .setNumber(expr.value) - .setRange(/** @type {Range} */ (expr.range)); - case "bigint": - return new BasicEvaluatedExpression() - .setBigInt(expr.value) - .setRange(/** @type {Range} */ (expr.range)); - case "string": - return new BasicEvaluatedExpression() - .setString(expr.value) - .setRange(/** @type {Range} */ (expr.range)); - case "boolean": - return new BasicEvaluatedExpression() - .setBoolean(expr.value) - .setRange(/** @type {Range} */ (expr.range)); - } - if (expr.value === null) { - return new BasicEvaluatedExpression() - .setNull() - .setRange(/** @type {Range} */ (expr.range)); - } - if (expr.value instanceof RegExp) { - return new BasicEvaluatedExpression() - .setRegExp(expr.value) - .setRange(/** @type {Range} */ (expr.range)); - } - }); - this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => { - const expr = /** @type {NewExpression} */ (_expr); - const callee = expr.callee; - if (callee.type !== "Identifier") return; - if (callee.name !== "RegExp") { - return this.callHooksForName( - this.hooks.evaluateNewExpression, - callee.name, - expr - ); - } else if ( - expr.arguments.length > 2 || - this.getVariableInfo("RegExp") !== "RegExp" - ) - return; - - let regExp; - const arg1 = expr.arguments[0]; - - if (arg1) { - if (arg1.type === "SpreadElement") return; - - const evaluatedRegExp = this.evaluateExpression(arg1); - - if (!evaluatedRegExp) return; - - regExp = evaluatedRegExp.asString(); - - if (!regExp) return; - } else { - return ( - new BasicEvaluatedExpression() - // eslint-disable-next-line prefer-regex-literals - .setRegExp(new RegExp("")) - .setRange(/** @type {Range} */ (expr.range)) - ); - } - - let flags; - const arg2 = expr.arguments[1]; - - if (arg2) { - if (arg2.type === "SpreadElement") return; - - const evaluatedFlags = this.evaluateExpression(arg2); - - if (!evaluatedFlags) return; - - if (!evaluatedFlags.isUndefined()) { - flags = evaluatedFlags.asString(); - - if ( - flags === undefined || - !BasicEvaluatedExpression.isValidRegExpFlags(flags) - ) - return; - } - } - - return new BasicEvaluatedExpression() - .setRegExp(flags ? new RegExp(regExp, flags) : new RegExp(regExp)) - .setRange(/** @type {Range} */ (expr.range)); - }); - this.hooks.evaluate - .for("LogicalExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {LogicalExpression} */ (_expr); - - const left = this.evaluateExpression(expr.left); - let returnRight = false; - /** @type {boolean | undefined} */ - let allowedRight; - if (expr.operator === "&&") { - const leftAsBool = left.asBool(); - if (leftAsBool === false) - return left.setRange(/** @type {Range} */ (expr.range)); - returnRight = leftAsBool === true; - allowedRight = false; - } else if (expr.operator === "||") { - const leftAsBool = left.asBool(); - if (leftAsBool === true) - return left.setRange(/** @type {Range} */ (expr.range)); - returnRight = leftAsBool === false; - allowedRight = true; - } else if (expr.operator === "??") { - const leftAsNullish = left.asNullish(); - if (leftAsNullish === false) - return left.setRange(/** @type {Range} */ (expr.range)); - if (leftAsNullish !== true) return; - returnRight = true; - } else return; - const right = this.evaluateExpression(expr.right); - if (returnRight) { - if (left.couldHaveSideEffects()) right.setSideEffects(); - return right.setRange(/** @type {Range} */ (expr.range)); - } - - const asBool = right.asBool(); - - if (allowedRight === true && asBool === true) { - return new BasicEvaluatedExpression() - .setRange(/** @type {Range} */ (expr.range)) - .setTruthy(); - } else if (allowedRight === false && asBool === false) { - return new BasicEvaluatedExpression() - .setRange(/** @type {Range} */ (expr.range)) - .setFalsy(); - } - }); - - /** - * In simple logical cases, we can use valueAsExpression to assist us in evaluating the expression on - * either side of a [BinaryExpression](https://github.com/estree/estree/blob/master/es5.md#binaryexpression). - * This supports scenarios in webpack like conditionally `import()`'ing modules based on some simple evaluation: - * - * ```js - * if (1 === 3) { - * import("./moduleA"); // webpack will auto evaluate this and not import the modules - * } - * ``` - * - * Additional scenarios include evaluation of strings inside of dynamic import statements: - * - * ```js - * const foo = "foo"; - * const bar = "bar"; - * - * import("./" + foo + bar); // webpack will auto evaluate this into import("./foobar") - * ``` - * @param {boolean | number | bigint | string} value the value to convert to an expression - * @param {BinaryExpression | UnaryExpression} expr the expression being evaluated - * @param {boolean} sideEffects whether the expression has side effects - * @returns {BasicEvaluatedExpression | undefined} the evaluated expression - * @example - * - * ```js - * const binaryExpr = new BinaryExpression("+", - * { type: "Literal", value: 2 }, - * { type: "Literal", value: 3 } - * ); - * - * const leftValue = 2; - * const rightValue = 3; - * - * const leftExpr = valueAsExpression(leftValue, binaryExpr.left, false); - * const rightExpr = valueAsExpression(rightValue, binaryExpr.right, false); - * const result = new BasicEvaluatedExpression() - * .setNumber(leftExpr.number + rightExpr.number) - * .setRange(binaryExpr.range); - * - * console.log(result.number); // Output: 5 - * ``` - */ - const valueAsExpression = (value, expr, sideEffects) => { - switch (typeof value) { - case "boolean": - return new BasicEvaluatedExpression() - .setBoolean(value) - .setSideEffects(sideEffects) - .setRange(/** @type {Range} */ (expr.range)); - case "number": - return new BasicEvaluatedExpression() - .setNumber(value) - .setSideEffects(sideEffects) - .setRange(/** @type {Range} */ (expr.range)); - case "bigint": - return new BasicEvaluatedExpression() - .setBigInt(value) - .setSideEffects(sideEffects) - .setRange(/** @type {Range} */ (expr.range)); - case "string": - return new BasicEvaluatedExpression() - .setString(value) - .setSideEffects(sideEffects) - .setRange(/** @type {Range} */ (expr.range)); - } - }; - - this.hooks.evaluate - .for("BinaryExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {BinaryExpression} */ (_expr); - - /** - * Evaluates a binary expression if and only if it is a const operation (e.g. 1 + 2, "a" + "b", etc.). - * @template T - * @param {(leftOperand: T, rightOperand: T) => boolean | number | bigint | string} operandHandler the handler for the operation (e.g. (a, b) => a + b) - * @returns {BasicEvaluatedExpression | undefined} the evaluated expression - */ - const handleConstOperation = operandHandler => { - const left = this.evaluateExpression(expr.left); - if (!left.isCompileTimeValue()) return; - - const right = this.evaluateExpression(expr.right); - if (!right.isCompileTimeValue()) return; - - const result = operandHandler( - left.asCompileTimeValue(), - right.asCompileTimeValue() - ); - return valueAsExpression( - result, - expr, - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - }; - - /** - * Helper function to determine if two booleans are always different. This is used in `handleStrictEqualityComparison` - * to determine if an expressions boolean or nullish conversion is equal or not. - * @param {boolean} a first boolean to compare - * @param {boolean} b second boolean to compare - * @returns {boolean} true if the two booleans are always different, false otherwise - */ - const isAlwaysDifferent = (a, b) => - (a === true && b === false) || (a === false && b === true); - - /** - * @param {BasicEvaluatedExpression} left left - * @param {BasicEvaluatedExpression} right right - * @param {BasicEvaluatedExpression} res res - * @param {boolean} eql true for "===" and false for "!==" - * @returns {BasicEvaluatedExpression | undefined} result - */ - const handleTemplateStringCompare = (left, right, res, eql) => { - /** - * @param {BasicEvaluatedExpression[]} parts parts - * @returns {string} value - */ - const getPrefix = parts => { - let value = ""; - for (const p of parts) { - const v = p.asString(); - if (v !== undefined) value += v; - else break; - } - return value; - }; - /** - * @param {BasicEvaluatedExpression[]} parts parts - * @returns {string} value - */ - const getSuffix = parts => { - let value = ""; - for (let i = parts.length - 1; i >= 0; i--) { - const v = parts[i].asString(); - if (v !== undefined) value = v + value; - else break; - } - return value; - }; - const leftPrefix = getPrefix( - /** @type {BasicEvaluatedExpression[]} */ (left.parts) - ); - const rightPrefix = getPrefix( - /** @type {BasicEvaluatedExpression[]} */ (right.parts) - ); - const leftSuffix = getSuffix( - /** @type {BasicEvaluatedExpression[]} */ (left.parts) - ); - const rightSuffix = getSuffix( - /** @type {BasicEvaluatedExpression[]} */ (right.parts) - ); - const lenPrefix = Math.min(leftPrefix.length, rightPrefix.length); - const lenSuffix = Math.min(leftSuffix.length, rightSuffix.length); - const prefixMismatch = - lenPrefix > 0 && - leftPrefix.slice(0, lenPrefix) !== rightPrefix.slice(0, lenPrefix); - const suffixMismatch = - lenSuffix > 0 && - leftSuffix.slice(-lenSuffix) !== rightSuffix.slice(-lenSuffix); - if (prefixMismatch || suffixMismatch) { - return res - .setBoolean(!eql) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - }; - - /** - * Helper function to handle BinaryExpressions using strict equality comparisons (e.g. "===" and "!=="). - * @param {boolean} eql true for "===" and false for "!==" - * @returns {BasicEvaluatedExpression | undefined} the evaluated expression - */ - const handleStrictEqualityComparison = eql => { - const left = this.evaluateExpression(expr.left); - const right = this.evaluateExpression(expr.right); - const res = new BasicEvaluatedExpression(); - res.setRange(/** @type {Range} */ (expr.range)); - - const leftConst = left.isCompileTimeValue(); - const rightConst = right.isCompileTimeValue(); - - if (leftConst && rightConst) { - return res - .setBoolean( - eql === - (left.asCompileTimeValue() === right.asCompileTimeValue()) - ) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - - if (left.isArray() && right.isArray()) { - return res - .setBoolean(!eql) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - if (left.isTemplateString() && right.isTemplateString()) { - return handleTemplateStringCompare(left, right, res, eql); - } - - const leftPrimitive = left.isPrimitiveType(); - const rightPrimitive = right.isPrimitiveType(); - - if ( - // Primitive !== Object or - // compile-time object types are never equal to something at runtime - (leftPrimitive === false && - (leftConst || rightPrimitive === true)) || - (rightPrimitive === false && - (rightConst || leftPrimitive === true)) || - // Different nullish or boolish status also means not equal - isAlwaysDifferent( - /** @type {boolean} */ (left.asBool()), - /** @type {boolean} */ (right.asBool()) - ) || - isAlwaysDifferent( - /** @type {boolean} */ (left.asNullish()), - /** @type {boolean} */ (right.asNullish()) - ) - ) { - return res - .setBoolean(!eql) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - }; - - /** - * Helper function to handle BinaryExpressions using abstract equality comparisons (e.g. "==" and "!="). - * @param {boolean} eql true for "==" and false for "!=" - * @returns {BasicEvaluatedExpression | undefined} the evaluated expression - */ - const handleAbstractEqualityComparison = eql => { - const left = this.evaluateExpression(expr.left); - const right = this.evaluateExpression(expr.right); - const res = new BasicEvaluatedExpression(); - res.setRange(/** @type {Range} */ (expr.range)); - - const leftConst = left.isCompileTimeValue(); - const rightConst = right.isCompileTimeValue(); - - if (leftConst && rightConst) { - return res - .setBoolean( - eql === - // eslint-disable-next-line eqeqeq - (left.asCompileTimeValue() == right.asCompileTimeValue()) - ) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - - if (left.isArray() && right.isArray()) { - return res - .setBoolean(!eql) - .setSideEffects( - left.couldHaveSideEffects() || right.couldHaveSideEffects() - ); - } - if (left.isTemplateString() && right.isTemplateString()) { - return handleTemplateStringCompare(left, right, res, eql); - } - }; - - if (expr.operator === "+") { - const left = this.evaluateExpression(expr.left); - const right = this.evaluateExpression(expr.right); - const res = new BasicEvaluatedExpression(); - if (left.isString()) { - if (right.isString()) { - res.setString( - /** @type {string} */ (left.string) + - /** @type {string} */ (right.string) - ); - } else if (right.isNumber()) { - res.setString(/** @type {string} */ (left.string) + right.number); - } else if ( - right.isWrapped() && - right.prefix && - right.prefix.isString() - ) { - // "left" + ("prefix" + inner + "postfix") - // => ("leftPrefix" + inner + "postfix") - res.setWrapped( - new BasicEvaluatedExpression() - .setString( - /** @type {string} */ (left.string) + - /** @type {string} */ (right.prefix.string) - ) - .setRange( - joinRanges( - /** @type {Range} */ (left.range), - /** @type {Range} */ (right.prefix.range) - ) - ), - right.postfix, - right.wrappedInnerExpressions - ); - } else if (right.isWrapped()) { - // "left" + ([null] + inner + "postfix") - // => ("left" + inner + "postfix") - res.setWrapped( - left, - right.postfix, - right.wrappedInnerExpressions - ); - } else { - // "left" + expr - // => ("left" + expr + "") - res.setWrapped(left, null, [right]); - } - } else if (left.isNumber()) { - if (right.isString()) { - res.setString(left.number + /** @type {string} */ (right.string)); - } else if (right.isNumber()) { - res.setNumber( - /** @type {number} */ (left.number) + - /** @type {number} */ (right.number) - ); - } else { - return; - } - } else if (left.isBigInt()) { - if (right.isBigInt()) { - res.setBigInt( - /** @type {bigint} */ (left.bigint) + - /** @type {bigint} */ (right.bigint) - ); - } - } else if (left.isWrapped()) { - if (left.postfix && left.postfix.isString() && right.isString()) { - // ("prefix" + inner + "postfix") + "right" - // => ("prefix" + inner + "postfixRight") - res.setWrapped( - left.prefix, - new BasicEvaluatedExpression() - .setString( - /** @type {string} */ (left.postfix.string) + - /** @type {string} */ (right.string) - ) - .setRange( - joinRanges( - /** @type {Range} */ (left.postfix.range), - /** @type {Range} */ (right.range) - ) - ), - left.wrappedInnerExpressions - ); - } else if ( - left.postfix && - left.postfix.isString() && - right.isNumber() - ) { - // ("prefix" + inner + "postfix") + 123 - // => ("prefix" + inner + "postfix123") - res.setWrapped( - left.prefix, - new BasicEvaluatedExpression() - .setString( - /** @type {string} */ (left.postfix.string) + - /** @type {number} */ (right.number) - ) - .setRange( - joinRanges( - /** @type {Range} */ (left.postfix.range), - /** @type {Range} */ (right.range) - ) - ), - left.wrappedInnerExpressions - ); - } else if (right.isString()) { - // ("prefix" + inner + [null]) + "right" - // => ("prefix" + inner + "right") - res.setWrapped(left.prefix, right, left.wrappedInnerExpressions); - } else if (right.isNumber()) { - // ("prefix" + inner + [null]) + 123 - // => ("prefix" + inner + "123") - res.setWrapped( - left.prefix, - new BasicEvaluatedExpression() - .setString(String(right.number)) - .setRange(/** @type {Range} */ (right.range)), - left.wrappedInnerExpressions - ); - } else if (right.isWrapped()) { - // ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2") - // ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2") - res.setWrapped( - left.prefix, - right.postfix, - left.wrappedInnerExpressions && - right.wrappedInnerExpressions && - left.wrappedInnerExpressions - .concat(left.postfix ? [left.postfix] : []) - .concat(right.prefix ? [right.prefix] : []) - .concat(right.wrappedInnerExpressions) - ); - } else { - // ("prefix" + inner + postfix) + expr - // => ("prefix" + inner + postfix + expr + [null]) - res.setWrapped( - left.prefix, - null, - left.wrappedInnerExpressions && - left.wrappedInnerExpressions.concat( - left.postfix ? [left.postfix, right] : [right] - ) - ); - } - } else if (right.isString()) { - // left + "right" - // => ([null] + left + "right") - res.setWrapped(null, right, [left]); - } else if (right.isWrapped()) { - // left + (prefix + inner + "postfix") - // => ([null] + left + prefix + inner + "postfix") - res.setWrapped( - null, - right.postfix, - right.wrappedInnerExpressions && - (right.prefix ? [left, right.prefix] : [left]).concat( - right.wrappedInnerExpressions - ) - ); - } else { - return; - } - if (left.couldHaveSideEffects() || right.couldHaveSideEffects()) - res.setSideEffects(); - res.setRange(/** @type {Range} */ (expr.range)); - return res; - } else if (expr.operator === "-") { - return handleConstOperation((l, r) => l - r); - } else if (expr.operator === "*") { - return handleConstOperation((l, r) => l * r); - } else if (expr.operator === "/") { - return handleConstOperation((l, r) => l / r); - } else if (expr.operator === "**") { - return handleConstOperation((l, r) => l ** r); - } else if (expr.operator === "===") { - return handleStrictEqualityComparison(true); - } else if (expr.operator === "==") { - return handleAbstractEqualityComparison(true); - } else if (expr.operator === "!==") { - return handleStrictEqualityComparison(false); - } else if (expr.operator === "!=") { - return handleAbstractEqualityComparison(false); - } else if (expr.operator === "&") { - return handleConstOperation((l, r) => l & r); - } else if (expr.operator === "|") { - return handleConstOperation((l, r) => l | r); - } else if (expr.operator === "^") { - return handleConstOperation((l, r) => l ^ r); - } else if (expr.operator === ">>>") { - return handleConstOperation((l, r) => l >>> r); - } else if (expr.operator === ">>") { - return handleConstOperation((l, r) => l >> r); - } else if (expr.operator === "<<") { - return handleConstOperation((l, r) => l << r); - } else if (expr.operator === "<") { - return handleConstOperation((l, r) => l < r); - } else if (expr.operator === ">") { - return handleConstOperation((l, r) => l > r); - } else if (expr.operator === "<=") { - return handleConstOperation((l, r) => l <= r); - } else if (expr.operator === ">=") { - return handleConstOperation((l, r) => l >= r); - } - }); - this.hooks.evaluate - .for("UnaryExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {UnaryExpression} */ (_expr); - - /** - * Evaluates a UnaryExpression if and only if it is a basic const operator (e.g. +a, -a, ~a). - * @template T - * @param {(operand: T) => boolean | number | bigint | string} operandHandler handler for the operand - * @returns {BasicEvaluatedExpression | undefined} evaluated expression - */ - const handleConstOperation = operandHandler => { - const argument = this.evaluateExpression(expr.argument); - if (!argument.isCompileTimeValue()) return; - const result = operandHandler(argument.asCompileTimeValue()); - return valueAsExpression( - result, - expr, - argument.couldHaveSideEffects() - ); - }; - - if (expr.operator === "typeof") { - switch (expr.argument.type) { - case "Identifier": { - const res = this.callHooksForName( - this.hooks.evaluateTypeof, - expr.argument.name, - expr - ); - if (res !== undefined) return res; - break; - } - case "MetaProperty": { - const res = this.callHooksForName( - this.hooks.evaluateTypeof, - /** @type {string} */ (getRootName(expr.argument)), - expr - ); - if (res !== undefined) return res; - break; - } - case "MemberExpression": { - const res = this.callHooksForExpression( - this.hooks.evaluateTypeof, - expr.argument, - expr - ); - if (res !== undefined) return res; - break; - } - case "ChainExpression": { - const res = this.callHooksForExpression( - this.hooks.evaluateTypeof, - expr.argument.expression, - expr - ); - if (res !== undefined) return res; - break; - } - case "FunctionExpression": { - return new BasicEvaluatedExpression() - .setString("function") - .setRange(/** @type {Range} */ (expr.range)); - } - } - const arg = this.evaluateExpression(expr.argument); - if (arg.isUnknown()) return; - if (arg.isString()) { - return new BasicEvaluatedExpression() - .setString("string") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isWrapped()) { - return new BasicEvaluatedExpression() - .setString("string") - .setSideEffects() - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isUndefined()) { - return new BasicEvaluatedExpression() - .setString("undefined") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isNumber()) { - return new BasicEvaluatedExpression() - .setString("number") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isBigInt()) { - return new BasicEvaluatedExpression() - .setString("bigint") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isBoolean()) { - return new BasicEvaluatedExpression() - .setString("boolean") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isConstArray() || arg.isRegExp() || arg.isNull()) { - return new BasicEvaluatedExpression() - .setString("object") - .setRange(/** @type {Range} */ (expr.range)); - } - if (arg.isArray()) { - return new BasicEvaluatedExpression() - .setString("object") - .setSideEffects(arg.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - } - } else if (expr.operator === "!") { - const argument = this.evaluateExpression(expr.argument); - const bool = argument.asBool(); - if (typeof bool !== "boolean") return; - return new BasicEvaluatedExpression() - .setBoolean(!bool) - .setSideEffects(argument.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - } else if (expr.operator === "~") { - return handleConstOperation(v => ~v); - } else if (expr.operator === "+") { - // eslint-disable-next-line no-implicit-coercion - return handleConstOperation(v => +v); - } else if (expr.operator === "-") { - return handleConstOperation(v => -v); - } - }); - this.hooks.evaluateTypeof - .for("undefined") - .tap("JavascriptParser", expr => - new BasicEvaluatedExpression() - .setString("undefined") - .setRange(/** @type {Range} */ (expr.range)) - ); - this.hooks.evaluate.for("Identifier").tap("JavascriptParser", expr => { - if (/** @type {Identifier} */ (expr).name === "undefined") { - return new BasicEvaluatedExpression() - .setUndefined() - .setRange(/** @type {Range} */ (expr.range)); - } - }); - /** - * @param {"Identifier" | "ThisExpression" | "MemberExpression"} exprType expression type name - * @param {function(Expression | SpreadElement): GetInfoResult | undefined} getInfo get info - * @returns {void} - */ - const tapEvaluateWithVariableInfo = (exprType, getInfo) => { - /** @type {Expression | undefined} */ - let cachedExpression; - /** @type {GetInfoResult | undefined} */ - let cachedInfo; - this.hooks.evaluate.for(exprType).tap("JavascriptParser", expr => { - const expression = - /** @type {Identifier | ThisExpression | MemberExpression} */ (expr); - - const info = getInfo(expression); - if (info !== undefined) { - return this.callHooksForInfoWithFallback( - this.hooks.evaluateIdentifier, - info.name, - name => { - cachedExpression = expression; - cachedInfo = info; - }, - name => { - const hook = this.hooks.evaluateDefinedIdentifier.get(name); - if (hook !== undefined) { - return hook.call(expression); - } - }, - expression - ); - } - }); - this.hooks.evaluate - .for(exprType) - .tap({ name: "JavascriptParser", stage: 100 }, expr => { - const expression = - /** @type {Identifier | ThisExpression | MemberExpression} */ - (expr); - const info = - cachedExpression === expression ? cachedInfo : getInfo(expression); - if (info !== undefined) { - return new BasicEvaluatedExpression() - .setIdentifier( - info.name, - info.rootInfo, - info.getMembers, - info.getMembersOptionals, - info.getMemberRanges - ) - .setRange(/** @type {Range} */ (expression.range)); - } - }); - this.hooks.finish.tap("JavascriptParser", () => { - // Cleanup for GC - cachedExpression = cachedInfo = undefined; - }); - }; - tapEvaluateWithVariableInfo("Identifier", expr => { - const info = this.getVariableInfo(/** @type {Identifier} */ (expr).name); - if ( - typeof info === "string" || - (info instanceof VariableInfo && typeof info.freeName === "string") - ) { - return { - name: info, - rootInfo: info, - getMembers: () => [], - getMembersOptionals: () => [], - getMemberRanges: () => [] - }; - } - }); - tapEvaluateWithVariableInfo("ThisExpression", expr => { - const info = this.getVariableInfo("this"); - if ( - typeof info === "string" || - (info instanceof VariableInfo && typeof info.freeName === "string") - ) { - return { - name: info, - rootInfo: info, - getMembers: () => [], - getMembersOptionals: () => [], - getMemberRanges: () => [] - }; - } - }); - this.hooks.evaluate.for("MetaProperty").tap("JavascriptParser", expr => { - const metaProperty = /** @type {MetaProperty} */ (expr); - - return this.callHooksForName( - this.hooks.evaluateIdentifier, - /** @type {string} */ (getRootName(metaProperty)), - metaProperty - ); - }); - tapEvaluateWithVariableInfo("MemberExpression", expr => - this.getMemberExpressionInfo( - /** @type {MemberExpression} */ (expr), - ALLOWED_MEMBER_TYPES_EXPRESSION - ) - ); - - this.hooks.evaluate.for("CallExpression").tap("JavascriptParser", _expr => { - const expr = /** @type {CallExpression} */ (_expr); - if ( - expr.callee.type === "MemberExpression" && - expr.callee.property.type === - (expr.callee.computed ? "Literal" : "Identifier") - ) { - // type Super also possible here - const param = this.evaluateExpression( - /** @type {Expression} */ (expr.callee.object) - ); - const property = - expr.callee.property.type === "Literal" - ? `${expr.callee.property.value}` - : expr.callee.property.name; - const hook = this.hooks.evaluateCallExpressionMember.get(property); - if (hook !== undefined) { - return hook.call(expr, param); - } - } else if (expr.callee.type === "Identifier") { - return this.callHooksForName( - this.hooks.evaluateCallExpression, - expr.callee.name, - expr - ); - } - }); - this.hooks.evaluateCallExpressionMember - .for("indexOf") - .tap("JavascriptParser", (expr, param) => { - if (!param.isString()) return; - if (expr.arguments.length === 0) return; - const [arg1, arg2] = expr.arguments; - if (arg1.type === "SpreadElement") return; - const arg1Eval = this.evaluateExpression(arg1); - if (!arg1Eval.isString()) return; - const arg1Value = /** @type {string} */ (arg1Eval.string); - - let result; - if (arg2) { - if (arg2.type === "SpreadElement") return; - const arg2Eval = this.evaluateExpression(arg2); - if (!arg2Eval.isNumber()) return; - result = /** @type {string} */ (param.string).indexOf( - arg1Value, - arg2Eval.number - ); - } else { - result = /** @type {string} */ (param.string).indexOf(arg1Value); - } - return new BasicEvaluatedExpression() - .setNumber(result) - .setSideEffects(param.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - }); - this.hooks.evaluateCallExpressionMember - .for("replace") - .tap("JavascriptParser", (expr, param) => { - if (!param.isString()) return; - if (expr.arguments.length !== 2) return; - if (expr.arguments[0].type === "SpreadElement") return; - if (expr.arguments[1].type === "SpreadElement") return; - const arg1 = this.evaluateExpression(expr.arguments[0]); - const arg2 = this.evaluateExpression(expr.arguments[1]); - if (!arg1.isString() && !arg1.isRegExp()) return; - const arg1Value = /** @type {string | RegExp} */ ( - arg1.regExp || arg1.string - ); - if (!arg2.isString()) return; - const arg2Value = /** @type {string} */ (arg2.string); - return new BasicEvaluatedExpression() - .setString( - /** @type {string} */ (param.string).replace(arg1Value, arg2Value) - ) - .setSideEffects(param.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - }); - for (const fn of ["substr", "substring", "slice"]) { - this.hooks.evaluateCallExpressionMember - .for(fn) - .tap("JavascriptParser", (expr, param) => { - if (!param.isString()) return; - let arg1; - let result; - const str = /** @type {string} */ (param.string); - switch (expr.arguments.length) { - case 1: - if (expr.arguments[0].type === "SpreadElement") return; - arg1 = this.evaluateExpression(expr.arguments[0]); - if (!arg1.isNumber()) return; - result = str[ - /** @type {"substr" | "substring" | "slice"} */ (fn) - ](/** @type {number} */ (arg1.number)); - break; - case 2: { - if (expr.arguments[0].type === "SpreadElement") return; - if (expr.arguments[1].type === "SpreadElement") return; - arg1 = this.evaluateExpression(expr.arguments[0]); - const arg2 = this.evaluateExpression(expr.arguments[1]); - if (!arg1.isNumber()) return; - if (!arg2.isNumber()) return; - result = str[ - /** @type {"substr" | "substring" | "slice"} */ (fn) - ]( - /** @type {number} */ (arg1.number), - /** @type {number} */ (arg2.number) - ); - break; - } - default: - return; - } - return new BasicEvaluatedExpression() - .setString(result) - .setSideEffects(param.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - }); - } - - /** - * @param {"cooked" | "raw"} kind kind of values to get - * @param {TemplateLiteral} templateLiteralExpr TemplateLiteral expr - * @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template - */ - const getSimplifiedTemplateResult = (kind, templateLiteralExpr) => { - /** @type {BasicEvaluatedExpression[]} */ - const quasis = []; - /** @type {BasicEvaluatedExpression[]} */ - const parts = []; - - for (let i = 0; i < templateLiteralExpr.quasis.length; i++) { - const quasiExpr = templateLiteralExpr.quasis[i]; - const quasi = quasiExpr.value[kind]; - - if (i > 0) { - const prevExpr = parts[parts.length - 1]; - const expr = this.evaluateExpression( - templateLiteralExpr.expressions[i - 1] - ); - const exprAsString = expr.asString(); - if ( - typeof exprAsString === "string" && - !expr.couldHaveSideEffects() - ) { - // We can merge quasi + expr + quasi when expr - // is a const string - - prevExpr.setString(prevExpr.string + exprAsString + quasi); - prevExpr.setRange([ - /** @type {Range} */ (prevExpr.range)[0], - /** @type {Range} */ (quasiExpr.range)[1] - ]); - // We unset the expression as it doesn't match to a single expression - prevExpr.setExpression(undefined); - continue; - } - parts.push(expr); - } - - const part = new BasicEvaluatedExpression() - .setString(/** @type {string} */ (quasi)) - .setRange(/** @type {Range} */ (quasiExpr.range)) - .setExpression(quasiExpr); - quasis.push(part); - parts.push(part); - } - return { - quasis, - parts - }; - }; - - this.hooks.evaluate - .for("TemplateLiteral") - .tap("JavascriptParser", _node => { - const node = /** @type {TemplateLiteral} */ (_node); - - const { quasis, parts } = getSimplifiedTemplateResult("cooked", node); - if (parts.length === 1) { - return parts[0].setRange(/** @type {Range} */ (node.range)); - } - return new BasicEvaluatedExpression() - .setTemplateString(quasis, parts, "cooked") - .setRange(/** @type {Range} */ (node.range)); - }); - this.hooks.evaluate - .for("TaggedTemplateExpression") - .tap("JavascriptParser", _node => { - const node = /** @type {TaggedTemplateExpression} */ (_node); - const tag = this.evaluateExpression(node.tag); - - if (tag.isIdentifier() && tag.identifier === "String.raw") { - const { quasis, parts } = getSimplifiedTemplateResult( - "raw", - node.quasi - ); - return new BasicEvaluatedExpression() - .setTemplateString(quasis, parts, "raw") - .setRange(/** @type {Range} */ (node.range)); - } - }); - - this.hooks.evaluateCallExpressionMember - .for("concat") - .tap("JavascriptParser", (expr, param) => { - if (!param.isString() && !param.isWrapped()) return; - let stringSuffix = null; - let hasUnknownParams = false; - const innerExpressions = []; - for (let i = expr.arguments.length - 1; i >= 0; i--) { - const arg = expr.arguments[i]; - if (arg.type === "SpreadElement") return; - const argExpr = this.evaluateExpression(arg); - if ( - hasUnknownParams || - (!argExpr.isString() && !argExpr.isNumber()) - ) { - hasUnknownParams = true; - innerExpressions.push(argExpr); - continue; - } - - /** @type {string} */ - const value = argExpr.isString() - ? /** @type {string} */ (argExpr.string) - : String(/** @type {number} */ (argExpr.number)); - - /** @type {string} */ - const newString = value + (stringSuffix ? stringSuffix.string : ""); - const newRange = /** @type {Range} */ ([ - /** @type {Range} */ (argExpr.range)[0], - /** @type {Range} */ ((stringSuffix || argExpr).range)[1] - ]); - stringSuffix = new BasicEvaluatedExpression() - .setString(newString) - .setSideEffects( - (stringSuffix && stringSuffix.couldHaveSideEffects()) || - argExpr.couldHaveSideEffects() - ) - .setRange(newRange); - } - - if (hasUnknownParams) { - const prefix = param.isString() ? param : param.prefix; - const inner = - param.isWrapped() && param.wrappedInnerExpressions - ? param.wrappedInnerExpressions.concat(innerExpressions.reverse()) - : innerExpressions.reverse(); - return new BasicEvaluatedExpression() - .setWrapped(prefix, stringSuffix, inner) - .setRange(/** @type {Range} */ (expr.range)); - } else if (param.isWrapped()) { - const postfix = stringSuffix || param.postfix; - const inner = param.wrappedInnerExpressions - ? param.wrappedInnerExpressions.concat(innerExpressions.reverse()) - : innerExpressions.reverse(); - return new BasicEvaluatedExpression() - .setWrapped(param.prefix, postfix, inner) - .setRange(/** @type {Range} */ (expr.range)); - } - const newString = - /** @type {string} */ (param.string) + - (stringSuffix ? stringSuffix.string : ""); - return new BasicEvaluatedExpression() - .setString(newString) - .setSideEffects( - (stringSuffix && stringSuffix.couldHaveSideEffects()) || - param.couldHaveSideEffects() - ) - .setRange(/** @type {Range} */ (expr.range)); - }); - this.hooks.evaluateCallExpressionMember - .for("split") - .tap("JavascriptParser", (expr, param) => { - if (!param.isString()) return; - if (expr.arguments.length !== 1) return; - if (expr.arguments[0].type === "SpreadElement") return; - let result; - const arg = this.evaluateExpression(expr.arguments[0]); - if (arg.isString()) { - result = - /** @type {string} */ - (param.string).split(/** @type {string} */ (arg.string)); - } else if (arg.isRegExp()) { - result = /** @type {string} */ (param.string).split( - /** @type {RegExp} */ (arg.regExp) - ); - } else { - return; - } - return new BasicEvaluatedExpression() - .setArray(result) - .setSideEffects(param.couldHaveSideEffects()) - .setRange(/** @type {Range} */ (expr.range)); - }); - this.hooks.evaluate - .for("ConditionalExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {ConditionalExpression} */ (_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); - res = new BasicEvaluatedExpression(); - if (consequent.isConditional()) { - res.setOptions( - /** @type {BasicEvaluatedExpression[]} */ (consequent.options) - ); - } else { - res.setOptions([consequent]); - } - if (alternate.isConditional()) { - res.addOptions( - /** @type {BasicEvaluatedExpression[]} */ (alternate.options) - ); - } else { - res.addOptions([alternate]); - } - } else { - res = this.evaluateExpression( - conditionValue ? expr.consequent : expr.alternate - ); - if (condition.couldHaveSideEffects()) res.setSideEffects(); - } - res.setRange(/** @type {Range} */ (expr.range)); - return res; - }); - this.hooks.evaluate - .for("ArrayExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {ArrayExpression} */ (_expr); - - const items = expr.elements.map( - element => - element !== null && - element.type !== "SpreadElement" && - this.evaluateExpression(element) - ); - if (!items.every(Boolean)) return; - return new BasicEvaluatedExpression() - .setItems(/** @type {BasicEvaluatedExpression[]} */ (items)) - .setRange(/** @type {Range} */ (expr.range)); - }); - this.hooks.evaluate - .for("ChainExpression") - .tap("JavascriptParser", _expr => { - const expr = /** @type {ChainExpression} */ (_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 > 0) { - const expression = - /** @type {Expression} */ - (optionalExpressionsStack.pop()); - const evaluated = this.evaluateExpression(expression); - - if (evaluated.asNullish()) { - return evaluated.setRange(/** @type {Range} */ (_expr.range)); - } - } - return this.evaluateExpression(expr.expression); - }); - } - - /** - * @param {Expression} node node - * @returns {Set | undefined} destructured identifiers - */ - destructuringAssignmentPropertiesFor(node) { - if (!this.destructuringAssignmentProperties) return; - return this.destructuringAssignmentProperties.get(node); - } - - /** - * @param {Expression | SpreadElement} expr expression - * @returns {string | VariableInfoInterface | undefined} identifier - */ - getRenameIdentifier(expr) { - const result = this.evaluateExpression(expr); - if (result.isIdentifier()) { - return result.identifier; - } - } - - /** - * @param {ClassExpression | ClassDeclaration} classy a class node - * @returns {void} - */ - walkClass(classy) { - if ( - classy.superClass && - !this.hooks.classExtendsExpression.call(classy.superClass, classy) - ) { - this.walkExpression(classy.superClass); - } - if (classy.body && classy.body.type === "ClassBody") { - const scopeParams = []; - // Add class name in scope for recursive calls - if (classy.id) { - scopeParams.push(classy.id); - } - this.inClassScope(true, scopeParams, () => { - for (const classElement of /** @type {TODO} */ (classy.body.body)) { - if (!this.hooks.classBodyElement.call(classElement, classy)) { - if (classElement.computed && classElement.key) { - this.walkExpression(classElement.key); - } - if (classElement.value) { - if ( - !this.hooks.classBodyValue.call( - classElement.value, - classElement, - classy - ) - ) { - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = false; - this.walkExpression(classElement.value); - this.scope.topLevelScope = wasTopLevel; - } - } else if (classElement.type === "StaticBlock") { - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = false; - this.walkBlockStatement(classElement); - this.scope.topLevelScope = wasTopLevel; - } - } - } - }); - } - } - - /** - * Pre walking iterates the scope for variable declarations - * @param {(Statement | ModuleDeclaration)[]} statements statements - */ - preWalkStatements(statements) { - for (let index = 0, len = statements.length; index < len; index++) { - const statement = statements[index]; - this.preWalkStatement(statement); - } - } - - /** - * Block pre walking iterates the scope for block variable declarations - * @param {(Statement | ModuleDeclaration)[]} statements statements - */ - blockPreWalkStatements(statements) { - for (let index = 0, len = statements.length; index < len; index++) { - const statement = statements[index]; - this.blockPreWalkStatement(statement); - } - } - - /** - * Walking iterates the statements and expressions and processes them - * @param {(Statement | ModuleDeclaration)[]} statements statements - */ - walkStatements(statements) { - for (let index = 0, len = statements.length; index < len; index++) { - const statement = statements[index]; - this.walkStatement(statement); - } - } - - /** - * Walking iterates the statements and expressions and processes them - * @param {Statement | ModuleDeclaration} statement statement - */ - preWalkStatement(statement) { - /** @type {StatementPath} */ - (this.statementPath).push(statement); - if (this.hooks.preStatement.call(statement)) { - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - return; - } - switch (statement.type) { - case "BlockStatement": - this.preWalkBlockStatement(statement); - break; - case "DoWhileStatement": - this.preWalkDoWhileStatement(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 "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; - } - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - } - - /** - * @param {Statement | ModuleDeclaration} statement statement - */ - blockPreWalkStatement(statement) { - /** @type {StatementPath} */ - (this.statementPath).push(statement); - if (this.hooks.blockPreStatement.call(statement)) { - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - return; - } - switch (statement.type) { - case "ImportDeclaration": - this.blockPreWalkImportDeclaration(statement); - break; - case "ExportAllDeclaration": - this.blockPreWalkExportAllDeclaration(statement); - break; - case "ExportDefaultDeclaration": - this.blockPreWalkExportDefaultDeclaration(statement); - break; - case "ExportNamedDeclaration": - this.blockPreWalkExportNamedDeclaration(statement); - break; - case "VariableDeclaration": - this.blockPreWalkVariableDeclaration(statement); - break; - case "ClassDeclaration": - this.blockPreWalkClassDeclaration(statement); - break; - case "ExpressionStatement": - this.blockPreWalkExpressionStatement(statement); - } - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - } - - /** - * @param {Statement | ModuleDeclaration} statement statement - */ - walkStatement(statement) { - /** @type {StatementPath} */ - (this.statementPath).push(statement); - if (this.hooks.statement.call(statement) !== undefined) { - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - 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; - } - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - } - - /** - * Walks a statements that is nested within a parent statement - * and can potentially be a non-block statement. - * This enforces the nested statement to never be in ASI position. - * @param {Statement} statement the nested statement - */ - walkNestedStatement(statement) { - this.prevStatement = undefined; - this.walkStatement(statement); - } - - // Real Statements - /** - * @param {BlockStatement} statement block statement - */ - preWalkBlockStatement(statement) { - this.preWalkStatements(statement.body); - } - - /** - * @param {BlockStatement} statement block statement - */ - walkBlockStatement(statement) { - this.inBlockScope(() => { - const body = statement.body; - const prev = this.prevStatement; - this.blockPreWalkStatements(body); - this.prevStatement = prev; - this.walkStatements(body); - }); - } - - /** - * @param {ExpressionStatement} statement expression statement - */ - walkExpressionStatement(statement) { - this.walkExpression(statement.expression); - } - - /** - * @param {IfStatement} statement if statement - */ - preWalkIfStatement(statement) { - this.preWalkStatement(statement.consequent); - if (statement.alternate) { - this.preWalkStatement(statement.alternate); - } - } - - /** - * @param {IfStatement} statement if statement - */ - walkIfStatement(statement) { - const result = this.hooks.statementIf.call(statement); - if (result === undefined) { - this.walkExpression(statement.test); - this.walkNestedStatement(statement.consequent); - if (statement.alternate) { - this.walkNestedStatement(statement.alternate); - } - } else if (result) { - this.walkNestedStatement(statement.consequent); - } else if (statement.alternate) { - this.walkNestedStatement(statement.alternate); - } - } - - /** - * @param {LabeledStatement} statement with statement - */ - preWalkLabeledStatement(statement) { - this.preWalkStatement(statement.body); - } - - /** - * @param {LabeledStatement} statement with statement - */ - walkLabeledStatement(statement) { - const hook = this.hooks.label.get(statement.label.name); - if (hook !== undefined) { - const result = hook.call(statement); - if (result === true) return; - } - this.walkNestedStatement(statement.body); - } - - /** - * @param {WithStatement} statement with statement - */ - preWalkWithStatement(statement) { - this.preWalkStatement(statement.body); - } - - /** - * @param {WithStatement} statement with statement - */ - walkWithStatement(statement) { - this.walkExpression(statement.object); - this.walkNestedStatement(statement.body); - } - - /** - * @param {SwitchStatement} statement switch statement - */ - preWalkSwitchStatement(statement) { - this.preWalkSwitchCases(statement.cases); - } - - /** - * @param {SwitchStatement} statement switch statement - */ - walkSwitchStatement(statement) { - this.walkExpression(statement.discriminant); - this.walkSwitchCases(statement.cases); - } - - /** - * @param {ReturnStatement | ThrowStatement} statement return or throw statement - */ - walkTerminatingStatement(statement) { - if (statement.argument) this.walkExpression(statement.argument); - } - - /** - * @param {ReturnStatement} statement return statement - */ - walkReturnStatement(statement) { - this.walkTerminatingStatement(statement); - } - - /** - * @param {ThrowStatement} statement return statement - */ - walkThrowStatement(statement) { - this.walkTerminatingStatement(statement); - } - - /** - * @param {TryStatement} statement try statement - */ - preWalkTryStatement(statement) { - this.preWalkStatement(statement.block); - if (statement.handler) this.preWalkCatchClause(statement.handler); - if (statement.finalizer) this.preWalkStatement(statement.finalizer); - } - - /** - * @param {TryStatement} statement try statement - */ - 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); - } - - /** - * @param {WhileStatement} statement while statement - */ - preWalkWhileStatement(statement) { - this.preWalkStatement(statement.body); - } - - /** - * @param {WhileStatement} statement while statement - */ - walkWhileStatement(statement) { - this.walkExpression(statement.test); - this.walkNestedStatement(statement.body); - } - - /** - * @param {DoWhileStatement} statement do while statement - */ - preWalkDoWhileStatement(statement) { - this.preWalkStatement(statement.body); - } - - /** - * @param {DoWhileStatement} statement do while statement - */ - walkDoWhileStatement(statement) { - this.walkNestedStatement(statement.body); - this.walkExpression(statement.test); - } - - /** - * @param {ForStatement} statement for statement - */ - preWalkForStatement(statement) { - if (statement.init && statement.init.type === "VariableDeclaration") { - this.preWalkStatement(statement.init); - } - this.preWalkStatement(statement.body); - } - - /** - * @param {ForStatement} statement for statement - */ - walkForStatement(statement) { - this.inBlockScope(() => { - if (statement.init) { - if (statement.init.type === "VariableDeclaration") { - this.blockPreWalkVariableDeclaration(statement.init); - this.prevStatement = undefined; - this.walkStatement(statement.init); - } else { - this.walkExpression(statement.init); - } - } - if (statement.test) { - this.walkExpression(statement.test); - } - if (statement.update) { - this.walkExpression(statement.update); - } - const body = statement.body; - if (body.type === "BlockStatement") { - // no need to add additional scope - const prev = this.prevStatement; - this.blockPreWalkStatements(body.body); - this.prevStatement = prev; - this.walkStatements(body.body); - } else { - this.walkNestedStatement(body); - } - }); - } - - /** - * @param {ForInStatement} statement for statement - */ - preWalkForInStatement(statement) { - if (statement.left.type === "VariableDeclaration") { - this.preWalkVariableDeclaration(statement.left); - } - this.preWalkStatement(statement.body); - } - - /** - * @param {ForInStatement} statement for statement - */ - walkForInStatement(statement) { - this.inBlockScope(() => { - if (statement.left.type === "VariableDeclaration") { - this.blockPreWalkVariableDeclaration(statement.left); - this.walkVariableDeclaration(statement.left); - } else { - this.walkPattern(statement.left); - } - this.walkExpression(statement.right); - const body = statement.body; - if (body.type === "BlockStatement") { - // no need to add additional scope - const prev = this.prevStatement; - this.blockPreWalkStatements(body.body); - this.prevStatement = prev; - this.walkStatements(body.body); - } else { - this.walkNestedStatement(body); - } - }); - } - - /** - * @param {ForOfStatement} statement statement - */ - preWalkForOfStatement(statement) { - if (statement.await && this.scope.topLevelScope === true) { - this.hooks.topLevelAwait.call(statement); - } - if (statement.left.type === "VariableDeclaration") { - this.preWalkVariableDeclaration(statement.left); - } - this.preWalkStatement(statement.body); - } - - /** - * @param {ForOfStatement} statement for statement - */ - walkForOfStatement(statement) { - this.inBlockScope(() => { - if (statement.left.type === "VariableDeclaration") { - this.blockPreWalkVariableDeclaration(statement.left); - this.walkVariableDeclaration(statement.left); - } else { - this.walkPattern(statement.left); - } - this.walkExpression(statement.right); - const body = statement.body; - if (body.type === "BlockStatement") { - // no need to add additional scope - const prev = this.prevStatement; - this.blockPreWalkStatements(body.body); - this.prevStatement = prev; - this.walkStatements(body.body); - } else { - this.walkNestedStatement(body); - } - }); - } - - /** - * @param {FunctionDeclaration} statement function declaration - */ - preWalkFunctionDeclaration(statement) { - if (statement.id) { - this.defineVariable(statement.id.name); - } - } - - /** - * @param {FunctionDeclaration} statement function declaration - */ - walkFunctionDeclaration(statement) { - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = false; - this.inFunctionScope(true, statement.params, () => { - for (const param of statement.params) { - this.walkPattern(param); - } - if (statement.body.type === "BlockStatement") { - this.detectMode(statement.body.body); - const prev = this.prevStatement; - this.preWalkStatement(statement.body); - this.prevStatement = prev; - this.walkStatement(statement.body); - } else { - this.walkExpression(statement.body); - } - }); - this.scope.topLevelScope = wasTopLevel; - } - - /** - * @param {ExpressionStatement} statement expression statement - */ - blockPreWalkExpressionStatement(statement) { - const expression = statement.expression; - switch (expression.type) { - case "AssignmentExpression": - this.preWalkAssignmentExpression(expression); - } - } - - /** - * @param {AssignmentExpression} expression assignment expression - */ - preWalkAssignmentExpression(expression) { - if ( - expression.left.type !== "ObjectPattern" || - !this.destructuringAssignmentProperties - ) - return; - const keys = this._preWalkObjectPattern(expression.left); - if (!keys) return; - - // check multiple assignments - if (this.destructuringAssignmentProperties.has(expression)) { - const set = - /** @type {Set} */ - (this.destructuringAssignmentProperties.get(expression)); - this.destructuringAssignmentProperties.delete(expression); - for (const id of set) keys.add(id); - } - - this.destructuringAssignmentProperties.set( - expression.right.type === "AwaitExpression" - ? expression.right.argument - : expression.right, - keys - ); - - if (expression.right.type === "AssignmentExpression") { - this.preWalkAssignmentExpression(expression.right); - } - } - - /** - * @param {ImportDeclaration} statement statement - */ - blockPreWalkImportDeclaration(statement) { - const source = /** @type {ImportSource} */ (statement.source.value); - this.hooks.import.call(statement, source); - for (const specifier of statement.specifiers) { - const name = specifier.local.name; - switch (specifier.type) { - case "ImportDefaultSpecifier": - if ( - !this.hooks.importSpecifier.call(statement, source, "default", name) - ) { - this.defineVariable(name); - } - break; - case "ImportSpecifier": - if ( - !this.hooks.importSpecifier.call( - statement, - source, - /** @type {Identifier} */ - (specifier.imported).name || - /** @type {string} */ - ( - /** @type {Literal} */ - (specifier.imported).value - ), - name - ) - ) { - this.defineVariable(name); - } - break; - case "ImportNamespaceSpecifier": - if (!this.hooks.importSpecifier.call(statement, source, null, name)) { - this.defineVariable(name); - } - break; - default: - this.defineVariable(name); - } - } - } - - /** - * @param {Declaration} declaration declaration - * @param {OnIdent} onIdent on ident callback - */ - enterDeclaration(declaration, onIdent) { - switch (declaration.type) { - case "VariableDeclaration": - for (const declarator of declaration.declarations) { - switch (declarator.type) { - case "VariableDeclarator": { - this.enterPattern(declarator.id, onIdent); - break; - } - } - } - break; - case "FunctionDeclaration": - this.enterPattern(declaration.id, onIdent); - break; - case "ClassDeclaration": - this.enterPattern(declaration.id, onIdent); - break; - } - } - - /** - * @param {ExportNamedDeclaration} statement statement - */ - blockPreWalkExportNamedDeclaration(statement) { - let source; - if (statement.source) { - source = /** @type {ImportSource} */ (statement.source.value); - this.hooks.exportImport.call(statement, source); - } else { - this.hooks.export.call(statement); - } - if ( - statement.declaration && - !this.hooks.exportDeclaration.call(statement, statement.declaration) - ) { - const prev = this.prevStatement; - this.preWalkStatement(statement.declaration); - this.prevStatement = prev; - this.blockPreWalkStatement(statement.declaration); - let index = 0; - this.enterDeclaration(statement.declaration, def => { - 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 localName = - /** @type {Identifier} */ (specifier.local).name || - /** @type {string} */ ( - /** @type {Literal} */ (specifier.local).value - ); - const name = - /** @type {Identifier} */ - (specifier.exported).name || - /** @type {string} */ - (/** @type {Literal} */ (specifier.exported).value); - if (source) { - this.hooks.exportImportSpecifier.call( - statement, - source, - localName, - name, - specifierIndex - ); - } else { - this.hooks.exportSpecifier.call( - statement, - localName, - name, - specifierIndex - ); - } - break; - } - } - } - } - } - - /** - * @param {ExportNamedDeclaration} statement the statement - */ - walkExportNamedDeclaration(statement) { - if (statement.declaration) { - this.walkStatement(statement.declaration); - } - } - - /** - * @param {TODO} statement statement - */ - blockPreWalkExportDefaultDeclaration(statement) { - const prev = this.prevStatement; - this.preWalkStatement(statement.declaration); - this.prevStatement = prev; - this.blockPreWalkStatement(statement.declaration); - if ( - /** @type {FunctionDeclaration | ClassDeclaration} */ ( - statement.declaration - ).id && - statement.declaration.type !== "FunctionExpression" && - statement.declaration.type !== "ClassExpression" - ) { - const declaration = - /** @type {FunctionDeclaration | ClassDeclaration} */ - (statement.declaration); - this.hooks.exportSpecifier.call( - statement, - declaration.id.name, - "default", - undefined - ); - } - } - - /** - * @param {ExportDefaultDeclaration} statement statement - */ - walkExportDefaultDeclaration(statement) { - this.hooks.export.call(statement); - if ( - /** @type {FunctionDeclaration | ClassDeclaration} */ ( - statement.declaration - ).id && - statement.declaration.type !== "FunctionExpression" && - statement.declaration.type !== "ClassExpression" - ) { - const declaration = - /** @type {FunctionDeclaration | ClassDeclaration} */ - (statement.declaration); - if (!this.hooks.exportDeclaration.call(statement, declaration)) { - this.walkStatement(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" || - statement.declaration.type === "ClassDeclaration" - ) { - this.walkStatement( - /** @type {FunctionDeclaration | ClassDeclaration} */ - (statement.declaration) - ); - } else { - this.walkExpression(statement.declaration); - } - - if ( - !this.hooks.exportExpression.call( - statement, - /** @type {TODO} */ (statement).declaration - ) - ) { - this.hooks.exportSpecifier.call( - statement, - /** @type {TODO} */ (statement.declaration), - "default", - undefined - ); - } - } - } - - /** - * @param {ExportAllDeclaration} statement statement - */ - blockPreWalkExportAllDeclaration(statement) { - const source = /** @type {ImportSource} */ (statement.source.value); - const name = statement.exported - ? /** @type {Identifier} */ - (statement.exported).name || - /** @type {string} */ - (/** @type {Literal} */ (statement.exported).value) - : null; - this.hooks.exportImport.call(statement, source); - this.hooks.exportImportSpecifier.call(statement, source, null, name, 0); - } - - /** - * @param {VariableDeclaration} statement variable declaration - */ - preWalkVariableDeclaration(statement) { - if (statement.kind !== "var") return; - this._preWalkVariableDeclaration(statement, this.hooks.varDeclarationVar); - } - - /** - * @param {VariableDeclaration} statement variable declaration - */ - blockPreWalkVariableDeclaration(statement) { - if (statement.kind === "var") return; - const hookMap = - statement.kind === "const" - ? this.hooks.varDeclarationConst - : this.hooks.varDeclarationLet; - this._preWalkVariableDeclaration(statement, hookMap); - } - - /** - * @param {VariableDeclaration} statement variable declaration - * @param {TODO} hookMap map of hooks - */ - _preWalkVariableDeclaration(statement, hookMap) { - for (const declarator of statement.declarations) { - switch (declarator.type) { - case "VariableDeclarator": { - this.preWalkVariableDeclarator(declarator); - if (!this.hooks.preDeclarator.call(declarator, statement)) { - 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.defineVariable(name); - } - } - }); - } - break; - } - } - } - } - - /** - * @param {ObjectPattern} objectPattern object pattern - * @returns {Set | undefined} set of names or undefined if not all keys are identifiers - */ - _preWalkObjectPattern(objectPattern) { - /** @type {Set} */ - const props = new Set(); - const properties = objectPattern.properties; - for (let i = 0; i < properties.length; i++) { - const property = properties[i]; - if (property.type !== "Property") return; - if (property.shorthand && property.value.type === "Identifier") { - this.scope.inShorthand = property.value.name; - } - const key = property.key; - if (key.type === "Identifier") { - props.add({ - id: key.name, - range: key.range, - shorthand: this.scope.inShorthand - }); - } else { - const id = this.evaluateExpression(key); - const str = id.asString(); - if (str) { - props.add({ - id: str, - range: key.range, - shorthand: this.scope.inShorthand - }); - } else { - // could not evaluate key - return; - } - } - this.scope.inShorthand = false; - } - - return props; - } - - /** - * @param {VariableDeclarator} declarator variable declarator - */ - preWalkVariableDeclarator(declarator) { - if ( - !declarator.init || - declarator.id.type !== "ObjectPattern" || - !this.destructuringAssignmentProperties - ) - return; - const keys = this._preWalkObjectPattern(declarator.id); - - if (!keys) return; - this.destructuringAssignmentProperties.set( - declarator.init.type === "AwaitExpression" - ? declarator.init.argument - : declarator.init, - keys - ); - - if (declarator.init.type === "AssignmentExpression") { - this.preWalkAssignmentExpression(declarator.init); - } - } - - /** - * @param {VariableDeclaration} statement variable declaration - */ - 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(/** @type {Expression} */ (declarator.init)) - ) { - // renaming with "var a = b;" - const hook = this.hooks.rename.get(renameIdentifier); - if ( - hook === undefined || - !hook.call(/** @type {Expression} */ (declarator.init)) - ) { - this.setVariable(declarator.id.name, renameIdentifier); - } - break; - } - } - if (!this.hooks.declarator.call(declarator, statement)) { - this.walkPattern(declarator.id); - if (declarator.init) this.walkExpression(declarator.init); - } - break; - } - } - } - } - - /** - * @param {ClassDeclaration} statement class declaration - */ - blockPreWalkClassDeclaration(statement) { - if (statement.id) { - this.defineVariable(statement.id.name); - } - } - - /** - * @param {ClassDeclaration} statement class declaration - */ - walkClassDeclaration(statement) { - this.walkClass(statement); - } - - /** - * @param {SwitchCase[]} switchCases switch statement - */ - preWalkSwitchCases(switchCases) { - for (let index = 0, len = switchCases.length; index < len; index++) { - const switchCase = switchCases[index]; - this.preWalkStatements(switchCase.consequent); - } - } - - /** - * @param {SwitchCase[]} switchCases switch statement - */ - walkSwitchCases(switchCases) { - this.inBlockScope(() => { - const len = switchCases.length; - - // we need to pre walk all statements first since we can have invalid code - // import A from "module"; - // switch(1) { - // case 1: - // console.log(A); // should fail at runtime - // case 2: - // const A = 1; - // } - for (let index = 0; index < len; index++) { - const switchCase = switchCases[index]; - - if (switchCase.consequent.length > 0) { - const prev = this.prevStatement; - this.blockPreWalkStatements(switchCase.consequent); - this.prevStatement = prev; - } - } - - for (let index = 0; index < len; index++) { - const switchCase = switchCases[index]; - - if (switchCase.test) { - this.walkExpression(switchCase.test); - } - if (switchCase.consequent.length > 0) { - this.walkStatements(switchCase.consequent); - } - } - }); - } - - /** - * @param {CatchClause} catchClause catch clause - */ - preWalkCatchClause(catchClause) { - this.preWalkStatement(catchClause.body); - } - - /** - * @param {CatchClause} catchClause catch clause - */ - walkCatchClause(catchClause) { - this.inBlockScope(() => { - // Error binding is optional in catch clause since ECMAScript 2019 - if (catchClause.param !== null) { - this.enterPattern(catchClause.param, ident => { - this.defineVariable(ident); - }); - this.walkPattern(catchClause.param); - } - const prev = this.prevStatement; - this.blockPreWalkStatement(catchClause.body); - this.prevStatement = prev; - this.walkStatement(catchClause.body); - }); - } - - /** - * @param {Pattern} pattern pattern - */ - 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; - } - } - - /** - * @param {AssignmentPattern} pattern assignment pattern - */ - walkAssignmentPattern(pattern) { - this.walkExpression(pattern.right); - this.walkPattern(pattern.left); - } - - /** - * @param {ObjectPattern} pattern pattern - */ - walkObjectPattern(pattern) { - for (let i = 0, len = pattern.properties.length; i < len; i++) { - const prop = pattern.properties[i]; - if (prop) { - if (prop.type === "RestElement") { - continue; - } - if (prop.computed) this.walkExpression(prop.key); - if (prop.value) this.walkPattern(prop.value); - } - } - } - - /** - * @param {ArrayPattern} pattern array pattern - */ - walkArrayPattern(pattern) { - for (let i = 0, len = pattern.elements.length; i < len; i++) { - const element = pattern.elements[i]; - if (element) this.walkPattern(element); - } - } - - /** - * @param {RestElement} pattern rest element - */ - walkRestElement(pattern) { - this.walkPattern(pattern.argument); - } - - /** - * @param {(Expression | SpreadElement | null)[]} expressions expressions - */ - walkExpressions(expressions) { - for (const expression of expressions) { - if (expression) { - this.walkExpression(expression); - } - } - } - - /** - * @param {TODO} expression 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 "ChainExpression": - this.walkChainExpression(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 "ImportExpression": - this.walkImportExpression(expression); - break; - case "LogicalExpression": - this.walkLogicalExpression(expression); - break; - case "MetaProperty": - this.walkMetaProperty(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; - } - } - - /** - * @param {AwaitExpression} expression await expression - */ - walkAwaitExpression(expression) { - if (this.scope.topLevelScope === true) - this.hooks.topLevelAwait.call(expression); - this.walkExpression(expression.argument); - } - - /** - * @param {ArrayExpression} expression array expression - */ - walkArrayExpression(expression) { - if (expression.elements) { - this.walkExpressions(expression.elements); - } - } - - /** - * @param {SpreadElement} expression spread element - */ - walkSpreadElement(expression) { - if (expression.argument) { - this.walkExpression(expression.argument); - } - } - - /** - * @param {ObjectExpression} expression object expression - */ - walkObjectExpression(expression) { - for ( - let propIndex = 0, len = expression.properties.length; - propIndex < len; - propIndex++ - ) { - const prop = expression.properties[propIndex]; - this.walkProperty(prop); - } - } - - /** - * @param {Property | SpreadElement} prop property or spread element - */ - walkProperty(prop) { - if (prop.type === "SpreadElement") { - this.walkExpression(prop.argument); - return; - } - if (prop.computed) { - this.walkExpression(prop.key); - } - if (prop.shorthand && prop.value && prop.value.type === "Identifier") { - this.scope.inShorthand = prop.value.name; - this.walkIdentifier(prop.value); - this.scope.inShorthand = false; - } else { - this.walkExpression(prop.value); - } - } - - /** - * @param {FunctionExpression} expression arrow function expression - */ - walkFunctionExpression(expression) { - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = false; - const scopeParams = [...expression.params]; - - // Add function name in scope for recursive calls - if (expression.id) { - scopeParams.push(expression.id); - } - - this.inFunctionScope(true, scopeParams, () => { - for (const param of expression.params) { - this.walkPattern(param); - } - if (expression.body.type === "BlockStatement") { - this.detectMode(expression.body.body); - const prev = this.prevStatement; - this.preWalkStatement(expression.body); - this.prevStatement = prev; - this.walkStatement(expression.body); - } else { - this.walkExpression(expression.body); - } - }); - this.scope.topLevelScope = wasTopLevel; - } - - /** - * @param {ArrowFunctionExpression} expression arrow function expression - */ - walkArrowFunctionExpression(expression) { - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = wasTopLevel ? "arrow" : false; - this.inFunctionScope(false, expression.params, () => { - for (const param of expression.params) { - this.walkPattern(param); - } - if (expression.body.type === "BlockStatement") { - this.detectMode(expression.body.body); - const prev = this.prevStatement; - this.preWalkStatement(expression.body); - this.prevStatement = prev; - this.walkStatement(expression.body); - } else { - this.walkExpression(expression.body); - } - }); - this.scope.topLevelScope = wasTopLevel; - } - - /** - * @param {SequenceExpression} expression the sequence - */ - walkSequenceExpression(expression) { - if (!expression.expressions) return; - // We treat sequence expressions like statements when they are one statement level - // This has some benefits for optimizations that only work on statement level - const currentStatement = - /** @type {StatementPath} */ - (this.statementPath)[ - /** @type {StatementPath} */ - (this.statementPath).length - 1 - ]; - if ( - currentStatement === expression || - (currentStatement.type === "ExpressionStatement" && - currentStatement.expression === expression) - ) { - const old = - /** @type {StatementPathItem} */ - (/** @type {StatementPath} */ (this.statementPath).pop()); - const prev = this.prevStatement; - for (const expr of expression.expressions) { - /** @type {StatementPath} */ - (this.statementPath).push(expr); - this.walkExpression(expr); - this.prevStatement = - /** @type {StatementPath} */ - (this.statementPath).pop(); - } - this.prevStatement = prev; - /** @type {StatementPath} */ - (this.statementPath).push(old); - } else { - this.walkExpressions(expression.expressions); - } - } - - /** - * @param {UpdateExpression} expression the update expression - */ - walkUpdateExpression(expression) { - this.walkExpression(expression.argument); - } - - /** - * @param {UnaryExpression} expression the unary expression - */ - walkUnaryExpression(expression) { - if (expression.operator === "typeof") { - const result = this.callHooksForExpression( - this.hooks.typeof, - expression.argument, - expression - ); - if (result === true) return; - if (expression.argument.type === "ChainExpression") { - const result = this.callHooksForExpression( - this.hooks.typeof, - expression.argument.expression, - expression - ); - if (result === true) return; - } - } - this.walkExpression(expression.argument); - } - - /** - * @param {LogicalExpression | BinaryExpression} expression the expression - */ - walkLeftRightExpression(expression) { - this.walkExpression(expression.left); - this.walkExpression(expression.right); - } - - /** - * @param {BinaryExpression} expression the binary expression - */ - walkBinaryExpression(expression) { - if (this.hooks.binaryExpression.call(expression) === undefined) { - this.walkLeftRightExpression(expression); - } - } - - /** - * @param {LogicalExpression} expression the logical expression - */ - walkLogicalExpression(expression) { - const result = this.hooks.expressionLogicalOperator.call(expression); - if (result === undefined) { - this.walkLeftRightExpression(expression); - } else if (result) { - this.walkExpression(expression.right); - } - } - - /** - * @param {AssignmentExpression} expression assignment expression - */ - walkAssignmentExpression(expression) { - if (expression.left.type === "Identifier") { - const renameIdentifier = this.getRenameIdentifier(expression.right); - if ( - renameIdentifier && - this.callHooksForInfo( - this.hooks.canRename, - renameIdentifier, - expression.right - ) - ) { - // renaming "a = b;" - if ( - !this.callHooksForInfo( - this.hooks.rename, - renameIdentifier, - expression.right - ) - ) { - this.setVariable( - expression.left.name, - typeof renameIdentifier === "string" - ? this.getVariableInfo(renameIdentifier) - : renameIdentifier - ); - } - return; - } - this.walkExpression(expression.right); - this.enterPattern(expression.left, (name, decl) => { - if (!this.callHooksForName(this.hooks.assign, name, expression)) { - this.walkExpression(expression.left); - } - }); - return; - } - if (expression.left.type.endsWith("Pattern")) { - this.walkExpression(expression.right); - this.enterPattern(expression.left, (name, decl) => { - if (!this.callHooksForName(this.hooks.assign, name, expression)) { - this.defineVariable(name); - } - }); - this.walkPattern(expression.left); - } else if (expression.left.type === "MemberExpression") { - const exprName = this.getMemberExpressionInfo( - expression.left, - ALLOWED_MEMBER_TYPES_EXPRESSION - ); - if ( - exprName && - this.callHooksForInfo( - this.hooks.assignMemberChain, - exprName.rootInfo, - expression, - exprName.getMembers() - ) - ) { - return; - } - this.walkExpression(expression.right); - this.walkExpression(expression.left); - } else { - this.walkExpression(expression.right); - this.walkExpression(expression.left); - } - } - - /** - * @param {ConditionalExpression} expression conditional expression - */ - 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); - } - } - - /** - * @param {NewExpression} expression new expression - */ - walkNewExpression(expression) { - const result = this.callHooksForExpression( - this.hooks.new, - expression.callee, - expression - ); - if (result === true) return; - this.walkExpression(expression.callee); - if (expression.arguments) { - this.walkExpressions(expression.arguments); - } - } - - /** - * @param {YieldExpression} expression yield expression - */ - walkYieldExpression(expression) { - if (expression.argument) { - this.walkExpression(expression.argument); - } - } - - /** - * @param {TemplateLiteral} expression template literal - */ - walkTemplateLiteral(expression) { - if (expression.expressions) { - this.walkExpressions(expression.expressions); - } - } - - /** - * @param {TaggedTemplateExpression} expression tagged template expression - */ - walkTaggedTemplateExpression(expression) { - if (expression.tag) { - this.scope.inTaggedTemplateTag = true; - this.walkExpression(expression.tag); - this.scope.inTaggedTemplateTag = false; - } - if (expression.quasi && expression.quasi.expressions) { - this.walkExpressions(expression.quasi.expressions); - } - } - - /** - * @param {ClassExpression} expression the class expression - */ - walkClassExpression(expression) { - this.walkClass(expression); - } - - /** - * @param {ChainExpression} expression expression - */ - walkChainExpression(expression) { - const result = this.hooks.optionalChaining.call(expression); - - if (result === undefined) { - if (expression.expression.type === "CallExpression") { - this.walkCallExpression(expression.expression); - } else { - this.walkMemberExpression(expression.expression); - } - } - } - - /** - * @private - * @param {FunctionExpression | ArrowFunctionExpression} functionExpression function expression - * @param {(Expression | SpreadElement)[]} options options - * @param {Expression | SpreadElement | null} currentThis current this - */ - _walkIIFE(functionExpression, options, currentThis) { - /** - * @param {Expression | SpreadElement} argOrThis arg or this - * @returns {string | VariableInfoInterface | undefined} var info - */ - const getVarInfo = argOrThis => { - const renameIdentifier = this.getRenameIdentifier(argOrThis); - if ( - renameIdentifier && - this.callHooksForInfo( - this.hooks.canRename, - renameIdentifier, - /** @type {Expression} */ - (argOrThis) - ) && - !this.callHooksForInfo( - this.hooks.rename, - renameIdentifier, - /** @type {Expression} */ - (argOrThis) - ) - ) { - return typeof renameIdentifier === "string" - ? /** @type {string} */ (this.getVariableInfo(renameIdentifier)) - : renameIdentifier; - } - this.walkExpression(argOrThis); - }; - const { params, type } = functionExpression; - const arrow = type === "ArrowFunctionExpression"; - const renameThis = currentThis ? getVarInfo(currentThis) : null; - const varInfoForArgs = options.map(getVarInfo); - const wasTopLevel = this.scope.topLevelScope; - this.scope.topLevelScope = wasTopLevel && arrow ? "arrow" : false; - const scopeParams = - /** @type {(Identifier | string)[]} */ - (params.filter((identifier, idx) => !varInfoForArgs[idx])); - - // Add function name in scope for recursive calls - if ( - functionExpression.type === "FunctionExpression" && - functionExpression.id - ) { - scopeParams.push(functionExpression.id.name); - } - - this.inFunctionScope(true, scopeParams, () => { - if (renameThis && !arrow) { - this.setVariable("this", renameThis); - } - for (let i = 0; i < varInfoForArgs.length; i++) { - const varInfo = varInfoForArgs[i]; - if (!varInfo) continue; - if (!params[i] || params[i].type !== "Identifier") continue; - this.setVariable(/** @type {Identifier} */ (params[i]).name, varInfo); - } - if (functionExpression.body.type === "BlockStatement") { - this.detectMode(functionExpression.body.body); - const prev = this.prevStatement; - this.preWalkStatement(functionExpression.body); - this.prevStatement = prev; - this.walkStatement(functionExpression.body); - } else { - this.walkExpression(functionExpression.body); - } - }); - this.scope.topLevelScope = wasTopLevel; - } - - /** - * @param {ImportExpression} expression import expression - */ - walkImportExpression(expression) { - const result = this.hooks.importCall.call(expression); - if (result === true) return; - - this.walkExpression(expression.source); - } - - /** - * @param {CallExpression} expression expression - */ - walkCallExpression(expression) { - /** - * @param {FunctionExpression | ArrowFunctionExpression} fn function - * @returns {boolean} true when simple function - */ - const isSimpleFunction = fn => - fn.params.every(p => p.type === "Identifier"); - if ( - expression.callee.type === "MemberExpression" && - expression.callee.object.type.endsWith("FunctionExpression") && - !expression.callee.computed && - // eslint-disable-next-line no-warning-comments - // @ts-ignore - // TODO check me and handle more cases - (expression.callee.property.name === "call" || - // eslint-disable-next-line no-warning-comments - // @ts-ignore - expression.callee.property.name === "bind") && - expression.arguments.length > 0 && - isSimpleFunction( - /** @type {FunctionExpression | ArrowFunctionExpression} */ - (expression.callee.object) - ) - ) { - // (function(…) { }.call/bind(?, …)) - this._walkIIFE( - /** @type {FunctionExpression | ArrowFunctionExpression} */ - (expression.callee.object), - expression.arguments.slice(1), - expression.arguments[0] - ); - } else if ( - expression.callee.type.endsWith("FunctionExpression") && - isSimpleFunction( - /** @type {FunctionExpression | ArrowFunctionExpression} */ - (expression.callee) - ) - ) { - // (function(…) { }(…)) - this._walkIIFE( - /** @type {FunctionExpression | ArrowFunctionExpression} */ - (expression.callee), - expression.arguments, - null - ); - } else { - if (expression.callee.type === "MemberExpression") { - const exprInfo = this.getMemberExpressionInfo( - expression.callee, - ALLOWED_MEMBER_TYPES_CALL_EXPRESSION - ); - if (exprInfo && exprInfo.type === "call") { - const result = this.callHooksForInfo( - this.hooks.callMemberChainOfCallMemberChain, - exprInfo.rootInfo, - expression, - exprInfo.getCalleeMembers(), - exprInfo.call, - exprInfo.getMembers(), - exprInfo.getMemberRanges() - ); - if (result === true) return; - } - } - const callee = this.evaluateExpression( - /** @type {TODO} */ (expression.callee) - ); - if (callee.isIdentifier()) { - const result1 = this.callHooksForInfo( - this.hooks.callMemberChain, - /** @type {NonNullable} */ - (callee.rootInfo), - expression, - /** @type {NonNullable} */ - (callee.getMembers)(), - callee.getMembersOptionals - ? callee.getMembersOptionals() - : /** @type {NonNullable} */ - (callee.getMembers)().map(() => false), - callee.getMemberRanges ? callee.getMemberRanges() : [] - ); - if (result1 === true) return; - const result2 = this.callHooksForInfo( - this.hooks.call, - /** @type {NonNullable} */ - (callee.identifier), - expression - ); - if (result2 === true) return; - } - - if (expression.callee) { - if (expression.callee.type === "MemberExpression") { - // because of call context we need to walk the call context as expression - this.walkExpression(expression.callee.object); - if (expression.callee.computed === true) - this.walkExpression(expression.callee.property); - } else { - this.walkExpression(expression.callee); - } - } - if (expression.arguments) this.walkExpressions(expression.arguments); - } - } - - /** - * @param {MemberExpression} expression member expression - */ - walkMemberExpression(expression) { - const exprInfo = this.getMemberExpressionInfo( - expression, - ALLOWED_MEMBER_TYPES_ALL - ); - if (exprInfo) { - switch (exprInfo.type) { - case "expression": { - const result1 = this.callHooksForInfo( - this.hooks.expression, - exprInfo.name, - expression - ); - if (result1 === true) return; - const members = exprInfo.getMembers(); - const membersOptionals = exprInfo.getMembersOptionals(); - const memberRanges = exprInfo.getMemberRanges(); - const result2 = this.callHooksForInfo( - this.hooks.expressionMemberChain, - exprInfo.rootInfo, - expression, - members, - membersOptionals, - memberRanges - ); - if (result2 === true) return; - this.walkMemberExpressionWithExpressionName( - expression, - exprInfo.name, - exprInfo.rootInfo, - members.slice(), - () => - this.callHooksForInfo( - this.hooks.unhandledExpressionMemberChain, - exprInfo.rootInfo, - expression, - members - ) - ); - return; - } - case "call": { - const result = this.callHooksForInfo( - this.hooks.memberChainOfCallMemberChain, - exprInfo.rootInfo, - expression, - exprInfo.getCalleeMembers(), - exprInfo.call, - exprInfo.getMembers(), - exprInfo.getMemberRanges() - ); - if (result === true) return; - // Fast skip over the member chain as we already called memberChainOfCallMemberChain - // and call computed property are literals anyway - this.walkExpression(exprInfo.call); - return; - } - } - } - this.walkExpression(expression.object); - if (expression.computed === true) this.walkExpression(expression.property); - } - - /** - * @param {TODO} expression member expression - * @param {string} name name - * @param {string | VariableInfo} rootInfo root info - * @param {string[]} members members - * @param {TODO} onUnhandled on unhandled callback - */ - walkMemberExpressionWithExpressionName( - expression, - name, - rootInfo, - members, - onUnhandled - ) { - if (expression.object.type === "MemberExpression") { - // optimize the case where expression.object is a MemberExpression too. - // we can keep info here when calling walkMemberExpression directly - const property = - expression.property.name || `${expression.property.value}`; - name = name.slice(0, -property.length - 1); - members.pop(); - const result = this.callHooksForInfo( - this.hooks.expression, - name, - expression.object - ); - if (result === true) return; - this.walkMemberExpressionWithExpressionName( - expression.object, - name, - rootInfo, - members, - onUnhandled - ); - } else if (!onUnhandled || !onUnhandled()) { - this.walkExpression(expression.object); - } - if (expression.computed === true) this.walkExpression(expression.property); - } - - /** - * @param {ThisExpression} expression this expression - */ - walkThisExpression(expression) { - this.callHooksForName(this.hooks.expression, "this", expression); - } - - /** - * @param {Identifier} expression identifier - */ - walkIdentifier(expression) { - this.callHooksForName(this.hooks.expression, expression.name, expression); - } - - /** - * @param {MetaProperty} metaProperty meta property - */ - walkMetaProperty(metaProperty) { - this.hooks.expression.for(getRootName(metaProperty)).call(metaProperty); - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks the should be called - * @param {Expression | Super} expr expression - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForExpression(hookMap, expr, ...args) { - return this.callHooksForExpressionWithFallback( - hookMap, - expr, - undefined, - undefined, - ...args - ); - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks the should be called - * @param {Expression | Super} expr expression info - * @param {(function(string, string | ScopeInfo | VariableInfo, function(): string[]): any) | undefined} fallback callback when variable in not handled by hooks - * @param {(function(string): any) | undefined} defined callback when variable is defined - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForExpressionWithFallback( - hookMap, - expr, - fallback, - defined, - ...args - ) { - const exprName = this.getMemberExpressionInfo( - expr, - ALLOWED_MEMBER_TYPES_EXPRESSION - ); - if (exprName !== undefined) { - const members = exprName.getMembers(); - return this.callHooksForInfoWithFallback( - hookMap, - members.length === 0 ? exprName.rootInfo : exprName.name, - fallback && - (name => fallback(name, exprName.rootInfo, exprName.getMembers)), - defined && (() => defined(exprName.name)), - ...args - ); - } - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks the should be called - * @param {string} name key in map - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForName(hookMap, name, ...args) { - return this.callHooksForNameWithFallback( - hookMap, - name, - undefined, - undefined, - ...args - ); - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks that should be called - * @param {ExportedVariableInfo} info variable info - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForInfo(hookMap, info, ...args) { - return this.callHooksForInfoWithFallback( - hookMap, - info, - undefined, - undefined, - ...args - ); - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks the should be called - * @param {ExportedVariableInfo} info variable info - * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks - * @param {(function(string=): any) | undefined} defined callback when variable is defined - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForInfoWithFallback(hookMap, info, fallback, defined, ...args) { - let name; - if (typeof info === "string") { - name = info; - } else { - if (!(info instanceof VariableInfo)) { - if (defined !== undefined) { - return defined(); - } - return; - } - let tagInfo = info.tagInfo; - while (tagInfo !== undefined) { - const hook = hookMap.get(tagInfo.tag); - if (hook !== undefined) { - this.currentTagData = tagInfo.data; - const result = hook.call(...args); - this.currentTagData = undefined; - if (result !== undefined) return result; - } - tagInfo = tagInfo.next; - } - if (info.freeName === true) { - if (defined !== undefined) { - return defined(); - } - return; - } - name = info.freeName; - } - const hook = hookMap.get(name); - if (hook !== undefined) { - const result = hook.call(...args); - if (result !== undefined) return result; - } - if (fallback !== undefined) { - return fallback(/** @type {string} */ (name)); - } - } - - /** - * @template T - * @template R - * @param {HookMap>} hookMap hooks the should be called - * @param {string} name key in map - * @param {(function(string): any) | undefined} fallback callback when variable in not handled by hooks - * @param {(function(): any) | undefined} defined callback when variable is defined - * @param {AsArray} args args for the hook - * @returns {R | undefined} result of hook - */ - callHooksForNameWithFallback(hookMap, name, fallback, defined, ...args) { - return this.callHooksForInfoWithFallback( - hookMap, - this.getVariableInfo(name), - fallback, - defined, - ...args - ); - } - - /** - * @deprecated - * @param {any} params scope params - * @param {function(): void} fn inner function - * @returns {void} - */ - inScope(params, fn) { - const oldScope = this.scope; - this.scope = { - topLevelScope: oldScope.topLevelScope, - inTry: false, - inShorthand: false, - inTaggedTemplateTag: false, - isStrict: oldScope.isStrict, - isAsmJs: oldScope.isAsmJs, - definitions: oldScope.definitions.createChild() - }; - - this.undefineVariable("this"); - - this.enterPatterns(params, ident => { - this.defineVariable(ident); - }); - - fn(); - - this.scope = oldScope; - } - - /** - * @param {boolean} hasThis true, when this is defined - * @param {Identifier[]} params scope params - * @param {function(): void} fn inner function - * @returns {void} - */ - inClassScope(hasThis, params, fn) { - const oldScope = this.scope; - this.scope = { - topLevelScope: oldScope.topLevelScope, - inTry: false, - inShorthand: false, - inTaggedTemplateTag: false, - isStrict: oldScope.isStrict, - isAsmJs: oldScope.isAsmJs, - definitions: oldScope.definitions.createChild() - }; - - if (hasThis) { - this.undefineVariable("this"); - } - - this.enterPatterns(params, ident => { - this.defineVariable(ident); - }); - - fn(); - - this.scope = oldScope; - } - - /** - * @param {boolean} hasThis true, when this is defined - * @param {(Pattern | string)[]} params scope params - * @param {function(): void} fn inner function - * @returns {void} - */ - inFunctionScope(hasThis, params, fn) { - const oldScope = this.scope; - this.scope = { - topLevelScope: oldScope.topLevelScope, - inTry: false, - inShorthand: false, - inTaggedTemplateTag: false, - isStrict: oldScope.isStrict, - isAsmJs: oldScope.isAsmJs, - definitions: oldScope.definitions.createChild() - }; - - if (hasThis) { - this.undefineVariable("this"); - } - - this.enterPatterns(params, ident => { - this.defineVariable(ident); - }); - - fn(); - - this.scope = oldScope; - } - - /** - * @param {function(): void} fn inner function - * @returns {void} - */ - inBlockScope(fn) { - const oldScope = this.scope; - this.scope = { - topLevelScope: oldScope.topLevelScope, - inTry: oldScope.inTry, - inShorthand: false, - inTaggedTemplateTag: false, - isStrict: oldScope.isStrict, - isAsmJs: oldScope.isAsmJs, - definitions: oldScope.definitions.createChild() - }; - - fn(); - - this.scope = oldScope; - } - - /** - * @param {Array} statements statements - */ - detectMode(statements) { - const isLiteral = - statements.length >= 1 && - statements[0].type === "ExpressionStatement" && - statements[0].expression.type === "Literal"; - if ( - isLiteral && - /** @type {Literal} */ - (/** @type {ExpressionStatement} */ (statements[0]).expression).value === - "use strict" - ) { - this.scope.isStrict = true; - } - if ( - isLiteral && - /** @type {Literal} */ - (/** @type {ExpressionStatement} */ (statements[0]).expression).value === - "use asm" - ) { - this.scope.isAsmJs = true; - } - } - - /** - * @param {(string | Pattern | Property)[]} patterns patterns - * @param {OnIdentString} onIdent on ident callback - */ - enterPatterns(patterns, onIdent) { - for (const pattern of patterns) { - if (typeof pattern !== "string") { - this.enterPattern(pattern, onIdent); - } else if (pattern) { - onIdent(pattern); - } - } - } - - /** - * @param {Pattern | Property} pattern pattern - * @param {OnIdent} onIdent on ident callback - */ - 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; - case "Property": - if (pattern.shorthand && pattern.value.type === "Identifier") { - this.scope.inShorthand = pattern.value.name; - this.enterIdentifier(pattern.value, onIdent); - this.scope.inShorthand = false; - } else { - this.enterPattern(/** @type {Pattern} */ (pattern.value), onIdent); - } - break; - } - } - - /** - * @param {Identifier} pattern identifier pattern - * @param {OnIdent} onIdent callback - */ - enterIdentifier(pattern, onIdent) { - if (!this.callHooksForName(this.hooks.pattern, pattern.name, pattern)) { - onIdent(pattern.name, pattern); - } - } - - /** - * @param {ObjectPattern} pattern object pattern - * @param {OnIdent} onIdent callback - */ - enterObjectPattern(pattern, onIdent) { - for ( - let propIndex = 0, len = pattern.properties.length; - propIndex < len; - propIndex++ - ) { - const prop = pattern.properties[propIndex]; - this.enterPattern(prop, onIdent); - } - } - - /** - * @param {ArrayPattern} pattern object pattern - * @param {OnIdent} onIdent callback - */ - enterArrayPattern(pattern, onIdent) { - for ( - let elementIndex = 0, len = pattern.elements.length; - elementIndex < len; - elementIndex++ - ) { - const element = pattern.elements[elementIndex]; - - if (element) { - this.enterPattern(element, onIdent); - } - } - } - - /** - * @param {RestElement} pattern object pattern - * @param {OnIdent} onIdent callback - */ - enterRestElement(pattern, onIdent) { - this.enterPattern(pattern.argument, onIdent); - } - - /** - * @param {AssignmentPattern} pattern object pattern - * @param {OnIdent} onIdent callback - */ - enterAssignmentPattern(pattern, onIdent) { - this.enterPattern(pattern.left, onIdent); - } - - /** - * @param {Expression | SpreadElement | PrivateIdentifier} expression expression node - * @returns {BasicEvaluatedExpression} evaluation result - */ - evaluateExpression(expression) { - try { - const hook = this.hooks.evaluate.get(expression.type); - if (hook !== undefined) { - const result = hook.call(expression); - if (result !== undefined && result !== null) { - result.setExpression(expression); - return result; - } - } - } catch (err) { - console.warn(err); - // ignore error - } - return new BasicEvaluatedExpression() - .setRange(/** @type {Range} */ (expression.range)) - .setExpression(expression); - } - - /** - * @param {Expression} expression expression - * @returns {string} parsed string - */ - parseString(expression) { - switch (expression.type) { - case "BinaryExpression": - if (expression.operator === "+") { - return ( - this.parseString(/** @type {Expression} */ (expression.left)) + - this.parseString(expression.right) - ); - } - break; - case "Literal": - return String(expression.value); - } - throw new Error( - `${expression.type} is not supported as parameter for require` - ); - } - - /** - * @param {Expression} expression expression - * @returns {{ range?: Range, value: string, code: boolean, conditional: TODO }} result - */ - parseCalculatedString(expression) { - switch (expression.type) { - case "BinaryExpression": - if (expression.operator === "+") { - const left = this.parseCalculatedString( - /** @type {Expression} */ - (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: [ - /** @type {Range} */ - (left.range)[0], - right.range - ? right.range[1] - : /** @type {Range} */ (left.range)[1] - ], - value: left.value + right.value, - code: true, - conditional: false - }; - } - return { - range: [ - /** @type {Range} */ - (left.range)[0], - /** @type {Range} */ - (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: String(expression.value), - code: false, - conditional: false - }; - } - return { - range: undefined, - value: "", - code: true, - conditional: false - }; - } - - /** - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - let ast; - /** @type {import("acorn").Comment[]} */ - let comments; - const semicolons = new Set(); - if (source === null) { - throw new Error("source must not be null"); - } - if (Buffer.isBuffer(source)) { - source = source.toString("utf-8"); - } - if (typeof source === "object") { - ast = /** @type {Program} */ (source); - comments = source.comments; - } else { - comments = []; - ast = JavascriptParser._parse(source, { - sourceType: this.sourceType, - onComment: comments, - onInsertedSemicolon: pos => semicolons.add(pos) - }); - } - - const oldScope = this.scope; - const oldState = this.state; - const oldComments = this.comments; - const oldSemicolons = this.semicolons; - const oldStatementPath = this.statementPath; - const oldPrevStatement = this.prevStatement; - this.scope = { - topLevelScope: true, - inTry: false, - inShorthand: false, - inTaggedTemplateTag: false, - isStrict: false, - isAsmJs: false, - definitions: new StackedMap() - }; - /** @type {ParserState} */ - this.state = state; - this.comments = comments; - this.semicolons = semicolons; - this.statementPath = []; - this.prevStatement = undefined; - if (this.hooks.program.call(ast, comments) === undefined) { - this.destructuringAssignmentProperties = new WeakMap(); - this.detectMode(ast.body); - this.preWalkStatements(ast.body); - this.prevStatement = undefined; - this.blockPreWalkStatements(ast.body); - this.prevStatement = undefined; - this.walkStatements(ast.body); - this.destructuringAssignmentProperties = undefined; - } - this.hooks.finish.call(ast, comments); - this.scope = oldScope; - /** @type {ParserState} */ - this.state = oldState; - this.comments = oldComments; - this.semicolons = oldSemicolons; - this.statementPath = oldStatementPath; - this.prevStatement = oldPrevStatement; - return state; - } - - /** - * @param {string} source source code - * @returns {BasicEvaluatedExpression} evaluation result - */ - evaluate(source) { - const ast = JavascriptParser._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); - } - - /** - * @param {Expression | Declaration | PrivateIdentifier | null | undefined} expr an expression - * @param {number} commentsStartPos source position from which annotation comments are checked - * @returns {boolean} true, when the expression is pure - */ - isPure(expr, commentsStartPos) { - if (!expr) return true; - const result = this.hooks.isPure - .for(expr.type) - .call(expr, commentsStartPos); - if (typeof result === "boolean") return result; - switch (expr.type) { - // TODO handle more cases - case "ClassDeclaration": - case "ClassExpression": { - if (expr.body.type !== "ClassBody") return false; - if ( - expr.superClass && - !this.isPure(expr.superClass, /** @type {Range} */ (expr.range)[0]) - ) { - return false; - } - const items = - /** @type {TODO[]} */ - (expr.body.body); - return items.every(item => { - if ( - item.computed && - item.key && - !this.isPure(item.key, item.range[0]) - ) { - return false; - } - - if ( - item.static && - item.value && - !this.isPure( - item.value, - item.key ? item.key.range[1] : item.range[0] - ) - ) { - return false; - } - - if (item.type === "StaticBlock") { - return false; - } - - if ( - expr.superClass && - item.type === "MethodDefinition" && - item.kind === "constructor" - ) { - return false; - } - - return true; - }); - } - - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - case "ThisExpression": - case "Literal": - case "TemplateLiteral": - case "Identifier": - case "PrivateIdentifier": - return true; - - case "VariableDeclaration": - return expr.declarations.every(decl => - this.isPure(decl.init, /** @type {Range} */ (decl.range)[0]) - ); - - case "ConditionalExpression": - return ( - this.isPure(expr.test, commentsStartPos) && - this.isPure( - expr.consequent, - /** @type {Range} */ (expr.test.range)[1] - ) && - this.isPure( - expr.alternate, - /** @type {Range} */ (expr.consequent.range)[1] - ) - ); - - case "LogicalExpression": - return ( - this.isPure(expr.left, commentsStartPos) && - this.isPure(expr.right, /** @type {Range} */ (expr.left.range)[1]) - ); - - case "SequenceExpression": - return expr.expressions.every(expr => { - const pureFlag = this.isPure(expr, commentsStartPos); - commentsStartPos = /** @type {Range} */ (expr.range)[1]; - return pureFlag; - }); - - case "CallExpression": { - const pureFlag = - /** @type {Range} */ (expr.range)[0] - commentsStartPos > 12 && - this.getComments([ - commentsStartPos, - /** @type {Range} */ (expr.range)[0] - ]).some( - comment => - comment.type === "Block" && - /^\s*(#|@)__PURE__\s*$/.test(comment.value) - ); - if (!pureFlag) return false; - commentsStartPos = /** @type {Range} */ (expr.callee.range)[1]; - return expr.arguments.every(arg => { - if (arg.type === "SpreadElement") return false; - const pureFlag = this.isPure(arg, commentsStartPos); - commentsStartPos = /** @type {Range} */ (arg.range)[1]; - return pureFlag; - }); - } - } - const evaluated = this.evaluateExpression(expr); - return !evaluated.couldHaveSideEffects(); - } - - /** - * @param {Range} range range - * @returns {Comment[]} comments in the range - */ - getComments(range) { - const [rangeStart, rangeEnd] = range; - /** - * @param {Comment} comment comment - * @param {number} needle needle - * @returns {number} compared - */ - const compare = (comment, needle) => - /** @type {Range} */ (comment.range)[0] - needle; - const comments = /** @type {Comment[]} */ (this.comments); - let idx = binarySearchBounds.ge(comments, rangeStart, compare); - /** @type {Comment[]} */ - const commentsInRange = []; - while ( - comments[idx] && - /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd - ) { - commentsInRange.push(comments[idx]); - idx++; - } - - return commentsInRange; - } - - /** - * @param {number} pos source code position - * @returns {boolean} true when a semicolon has been inserted before this position, false if not - */ - isAsiPosition(pos) { - const currentStatement = - /** @type {StatementPath} */ - (this.statementPath)[ - /** @type {StatementPath} */ - (this.statementPath).length - 1 - ]; - if (currentStatement === undefined) throw new Error("Not in statement"); - const range = /** @type {Range} */ (currentStatement.range); - - return ( - // Either asking directly for the end position of the current statement - (range[1] === pos && - /** @type {Set} */ (this.semicolons).has(pos)) || - // Or asking for the start position of the current statement, - // here we have to check multiple things - (range[0] === pos && - // is there a previous statement which might be relevant? - this.prevStatement !== undefined && - // is the end position of the previous statement an ASI position? - /** @type {Set} */ (this.semicolons).has( - /** @type {Range} */ (this.prevStatement.range)[1] - )) - ); - } - - /** - * @param {number} pos source code position - * @returns {void} - */ - setAsiPosition(pos) { - /** @type {Set} */ (this.semicolons).add(pos); - } - - /** - * @param {number} pos source code position - * @returns {void} - */ - unsetAsiPosition(pos) { - /** @type {Set} */ (this.semicolons).delete(pos); - } - - /** - * @param {Expression} expr expression - * @returns {boolean} true, when the expression is a statement level expression - */ - isStatementLevelExpression(expr) { - const currentStatement = - /** @type {StatementPath} */ - (this.statementPath)[ - /** @type {StatementPath} */ - (this.statementPath).length - 1 - ]; - return ( - expr === currentStatement || - (currentStatement.type === "ExpressionStatement" && - currentStatement.expression === expr) - ); - } - - /** - * @param {string} name name - * @param {symbol} tag tag info - * @returns {TODO} tag data - */ - getTagData(name, tag) { - const info = this.scope.definitions.get(name); - if (info instanceof VariableInfo) { - let tagInfo = info.tagInfo; - while (tagInfo !== undefined) { - if (tagInfo.tag === tag) return tagInfo.data; - tagInfo = tagInfo.next; - } - } - } - - /** - * @param {string} name name - * @param {symbol} tag tag info - * @param {TODO=} data data - */ - tagVariable(name, tag, data) { - const oldInfo = this.scope.definitions.get(name); - /** @type {VariableInfo} */ - let newInfo; - if (oldInfo === undefined) { - newInfo = new VariableInfo(this.scope, name, { - tag, - data, - next: undefined - }); - } else if (oldInfo instanceof VariableInfo) { - newInfo = new VariableInfo(oldInfo.declaredScope, oldInfo.freeName, { - tag, - data, - next: oldInfo.tagInfo - }); - } else { - newInfo = new VariableInfo(oldInfo, true, { - tag, - data, - next: undefined - }); - } - this.scope.definitions.set(name, newInfo); - } - - /** - * @param {string} name variable name - */ - defineVariable(name) { - const oldInfo = this.scope.definitions.get(name); - // Don't redefine variable in same scope to keep existing tags - if (oldInfo instanceof VariableInfo && oldInfo.declaredScope === this.scope) - return; - this.scope.definitions.set(name, this.scope); - } - - /** - * @param {string} name variable name - */ - undefineVariable(name) { - this.scope.definitions.delete(name); - } - - /** - * @param {string} name variable name - * @returns {boolean} true, when variable is defined - */ - isVariableDefined(name) { - const info = this.scope.definitions.get(name); - if (info === undefined) return false; - if (info instanceof VariableInfo) { - return info.freeName === true; - } - return true; - } - - /** - * @param {string} name variable name - * @returns {string | ExportedVariableInfo} info for this variable - */ - getVariableInfo(name) { - const value = this.scope.definitions.get(name); - if (value === undefined) { - return name; - } - return value; - } - - /** - * @param {string} name variable name - * @param {string | ExportedVariableInfo} variableInfo new info for this variable - * @returns {void} - */ - setVariable(name, variableInfo) { - if (typeof variableInfo === "string") { - if (variableInfo === name) { - this.scope.definitions.delete(name); - } else { - this.scope.definitions.set( - name, - new VariableInfo(this.scope, variableInfo, undefined) - ); - } - } else { - this.scope.definitions.set(name, variableInfo); - } - } - - /** - * @param {TagInfo} tagInfo tag info - * @returns {VariableInfo} variable info - */ - evaluatedVariable(tagInfo) { - return new VariableInfo(this.scope, undefined, tagInfo); - } - - /** - * @param {Range} range range of the comment - * @returns {{ options: Record | null, errors: (Error & { comment: Comment })[] | null }} result - */ - parseCommentOptions(range) { - const comments = this.getComments(range); - if (comments.length === 0) { - return EMPTY_COMMENT_OPTIONS; - } - /** @type {Record } */ - const options = {}; - /** @type {(Error & { comment: Comment })[]} */ - const errors = []; - for (const comment of comments) { - const { value } = comment; - if (value && webpackCommentRegExp.test(value)) { - // try compile only if webpack options comment is present - try { - for (let [key, val] of Object.entries( - vm.runInContext( - `(function(){return {${value}};})()`, - this.magicCommentContext - ) - )) { - if (typeof val === "object" && val !== null) { - val = - val.constructor.name === "RegExp" - ? new RegExp(val) - : JSON.parse(JSON.stringify(val)); - } - options[key] = val; - } - } catch (err) { - const newErr = new Error(String(/** @type {Error} */ (err).message)); - newErr.stack = String(/** @type {Error} */ (err).stack); - Object.assign(newErr, { comment }); - errors.push(/** @type {(Error & { comment: Comment })} */ (newErr)); - } - } - } - return { options, errors }; - } - - /** - * @param {Expression | Super} expression a member expression - * @returns {{ members: string[], object: Expression | Super, membersOptionals: boolean[], memberRanges: Range[] }} member names (reverse order) and remaining object - */ - extractMemberExpressionChain(expression) { - /** @type {Node} */ - let expr = expression; - const members = []; - const membersOptionals = []; - const memberRanges = []; - while (expr.type === "MemberExpression") { - if (expr.computed) { - if (expr.property.type !== "Literal") break; - members.push(`${expr.property.value}`); // the literal - memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the literal - } else { - if (expr.property.type !== "Identifier") break; - members.push(expr.property.name); // the identifier - memberRanges.push(/** @type {Range} */ (expr.object.range)); // the range of the expression fragment before the identifier - } - membersOptionals.push(expr.optional); - expr = expr.object; - } - - return { - members, - membersOptionals, - memberRanges, - object: expr - }; - } - - /** - * @param {string} varName variable name - * @returns {{name: string, info: VariableInfo | string} | undefined} name of the free variable and variable info for that - */ - getFreeInfoFromVariable(varName) { - const info = this.getVariableInfo(varName); - let name; - if (info instanceof VariableInfo) { - name = info.freeName; - if (typeof name !== "string") return; - } else if (typeof info !== "string") { - return; - } else { - name = info; - } - return { info, name }; - } - - /** @typedef {{ type: "call", call: CallExpression, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} CallExpressionInfo */ - /** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[], getMembersOptionals: () => boolean[], getMemberRanges: () => Range[]}} ExpressionExpressionInfo */ - - /** - * @param {Expression | Super} expression a member expression - * @param {number} allowedTypes which types should be returned, presented in bit mask - * @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info - */ - getMemberExpressionInfo(expression, allowedTypes) { - const { object, members, membersOptionals, memberRanges } = - this.extractMemberExpressionChain(expression); - switch (object.type) { - case "CallExpression": { - if ((allowedTypes & ALLOWED_MEMBER_TYPES_CALL_EXPRESSION) === 0) return; - let callee = object.callee; - let rootMembers = EMPTY_ARRAY; - if (callee.type === "MemberExpression") { - ({ object: callee, members: rootMembers } = - this.extractMemberExpressionChain(callee)); - } - const rootName = getRootName(callee); - if (!rootName) return; - const result = this.getFreeInfoFromVariable(rootName); - if (!result) return; - const { info: rootInfo, name: resolvedRoot } = result; - const calleeName = objectAndMembersToName(resolvedRoot, rootMembers); - return { - type: "call", - call: object, - calleeName, - rootInfo, - getCalleeMembers: memoize(() => rootMembers.reverse()), - name: objectAndMembersToName(`${calleeName}()`, members), - getMembers: memoize(() => members.reverse()), - getMembersOptionals: memoize(() => membersOptionals.reverse()), - getMemberRanges: memoize(() => memberRanges.reverse()) - }; - } - case "Identifier": - case "MetaProperty": - case "ThisExpression": { - if ((allowedTypes & ALLOWED_MEMBER_TYPES_EXPRESSION) === 0) return; - const rootName = getRootName(object); - if (!rootName) return; - - const result = this.getFreeInfoFromVariable(rootName); - if (!result) return; - const { info: rootInfo, name: resolvedRoot } = result; - return { - type: "expression", - name: objectAndMembersToName(resolvedRoot, members), - rootInfo, - getMembers: memoize(() => members.reverse()), - getMembersOptionals: memoize(() => membersOptionals.reverse()), - getMemberRanges: memoize(() => memberRanges.reverse()) - }; - } - } - } - - /** - * @param {MemberExpression} expression an expression - * @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]} | undefined} name info - */ - getNameForExpression(expression) { - return this.getMemberExpressionInfo( - expression, - ALLOWED_MEMBER_TYPES_EXPRESSION - ); - } - - /** - * @param {string} code source code - * @param {ParseOptions} options parsing options - * @returns {Program} parsed ast - */ - static _parse(code, options) { - const type = options ? options.sourceType : "module"; - /** @type {AcornOptions} */ - const parserOptions = { - ...defaultParserOptions, - allowReturnOutsideFunction: type === "script", - ...options, - sourceType: type === "auto" ? "module" : type - }; - - /** @type {import("acorn").Program | undefined} */ - let ast; - let error; - let threw = false; - try { - ast = parser.parse(code, parserOptions); - } catch (err) { - error = err; - threw = true; - } - - if (threw && type === "auto") { - parserOptions.sourceType = "script"; - if (!("allowReturnOutsideFunction" in options)) { - parserOptions.allowReturnOutsideFunction = true; - } - if (Array.isArray(parserOptions.onComment)) { - parserOptions.onComment.length = 0; - } - try { - ast = parser.parse(code, parserOptions); - threw = false; - } catch (_err) { - // we use the error from first parse try - // so nothing to do here - } - } - - if (threw) { - throw error; - } - - return /** @type {Program} */ (ast); - } -} - -module.exports = JavascriptParser; -module.exports.ALLOWED_MEMBER_TYPES_ALL = ALLOWED_MEMBER_TYPES_ALL; -module.exports.ALLOWED_MEMBER_TYPES_EXPRESSION = - ALLOWED_MEMBER_TYPES_EXPRESSION; -module.exports.ALLOWED_MEMBER_TYPES_CALL_EXPRESSION = - ALLOWED_MEMBER_TYPES_CALL_EXPRESSION; -module.exports.getImportAttributes = getImportAttributes; -module.exports.VariableInfo = VariableInfo; diff --git a/webpack-lib/lib/javascript/JavascriptParserHelpers.js b/webpack-lib/lib/javascript/JavascriptParserHelpers.js deleted file mode 100644 index 7028c4dd158..00000000000 --- a/webpack-lib/lib/javascript/JavascriptParserHelpers.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); -const ConstDependency = require("../dependencies/ConstDependency"); -const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); - -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").Node} Node */ -/** @typedef {import("estree").SourceLocation} SourceLocation */ -/** @typedef {import("./JavascriptParser")} JavascriptParser */ -/** @typedef {import("./JavascriptParser").Range} Range */ - -/** - * @param {JavascriptParser} parser the parser - * @param {string} value the const value - * @param {(string[] | null)=} runtimeRequirements runtime requirements - * @returns {function(Expression): true} plugin function - */ -module.exports.toConstantDependency = (parser, value, runtimeRequirements) => - function constDependency(expr) { - const dep = new ConstDependency( - value, - /** @type {Range} */ (expr.range), - runtimeRequirements - ); - dep.loc = /** @type {SourceLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - return true; - }; - -/** - * @param {string} value the string value - * @returns {function(Expression): BasicEvaluatedExpression} plugin function - */ -module.exports.evaluateToString = value => - function stringExpression(expr) { - return new BasicEvaluatedExpression() - .setString(value) - .setRange(/** @type {Range} */ (expr.range)); - }; - -/** - * @param {number} value the number value - * @returns {function(Expression): BasicEvaluatedExpression} plugin function - */ -module.exports.evaluateToNumber = value => - function stringExpression(expr) { - return new BasicEvaluatedExpression() - .setNumber(value) - .setRange(/** @type {Range} */ (expr.range)); - }; - -/** - * @param {boolean} value the boolean value - * @returns {function(Expression): BasicEvaluatedExpression} plugin function - */ -module.exports.evaluateToBoolean = value => - function booleanExpression(expr) { - return new BasicEvaluatedExpression() - .setBoolean(value) - .setRange(/** @type {Range} */ (expr.range)); - }; - -/** - * @param {string} identifier identifier - * @param {string} rootInfo rootInfo - * @param {function(): string[]} getMembers getMembers - * @param {boolean|null=} truthy is truthy, null if nullish - * @returns {function(Expression): BasicEvaluatedExpression} callback - */ -module.exports.evaluateToIdentifier = ( - identifier, - rootInfo, - getMembers, - truthy -) => - function identifierExpression(expr) { - const evaluatedExpression = new BasicEvaluatedExpression() - .setIdentifier(identifier, rootInfo, getMembers) - .setSideEffects(false) - .setRange(/** @type {Range} */ (expr.range)); - switch (truthy) { - case true: - evaluatedExpression.setTruthy(); - break; - case null: - evaluatedExpression.setNullish(true); - break; - case false: - evaluatedExpression.setFalsy(); - break; - } - - return evaluatedExpression; - }; - -/** - * @param {JavascriptParser} parser the parser - * @param {string} message the message - * @returns {function(Expression): boolean | undefined} callback to handle unsupported expression - */ -module.exports.expressionIsUnsupported = (parser, message) => - function unsupportedExpression(expr) { - const dep = new ConstDependency( - "(void 0)", - /** @type {Range} */ (expr.range), - null - ); - dep.loc = /** @type {SourceLocation} */ (expr.loc); - parser.state.module.addPresentationalDependency(dep); - if (!parser.state.module) return; - parser.state.module.addWarning( - new UnsupportedFeatureWarning( - message, - /** @type {SourceLocation} */ (expr.loc) - ) - ); - return true; - }; - -module.exports.skipTraversal = () => true; - -module.exports.approve = () => true; diff --git a/webpack-lib/lib/javascript/StartupHelpers.js b/webpack-lib/lib/javascript/StartupHelpers.js deleted file mode 100644 index f6f759d44bc..00000000000 --- a/webpack-lib/lib/javascript/StartupHelpers.js +++ /dev/null @@ -1,177 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const { isSubset } = require("../util/SetHelpers"); -const { getAllChunks } = require("./ChunkHelpers"); - -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../Entrypoint")} Entrypoint */ -/** @typedef {import("../ChunkGraph").EntryModuleWithChunkGroup} EntryModuleWithChunkGroup */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {(string|number)[]} EntryItem */ - -const EXPORT_PREFIX = `var ${RuntimeGlobals.exports} = `; - -/** @typedef {Set} Chunks */ -/** @typedef {ModuleId[]} ModuleIds */ - -/** - * @param {ChunkGraph} chunkGraph chunkGraph - * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate - * @param {EntryModuleWithChunkGroup[]} entries entries - * @param {Chunk} chunk chunk - * @param {boolean} passive true: passive startup with on chunks loaded - * @returns {string} runtime code - */ -module.exports.generateEntryStartup = ( - chunkGraph, - runtimeTemplate, - entries, - chunk, - passive -) => { - /** @type {string[]} */ - const runtime = [ - `var __webpack_exec__ = ${runtimeTemplate.returningFunction( - `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)`, - "moduleId" - )}` - ]; - - /** - * @param {ModuleId} id id - * @returns {string} fn to execute - */ - const runModule = id => `__webpack_exec__(${JSON.stringify(id)})`; - /** - * @param {Chunks} chunks chunks - * @param {ModuleIds} moduleIds module ids - * @param {boolean=} final true when final, otherwise false - */ - const outputCombination = (chunks, moduleIds, final) => { - if (chunks.size === 0) { - runtime.push( - `${final ? EXPORT_PREFIX : ""}(${moduleIds.map(runModule).join(", ")});` - ); - } else { - const fn = runtimeTemplate.returningFunction( - moduleIds.map(runModule).join(", ") - ); - runtime.push( - `${final && !passive ? EXPORT_PREFIX : ""}${ - passive - ? RuntimeGlobals.onChunksLoaded - : RuntimeGlobals.startupEntrypoint - }(0, ${JSON.stringify(Array.from(chunks, c => c.id))}, ${fn});` - ); - if (final && passive) { - runtime.push(`${EXPORT_PREFIX}${RuntimeGlobals.onChunksLoaded}();`); - } - } - }; - - /** @type {Chunks | undefined} */ - let currentChunks; - /** @type {ModuleIds | undefined} */ - let currentModuleIds; - - for (const [module, entrypoint] of entries) { - const runtimeChunk = - /** @type {Entrypoint} */ - (entrypoint).getRuntimeChunk(); - const moduleId = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - const chunks = getAllChunks( - /** @type {Entrypoint} */ - (entrypoint), - chunk, - runtimeChunk - ); - if ( - currentChunks && - currentChunks.size === chunks.size && - isSubset(currentChunks, chunks) - ) { - /** @type {ModuleIds} */ - (currentModuleIds).push(moduleId); - } else { - if (currentChunks) { - outputCombination( - currentChunks, - /** @type {ModuleIds} */ (currentModuleIds) - ); - } - currentChunks = chunks; - currentModuleIds = [moduleId]; - } - } - - // output current modules with export prefix - if (currentChunks) { - outputCombination( - currentChunks, - /** @type {ModuleIds} */ - (currentModuleIds), - true - ); - } - runtime.push(""); - return Template.asString(runtime); -}; - -/** - * @param {Hash} hash the hash to update - * @param {ChunkGraph} chunkGraph chunkGraph - * @param {EntryModuleWithChunkGroup[]} entries entries - * @param {Chunk} chunk chunk - * @returns {void} - */ -module.exports.updateHashForEntryStartup = ( - hash, - chunkGraph, - entries, - chunk -) => { - for (const [module, entrypoint] of entries) { - const runtimeChunk = - /** @type {Entrypoint} */ - (entrypoint).getRuntimeChunk(); - const moduleId = chunkGraph.getModuleId(module); - hash.update(`${moduleId}`); - for (const c of getAllChunks( - /** @type {Entrypoint} */ (entrypoint), - chunk, - /** @type {Chunk} */ (runtimeChunk) - )) { - hash.update(`${c.id}`); - } - } -}; - -/** - * @param {Chunk} chunk the chunk - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {function(Chunk, ChunkGraph): boolean} filterFn filter function - * @returns {Set} initially fulfilled chunk ids - */ -module.exports.getInitialChunkIds = (chunk, chunkGraph, filterFn) => { - const initialChunkIds = new Set(chunk.ids); - for (const c of chunk.getAllInitialChunks()) { - if (c === chunk || filterFn(c, chunkGraph)) continue; - for (const id of /** @type {ChunkId[]} */ (c.ids)) { - initialChunkIds.add(id); - } - } - return initialChunkIds; -}; diff --git a/webpack-lib/lib/json/JsonData.js b/webpack-lib/lib/json/JsonData.js deleted file mode 100644 index cb39dfc011b..00000000000 --- a/webpack-lib/lib/json/JsonData.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { register } = require("../util/serialization"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ - -class JsonData { - /** - * @param {Buffer | RawJsonData} data JSON data - */ - constructor(data) { - /** @type {Buffer | undefined} */ - this._buffer = undefined; - /** @type {RawJsonData | undefined} */ - this._data = undefined; - if (Buffer.isBuffer(data)) { - this._buffer = data; - } else { - this._data = data; - } - } - - /** - * @returns {RawJsonData|undefined} Raw JSON data - */ - get() { - if (this._data === undefined && this._buffer !== undefined) { - this._data = JSON.parse(this._buffer.toString()); - } - return this._data; - } - - /** - * @param {Hash} hash hash to be updated - * @returns {void} the updated hash - */ - updateHash(hash) { - if (this._buffer === undefined && this._data !== undefined) { - this._buffer = Buffer.from(JSON.stringify(this._data)); - } - - if (this._buffer) hash.update(this._buffer); - } -} - -register(JsonData, "webpack/lib/json/JsonData", null, { - /** - * @param {JsonData} obj JSONData object - * @param {ObjectSerializerContext} context context - */ - serialize(obj, { write }) { - if (obj._buffer === undefined && obj._data !== undefined) { - obj._buffer = Buffer.from(JSON.stringify(obj._data)); - } - write(obj._buffer); - }, - /** - * @param {ObjectDeserializerContext} context context - * @returns {JsonData} deserialized JSON data - */ - deserialize({ read }) { - return new JsonData(read()); - } -}); - -module.exports = JsonData; diff --git a/webpack-lib/lib/json/JsonGenerator.js b/webpack-lib/lib/json/JsonGenerator.js deleted file mode 100644 index b16d406e7cb..00000000000 --- a/webpack-lib/lib/json/JsonGenerator.js +++ /dev/null @@ -1,199 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const ConcatenationScope = require("../ConcatenationScope"); -const { UsageState } = require("../ExportsInfo"); -const Generator = require("../Generator"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../ExportsInfo")} ExportsInfo */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./JsonData")} JsonData */ -/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ - -/** - * @param {RawJsonData} data Raw JSON data - * @returns {undefined|string} stringified data - */ -const stringifySafe = data => { - const stringified = JSON.stringify(data); - if (!stringified) { - return; // Invalid JSON - } - - return stringified.replace(/\u2028|\u2029/g, str => - str === "\u2029" ? "\\u2029" : "\\u2028" - ); // invalid in JavaScript but valid JSON -}; - -/** - * @param {RawJsonData} data Raw JSON data (always an object or array) - * @param {ExportsInfo} exportsInfo exports info - * @param {RuntimeSpec} runtime the runtime - * @returns {RawJsonData} reduced data - */ -const createObjectForExportsInfo = (data, exportsInfo, runtime) => { - if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) - return data; - const isArray = Array.isArray(data); - /** @type {RawJsonData} */ - const reducedData = isArray ? [] : {}; - for (const key of Object.keys(data)) { - const exportInfo = exportsInfo.getReadOnlyExportInfo(key); - const used = exportInfo.getUsed(runtime); - if (used === UsageState.Unused) continue; - - /** @type {RawJsonData} */ - const value = - used === UsageState.OnlyPropertiesUsed && exportInfo.exportsInfo - ? createObjectForExportsInfo(data[key], exportInfo.exportsInfo, runtime) - : data[key]; - - const name = /** @type {string} */ (exportInfo.getUsedName(key, runtime)); - /** @type {Record} */ (reducedData)[name] = value; - } - if (isArray) { - const arrayLengthWhenUsed = - exportsInfo.getReadOnlyExportInfo("length").getUsed(runtime) !== - UsageState.Unused - ? data.length - : undefined; - - let sizeObjectMinusArray = 0; - for (let i = 0; i < reducedData.length; i++) { - if (reducedData[i] === undefined) { - sizeObjectMinusArray -= 2; - } else { - sizeObjectMinusArray += `${i}`.length + 3; - } - } - if (arrayLengthWhenUsed !== undefined) { - sizeObjectMinusArray += - `${arrayLengthWhenUsed}`.length + - 8 - - (arrayLengthWhenUsed - reducedData.length) * 2; - } - if (sizeObjectMinusArray < 0) - return Object.assign( - arrayLengthWhenUsed === undefined - ? {} - : { length: arrayLengthWhenUsed }, - reducedData - ); - /** @type {number} */ - const generatedLength = - arrayLengthWhenUsed !== undefined - ? Math.max(arrayLengthWhenUsed, reducedData.length) - : reducedData.length; - for (let i = 0; i < generatedLength; i++) { - if (reducedData[i] === undefined) { - reducedData[i] = 0; - } - } - } - return reducedData; -}; - -class JsonGenerator extends Generator { - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return JS_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - /** @type {RawJsonData | undefined} */ - const data = - module.buildInfo && - module.buildInfo.jsonData && - module.buildInfo.jsonData.get(); - if (!data) return 0; - return /** @type {string} */ (stringifySafe(data)).length + 10; - } - - /** - * @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; - } - - /** - * @param {NormalModule} module module for which the code should be generated - * @param {GenerateContext} generateContext context for generate - * @returns {Source | null} generated code - */ - generate( - module, - { - moduleGraph, - runtimeTemplate, - runtimeRequirements, - runtime, - concatenationScope - } - ) { - /** @type {RawJsonData | undefined} */ - const data = - module.buildInfo && - module.buildInfo.jsonData && - module.buildInfo.jsonData.get(); - if (data === undefined) { - return new RawSource( - runtimeTemplate.missingModuleStatement({ - request: module.rawRequest - }) - ); - } - const exportsInfo = moduleGraph.getExportsInfo(module); - /** @type {RawJsonData} */ - const finalJson = - typeof data === "object" && - data && - exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused - ? createObjectForExportsInfo(data, exportsInfo, runtime) - : data; - // Use JSON because JSON.parse() is much faster than JavaScript evaluation - const jsonStr = /** @type {string} */ (stringifySafe(finalJson)); - const jsonExpr = - jsonStr.length > 20 && typeof finalJson === "object" - ? `/*#__PURE__*/JSON.parse('${jsonStr.replace(/[\\']/g, "\\$&")}')` - : jsonStr; - /** @type {string} */ - let content; - if (concatenationScope) { - content = `${runtimeTemplate.supportsConst() ? "const" : "var"} ${ - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - } = ${jsonExpr};`; - concatenationScope.registerNamespaceExport( - ConcatenationScope.NAMESPACE_OBJECT_EXPORT - ); - } else { - runtimeRequirements.add(RuntimeGlobals.module); - content = `${module.moduleArgument}.exports = ${jsonExpr};`; - } - return new RawSource(content); - } -} - -module.exports = JsonGenerator; diff --git a/webpack-lib/lib/json/JsonModulesPlugin.js b/webpack-lib/lib/json/JsonModulesPlugin.js deleted file mode 100644 index a33c0e33e7d..00000000000 --- a/webpack-lib/lib/json/JsonModulesPlugin.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { JSON_MODULE_TYPE } = require("../ModuleTypeConstants"); -const createSchemaValidation = require("../util/create-schema-validation"); -const JsonGenerator = require("./JsonGenerator"); -const JsonParser = require("./JsonParser"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {Record} RawJsonData */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/JsonModulesPluginParser.check.js"), - () => require("../../schemas/plugins/JsonModulesPluginParser.json"), - { - name: "Json Modules Plugin", - baseDataPath: "parser" - } -); - -const PLUGIN_NAME = "JsonModulesPlugin"; - -/** - * The JsonModulesPlugin is the entrypoint plugin for the json modules feature. - * It adds the json module type to the compiler and registers the json parser and generator. - */ -class JsonModulesPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.createParser - .for(JSON_MODULE_TYPE) - .tap(PLUGIN_NAME, parserOptions => { - validate(parserOptions); - return new JsonParser(parserOptions); - }); - normalModuleFactory.hooks.createGenerator - .for(JSON_MODULE_TYPE) - .tap(PLUGIN_NAME, () => new JsonGenerator()); - } - ); - } -} - -module.exports = JsonModulesPlugin; diff --git a/webpack-lib/lib/json/JsonParser.js b/webpack-lib/lib/json/JsonParser.js deleted file mode 100644 index 93a4fb73489..00000000000 --- a/webpack-lib/lib/json/JsonParser.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Parser = require("../Parser"); -const JsonExportsDependency = require("../dependencies/JsonExportsDependency"); -const memoize = require("../util/memoize"); -const JsonData = require("./JsonData"); - -/** @typedef {import("../../declarations/plugins/JsonModulesPluginParser").JsonModulesPluginParserOptions} JsonModulesPluginParserOptions */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ -/** @typedef {import("./JsonModulesPlugin").RawJsonData} RawJsonData */ - -const getParseJson = memoize(() => require("json-parse-even-better-errors")); - -class JsonParser extends Parser { - /** - * @param {JsonModulesPluginParserOptions} options parser options - */ - constructor(options) { - super(); - this.options = options || {}; - } - - /** - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - if (Buffer.isBuffer(source)) { - source = source.toString("utf-8"); - } - - /** @type {NonNullable} */ - const parseFn = - typeof this.options.parse === "function" - ? this.options.parse - : getParseJson(); - /** @type {Buffer | RawJsonData | undefined} */ - let data; - try { - data = - typeof source === "object" - ? source - : parseFn(source[0] === "\uFEFF" ? source.slice(1) : source); - } catch (err) { - throw new Error( - `Cannot parse JSON: ${/** @type {Error} */ (err).message}` - ); - } - const jsonData = new JsonData(/** @type {Buffer | RawJsonData} */ (data)); - const buildInfo = /** @type {BuildInfo} */ (state.module.buildInfo); - buildInfo.jsonData = jsonData; - buildInfo.strict = true; - const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); - buildMeta.exportsType = "default"; - buildMeta.defaultObject = - typeof data === "object" ? "redirect-warn" : false; - state.module.addDependency( - new JsonExportsDependency(jsonData, this.options.exportsDepth) - ); - return state; - } -} - -module.exports = JsonParser; diff --git a/webpack-lib/lib/library/AbstractLibraryPlugin.js b/webpack-lib/lib/library/AbstractLibraryPlugin.js deleted file mode 100644 index fbd88a4bb82..00000000000 --- a/webpack-lib/lib/library/AbstractLibraryPlugin.js +++ /dev/null @@ -1,301 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ -/** @typedef {import("../util/Hash")} Hash */ - -const COMMON_LIBRARY_NAME_MESSAGE = - "Common configuration options that specific library names are 'output.library[.name]', 'entry.xyz.library[.name]', 'ModuleFederationPlugin.name' and 'ModuleFederationPlugin.library[.name]'."; - -/** - * @template T - * @typedef {object} LibraryContext - * @property {Compilation} compilation - * @property {ChunkGraph} chunkGraph - * @property {T} options - */ - -/** - * @template T - */ -class AbstractLibraryPlugin { - /** - * @param {object} options options - * @param {string} options.pluginName name of the plugin - * @param {LibraryType} options.type used library type - */ - constructor({ pluginName, type }) { - this._pluginName = pluginName; - this._type = type; - this._parseCache = new WeakMap(); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _pluginName } = this; - compiler.hooks.thisCompilation.tap(_pluginName, compilation => { - compilation.hooks.finishModules.tap( - { name: _pluginName, stage: 10 }, - () => { - for (const [ - name, - { - dependencies: deps, - options: { library } - } - ] of compilation.entries) { - const options = this._parseOptionsCached( - library !== undefined - ? library - : compilation.outputOptions.library - ); - if (options !== false) { - const dep = deps[deps.length - 1]; - if (dep) { - const module = compilation.moduleGraph.getModule(dep); - if (module) { - this.finishEntryModule(module, name, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - } - } - } - } - } - ); - - /** - * @param {Chunk} chunk chunk - * @returns {TODO} options for the chunk - */ - const getOptionsForChunk = chunk => { - if (compilation.chunkGraph.getNumberOfEntryModules(chunk) === 0) - return false; - const options = chunk.getEntryOptions(); - const library = options && options.library; - return this._parseOptionsCached( - library !== undefined ? library : compilation.outputOptions.library - ); - }; - - if ( - this.render !== AbstractLibraryPlugin.prototype.render || - this.runtimeRequirements !== - AbstractLibraryPlugin.prototype.runtimeRequirements - ) { - compilation.hooks.additionalChunkRuntimeRequirements.tap( - _pluginName, - (chunk, set, { chunkGraph }) => { - const options = getOptionsForChunk(chunk); - if (options !== false) { - this.runtimeRequirements(chunk, set, { - options, - compilation, - chunkGraph - }); - } - } - ); - } - - const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); - - if (this.render !== AbstractLibraryPlugin.prototype.render) { - hooks.render.tap(_pluginName, (source, renderContext) => { - const options = getOptionsForChunk(renderContext.chunk); - if (options === false) return source; - return this.render(source, renderContext, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - }); - } - - if ( - this.embedInRuntimeBailout !== - AbstractLibraryPlugin.prototype.embedInRuntimeBailout - ) { - hooks.embedInRuntimeBailout.tap( - _pluginName, - (module, renderContext) => { - const options = getOptionsForChunk(renderContext.chunk); - if (options === false) return; - return this.embedInRuntimeBailout(module, renderContext, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - } - ); - } - - if ( - this.strictRuntimeBailout !== - AbstractLibraryPlugin.prototype.strictRuntimeBailout - ) { - hooks.strictRuntimeBailout.tap(_pluginName, renderContext => { - const options = getOptionsForChunk(renderContext.chunk); - if (options === false) return; - return this.strictRuntimeBailout(renderContext, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - }); - } - - if ( - this.renderStartup !== AbstractLibraryPlugin.prototype.renderStartup - ) { - hooks.renderStartup.tap( - _pluginName, - (source, module, renderContext) => { - const options = getOptionsForChunk(renderContext.chunk); - if (options === false) return source; - return this.renderStartup(source, module, renderContext, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - } - ); - } - - hooks.chunkHash.tap(_pluginName, (chunk, hash, context) => { - const options = getOptionsForChunk(chunk); - if (options === false) return; - this.chunkHash(chunk, hash, context, { - options, - compilation, - chunkGraph: compilation.chunkGraph - }); - }); - }); - } - - /** - * @param {LibraryOptions=} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - _parseOptionsCached(library) { - if (!library) return false; - if (library.type !== this._type) return false; - const cacheEntry = this._parseCache.get(library); - if (cacheEntry !== undefined) return cacheEntry; - const result = this.parseOptions(library); - this._parseCache.set(library, result); - return result; - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const AbstractMethodError = require("../AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @param {Module} module the exporting entry module - * @param {string} entryName the name of the entrypoint - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - finishEntryModule(module, entryName, libraryContext) {} - - /** - * @param {Module} module the exporting entry module - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {string | undefined} bailout reason - */ - embedInRuntimeBailout(module, renderContext, libraryContext) { - return undefined; - } - - /** - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {string | undefined} bailout reason - */ - strictRuntimeBailout(renderContext, libraryContext) { - return undefined; - } - - /** - * @param {Chunk} chunk the chunk - * @param {Set} set runtime requirements - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - runtimeRequirements(chunk, set, libraryContext) { - if (this.render !== AbstractLibraryPlugin.prototype.render) - set.add(RuntimeGlobals.returnExportsFromRuntime); - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render(source, renderContext, libraryContext) { - return source; - } - - /** - * @param {Source} source source - * @param {Module} module module - * @param {StartupRenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - renderStartup(source, module, renderContext, libraryContext) { - return source; - } - - /** - * @param {Chunk} chunk the chunk - * @param {Hash} hash hash - * @param {ChunkHashContext} chunkHashContext chunk hash context - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - chunkHash(chunk, hash, chunkHashContext, libraryContext) { - const options = this._parseOptionsCached( - libraryContext.compilation.outputOptions.library - ); - hash.update(this._pluginName); - hash.update(JSON.stringify(options)); - } -} - -AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE = COMMON_LIBRARY_NAME_MESSAGE; -module.exports = AbstractLibraryPlugin; diff --git a/webpack-lib/lib/library/AmdLibraryPlugin.js b/webpack-lib/lib/library/AmdLibraryPlugin.js deleted file mode 100644 index d604c036c77..00000000000 --- a/webpack-lib/lib/library/AmdLibraryPlugin.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const ExternalModule = require("../ExternalModule"); -const Template = require("../Template"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} AmdLibraryPluginOptions - * @property {LibraryType} type - * @property {boolean=} requireAsWrapper - */ - -/** - * @typedef {object} AmdLibraryPluginParsed - * @property {string} name - * @property {string} amdContainer - */ - -/** - * @typedef {AmdLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class AmdLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {AmdLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "AmdLibraryPlugin", - type: options.type - }); - this.requireAsWrapper = options.requireAsWrapper; - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name, amdContainer } = library; - if (this.requireAsWrapper) { - if (name) { - throw new Error( - `AMD library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - } else if (name && typeof name !== "string") { - throw new Error( - `AMD library name must be a simple string or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string} */ (name); - const _amdContainer = /** @type {string} */ (amdContainer); - return { name: _name, amdContainer: _amdContainer }; - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render( - source, - { chunkGraph, chunk, runtimeTemplate }, - { options, compilation } - ) { - const modern = runtimeTemplate.supportsArrowFunction(); - const modules = chunkGraph - .getChunkModules(chunk) - .filter( - m => - m instanceof ExternalModule && - (m.externalType === "amd" || m.externalType === "amd-require") - ); - const externals = /** @type {ExternalModule[]} */ (modules); - const externalsDepsArray = JSON.stringify( - externals.map(m => - typeof m.request === "object" && !Array.isArray(m.request) - ? m.request.amd - : m.request - ) - ); - const externalsArguments = externals - .map( - m => - `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( - `${chunkGraph.getModuleId(m)}` - )}__` - ) - .join(", "); - - const iife = runtimeTemplate.isIIFE(); - const fnStart = - (modern - ? `(${externalsArguments}) => {` - : `function(${externalsArguments}) {`) + - (iife || !chunk.hasRuntime() ? " return " : "\n"); - const fnEnd = iife ? ";\n}" : "\n}"; - - let amdContainerPrefix = ""; - if (options.amdContainer) { - amdContainerPrefix = `${options.amdContainer}.`; - } - - if (this.requireAsWrapper) { - return new ConcatSource( - `${amdContainerPrefix}require(${externalsDepsArray}, ${fnStart}`, - source, - `${fnEnd});` - ); - } else if (options.name) { - const name = compilation.getPath(options.name, { - chunk - }); - - return new ConcatSource( - `${amdContainerPrefix}define(${JSON.stringify( - name - )}, ${externalsDepsArray}, ${fnStart}`, - source, - `${fnEnd});` - ); - } else if (externalsArguments) { - return new ConcatSource( - `${amdContainerPrefix}define(${externalsDepsArray}, ${fnStart}`, - source, - `${fnEnd});` - ); - } - return new ConcatSource( - `${amdContainerPrefix}define(${fnStart}`, - source, - `${fnEnd});` - ); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Hash} hash hash - * @param {ChunkHashContext} chunkHashContext chunk hash context - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { - hash.update("AmdLibraryPlugin"); - if (this.requireAsWrapper) { - hash.update("requireAsWrapper"); - } else if (options.name) { - hash.update("named"); - const name = compilation.getPath(options.name, { - chunk - }); - hash.update(name); - } else if (options.amdContainer) { - hash.update("amdContainer"); - hash.update(options.amdContainer); - } - } -} - -module.exports = AmdLibraryPlugin; diff --git a/webpack-lib/lib/library/AssignLibraryPlugin.js b/webpack-lib/lib/library/AssignLibraryPlugin.js deleted file mode 100644 index abdcfcf41a8..00000000000 --- a/webpack-lib/lib/library/AssignLibraryPlugin.js +++ /dev/null @@ -1,387 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const { UsageState } = require("../ExportsInfo"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const propertyAccess = require("../util/propertyAccess"); -const { getEntryRuntime } = require("../util/runtime"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -const KEYWORD_REGEX = - /^(await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|super|switch|static|this|throw|try|true|typeof|var|void|while|with|yield)$/; -const IDENTIFIER_REGEX = - /^[\p{L}\p{Nl}$_][\p{L}\p{Nl}$\p{Mn}\p{Mc}\p{Nd}\p{Pc}]*$/iu; - -/** - * Validates the library name by checking for keywords and valid characters - * @param {string} name name to be validated - * @returns {boolean} true, when valid - */ -const isNameValid = name => - !KEYWORD_REGEX.test(name) && IDENTIFIER_REGEX.test(name); - -/** - * @param {string[]} accessor variable plus properties - * @param {number} existingLength items of accessor that are existing already - * @param {boolean=} initLast if the last property should also be initialized to an object - * @returns {string} code to access the accessor while initializing - */ -const accessWithInit = (accessor, existingLength, initLast = false) => { - // This generates for [a, b, c, d]: - // (((a = typeof a === "undefined" ? {} : a).b = a.b || {}).c = a.b.c || {}).d - const base = accessor[0]; - if (accessor.length === 1 && !initLast) return base; - let current = - existingLength > 0 - ? base - : `(${base} = typeof ${base} === "undefined" ? {} : ${base})`; - - // i is the current position in accessor that has been printed - let i = 1; - - // all properties printed so far (excluding base) - /** @type {string[] | undefined} */ - let propsSoFar; - - // if there is existingLength, print all properties until this position as property access - if (existingLength > i) { - propsSoFar = accessor.slice(1, existingLength); - i = existingLength; - current += propertyAccess(propsSoFar); - } else { - propsSoFar = []; - } - - // all remaining properties (except the last one when initLast is not set) - // should be printed as initializer - const initUntil = initLast ? accessor.length : accessor.length - 1; - for (; i < initUntil; i++) { - const prop = accessor[i]; - propsSoFar.push(prop); - current = `(${current}${propertyAccess([prop])} = ${base}${propertyAccess( - propsSoFar - )} || {})`; - } - - // print the last property as property access if not yet printed - if (i < accessor.length) - current = `${current}${propertyAccess([accessor[accessor.length - 1]])}`; - - return current; -}; - -/** - * @typedef {object} AssignLibraryPluginOptions - * @property {LibraryType} type - * @property {string[] | "global"} prefix name prefix - * @property {string | false} declare declare name as variable - * @property {"error"|"static"|"copy"|"assign"} unnamed behavior for unnamed library name - * @property {"copy"|"assign"=} named behavior for named library name - */ - -/** - * @typedef {object} AssignLibraryPluginParsed - * @property {string | string[]} name - * @property {string | string[] | undefined} export - */ - -/** - * @typedef {AssignLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class AssignLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {AssignLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "AssignLibraryPlugin", - type: options.type - }); - this.prefix = options.prefix; - this.declare = options.declare; - this.unnamed = options.unnamed; - this.named = options.named || "assign"; - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name } = library; - if (this.unnamed === "error") { - if (typeof name !== "string" && !Array.isArray(name)) { - throw new Error( - `Library name must be a string or string array. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - } else if (name && typeof name !== "string" && !Array.isArray(name)) { - throw new Error( - `Library name must be a string, string array or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string | string[]} */ (name); - return { - name: _name, - export: library.export - }; - } - - /** - * @param {Module} module the exporting entry module - * @param {string} entryName the name of the entrypoint - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - finishEntryModule( - module, - entryName, - { options, compilation, compilation: { moduleGraph } } - ) { - const runtime = getEntryRuntime(compilation, entryName); - if (options.export) { - const exportsInfo = moduleGraph.getExportInfo( - module, - Array.isArray(options.export) ? options.export[0] : options.export - ); - exportsInfo.setUsed(UsageState.Used, runtime); - exportsInfo.canMangleUse = false; - } else { - const exportsInfo = moduleGraph.getExportsInfo(module); - exportsInfo.setUsedInUnknownWay(runtime); - } - moduleGraph.addExtraReason(module, "used as library export"); - } - - /** - * @param {Compilation} compilation the compilation - * @returns {string[]} the prefix - */ - _getPrefix(compilation) { - return this.prefix === "global" - ? [compilation.runtimeTemplate.globalObject] - : this.prefix; - } - - /** - * @param {AssignLibraryPluginParsed} options the library options - * @param {Chunk} chunk the chunk - * @param {Compilation} compilation the compilation - * @returns {Array} the resolved full name - */ - _getResolvedFullName(options, chunk, compilation) { - const prefix = this._getPrefix(compilation); - const fullName = options.name ? prefix.concat(options.name) : prefix; - return fullName.map(n => - compilation.getPath(n, { - chunk - }) - ); - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render(source, { chunk }, { options, compilation }) { - const fullNameResolved = this._getResolvedFullName( - options, - chunk, - compilation - ); - if (this.declare) { - const base = fullNameResolved[0]; - if (!isNameValid(base)) { - throw new Error( - `Library name base (${base}) must be a valid identifier when using a var declaring library type. Either use a valid identifier (e. g. ${Template.toIdentifier( - base - )}) or use a different library type (e. g. 'type: "global"', which assign a property on the global scope instead of declaring a variable). ${ - AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE - }` - ); - } - source = new ConcatSource(`${this.declare} ${base};\n`, source); - } - return source; - } - - /** - * @param {Module} module the exporting entry module - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {string | undefined} bailout reason - */ - embedInRuntimeBailout( - module, - { chunk, codeGenerationResults }, - { options, compilation } - ) { - const { data } = codeGenerationResults.get(module, chunk.runtime); - const topLevelDeclarations = - (data && data.get("topLevelDeclarations")) || - (module.buildInfo && module.buildInfo.topLevelDeclarations); - if (!topLevelDeclarations) - return "it doesn't tell about top level declarations."; - const fullNameResolved = this._getResolvedFullName( - options, - chunk, - compilation - ); - const base = fullNameResolved[0]; - if (topLevelDeclarations.has(base)) - return `it declares '${base}' on top-level, which conflicts with the current library output.`; - } - - /** - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {string | undefined} bailout reason - */ - strictRuntimeBailout({ chunk }, { options, compilation }) { - if ( - this.declare || - this.prefix === "global" || - this.prefix.length > 0 || - !options.name - ) { - return; - } - return "a global variable is assign and maybe created"; - } - - /** - * @param {Source} source source - * @param {Module} module module - * @param {StartupRenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - renderStartup( - source, - module, - { moduleGraph, chunk }, - { options, compilation } - ) { - const fullNameResolved = this._getResolvedFullName( - options, - chunk, - compilation - ); - const staticExports = this.unnamed === "static"; - const exportAccess = options.export - ? propertyAccess( - Array.isArray(options.export) ? options.export : [options.export] - ) - : ""; - const result = new ConcatSource(source); - if (staticExports) { - const exportsInfo = moduleGraph.getExportsInfo(module); - const exportTarget = accessWithInit( - fullNameResolved, - this._getPrefix(compilation).length, - true - ); - for (const exportInfo of exportsInfo.orderedExports) { - if (!exportInfo.provided) continue; - const nameAccess = propertyAccess([exportInfo.name]); - result.add( - `${exportTarget}${nameAccess} = ${RuntimeGlobals.exports}${exportAccess}${nameAccess};\n` - ); - } - result.add( - `Object.defineProperty(${exportTarget}, "__esModule", { value: true });\n` - ); - } else if (options.name ? this.named === "copy" : this.unnamed === "copy") { - result.add( - `var __webpack_export_target__ = ${accessWithInit( - fullNameResolved, - this._getPrefix(compilation).length, - true - )};\n` - ); - /** @type {string} */ - let exports = RuntimeGlobals.exports; - if (exportAccess) { - result.add( - `var __webpack_exports_export__ = ${RuntimeGlobals.exports}${exportAccess};\n` - ); - exports = "__webpack_exports_export__"; - } - result.add( - `for(var __webpack_i__ in ${exports}) __webpack_export_target__[__webpack_i__] = ${exports}[__webpack_i__];\n` - ); - result.add( - `if(${exports}.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });\n` - ); - } else { - result.add( - `${accessWithInit( - fullNameResolved, - this._getPrefix(compilation).length, - false - )} = ${RuntimeGlobals.exports}${exportAccess};\n` - ); - } - return result; - } - - /** - * @param {Chunk} chunk the chunk - * @param {Set} set runtime requirements - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - runtimeRequirements(chunk, set, libraryContext) { - set.add(RuntimeGlobals.exports); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Hash} hash hash - * @param {ChunkHashContext} chunkHashContext chunk hash context - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { - hash.update("AssignLibraryPlugin"); - const fullNameResolved = this._getResolvedFullName( - options, - chunk, - compilation - ); - if (options.name ? this.named === "copy" : this.unnamed === "copy") { - hash.update("copy"); - } - if (this.declare) { - hash.update(this.declare); - } - hash.update(fullNameResolved.join(".")); - if (options.export) { - hash.update(`${options.export}`); - } - } -} - -module.exports = AssignLibraryPlugin; diff --git a/webpack-lib/lib/library/EnableLibraryPlugin.js b/webpack-lib/lib/library/EnableLibraryPlugin.js deleted file mode 100644 index a772eac8ee8..00000000000 --- a/webpack-lib/lib/library/EnableLibraryPlugin.js +++ /dev/null @@ -1,279 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Compiler")} Compiler */ - -/** @type {WeakMap>} */ -const enabledTypes = new WeakMap(); - -/** - * @param {Compiler} compiler the compiler instance - * @returns {Set} enabled types - */ -const getEnabledTypes = compiler => { - let set = enabledTypes.get(compiler); - if (set === undefined) { - set = new Set(); - enabledTypes.set(compiler, set); - } - return set; -}; - -class EnableLibraryPlugin { - /** - * @param {LibraryType} type library type that should be available - */ - constructor(type) { - this.type = type; - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {LibraryType} type type of library - * @returns {void} - */ - static setEnabled(compiler, type) { - getEnabledTypes(compiler).add(type); - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {LibraryType} type type of library - * @returns {void} - */ - static checkEnabled(compiler, type) { - if (!getEnabledTypes(compiler).has(type)) { - throw new Error( - `Library type "${type}" is not enabled. ` + - "EnableLibraryPlugin need to be used to enable this type of library. " + - 'This usually happens through the "output.enabledLibraryTypes" option. ' + - 'If you are using a function as entry which sets "library", you need to add all potential library types to "output.enabledLibraryTypes". ' + - `These types are enabled: ${Array.from( - getEnabledTypes(compiler) - ).join(", ")}` - ); - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { type } = this; - - // Only enable once - const enabled = getEnabledTypes(compiler); - if (enabled.has(type)) return; - enabled.add(type); - - if (typeof type === "string") { - const enableExportProperty = () => { - const ExportPropertyTemplatePlugin = require("./ExportPropertyLibraryPlugin"); - new ExportPropertyTemplatePlugin({ - type, - nsObjectUsed: !["module", "modern-module"].includes(type), - runtimeExportsUsed: type !== "modern-module" - }).apply(compiler); - }; - switch (type) { - case "var": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: [], - declare: "var", - unnamed: "error" - }).apply(compiler); - break; - } - case "assign-properties": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: [], - declare: false, - unnamed: "error", - named: "copy" - }).apply(compiler); - break; - } - case "assign": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: [], - declare: false, - unnamed: "error" - }).apply(compiler); - break; - } - case "this": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["this"], - declare: false, - unnamed: "copy" - }).apply(compiler); - break; - } - case "window": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["window"], - declare: false, - unnamed: "copy" - }).apply(compiler); - break; - } - case "self": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["self"], - declare: false, - unnamed: "copy" - }).apply(compiler); - break; - } - case "global": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: "global", - declare: false, - unnamed: "copy" - }).apply(compiler); - break; - } - case "commonjs": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["exports"], - declare: false, - unnamed: "copy" - }).apply(compiler); - break; - } - case "commonjs-static": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["exports"], - declare: false, - unnamed: "static" - }).apply(compiler); - break; - } - case "commonjs2": - case "commonjs-module": { - // @ts-expect-error https://github.com/microsoft/TypeScript/issues/41697 - const AssignLibraryPlugin = require("./AssignLibraryPlugin"); - new AssignLibraryPlugin({ - type, - prefix: ["module", "exports"], - declare: false, - unnamed: "assign" - }).apply(compiler); - break; - } - case "amd": - case "amd-require": { - enableExportProperty(); - const AmdLibraryPlugin = require("./AmdLibraryPlugin"); - new AmdLibraryPlugin({ - type, - requireAsWrapper: type === "amd-require" - }).apply(compiler); - break; - } - case "umd": - case "umd2": { - if (compiler.options.output.iife === false) { - compiler.options.output.iife = true; - - class WarnFalseIifeUmdPlugin { - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "WarnFalseIifeUmdPlugin", - compilation => { - const FalseIIFEUmdWarning = require("../FalseIIFEUmdWarning"); - compilation.warnings.push(new FalseIIFEUmdWarning()); - } - ); - } - } - - new WarnFalseIifeUmdPlugin().apply(compiler); - } - enableExportProperty(); - const UmdLibraryPlugin = require("./UmdLibraryPlugin"); - new UmdLibraryPlugin({ - type, - optionalAmdExternalAsGlobal: type === "umd2" - }).apply(compiler); - break; - } - case "system": { - enableExportProperty(); - const SystemLibraryPlugin = require("./SystemLibraryPlugin"); - new SystemLibraryPlugin({ - type - }).apply(compiler); - break; - } - case "jsonp": { - enableExportProperty(); - const JsonpLibraryPlugin = require("./JsonpLibraryPlugin"); - new JsonpLibraryPlugin({ - type - }).apply(compiler); - break; - } - case "module": { - enableExportProperty(); - const ModuleLibraryPlugin = require("./ModuleLibraryPlugin"); - new ModuleLibraryPlugin({ - type - }).apply(compiler); - break; - } - case "modern-module": { - enableExportProperty(); - const ModernModuleLibraryPlugin = require("./ModernModuleLibraryPlugin"); - new ModernModuleLibraryPlugin({ - type - }).apply(compiler); - break; - } - default: - throw new Error(`Unsupported library type ${type}. -Plugins which provide custom library types must call EnableLibraryPlugin.setEnabled(compiler, type) to disable this error.`); - } - } else { - // TODO support plugin instances here - // apply them to the compiler - } - } -} - -module.exports = EnableLibraryPlugin; diff --git a/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js b/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js deleted file mode 100644 index 72b92f724af..00000000000 --- a/webpack-lib/lib/library/ExportPropertyLibraryPlugin.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const { UsageState } = require("../ExportsInfo"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const propertyAccess = require("../util/propertyAccess"); -const { getEntryRuntime } = require("../util/runtime"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} ExportPropertyLibraryPluginParsed - * @property {string | string[]} export - */ - -/** - * @typedef {object} ExportPropertyLibraryPluginOptions - * @property {LibraryType} type - * @property {boolean} nsObjectUsed the namespace object is used - * @property {boolean} runtimeExportsUsed runtime exports are used - */ -/** - * @typedef {ExportPropertyLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class ExportPropertyLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {ExportPropertyLibraryPluginOptions} options options - */ - constructor({ type, nsObjectUsed, runtimeExportsUsed }) { - super({ - pluginName: "ExportPropertyLibraryPlugin", - type - }); - this.nsObjectUsed = nsObjectUsed; - this.runtimeExportsUsed = runtimeExportsUsed; - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - return { - export: /** @type {string | string[]} */ (library.export) - }; - } - - /** - * @param {Module} module the exporting entry module - * @param {string} entryName the name of the entrypoint - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - finishEntryModule( - module, - entryName, - { options, compilation, compilation: { moduleGraph } } - ) { - const runtime = getEntryRuntime(compilation, entryName); - if (options.export) { - const exportsInfo = moduleGraph.getExportInfo( - module, - Array.isArray(options.export) ? options.export[0] : options.export - ); - exportsInfo.setUsed(UsageState.Used, runtime); - exportsInfo.canMangleUse = false; - } else { - const exportsInfo = moduleGraph.getExportsInfo(module); - if (this.nsObjectUsed) { - exportsInfo.setUsedInUnknownWay(runtime); - } else { - exportsInfo.setAllKnownExportsUsed(runtime); - } - } - moduleGraph.addExtraReason(module, "used as library export"); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Set} set runtime requirements - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - runtimeRequirements(chunk, set, libraryContext) { - if (this.runtimeExportsUsed) { - set.add(RuntimeGlobals.exports); - } - } - - /** - * @param {Source} source source - * @param {Module} module module - * @param {StartupRenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - renderStartup(source, module, renderContext, { options }) { - if (!options.export) return source; - const postfix = `${RuntimeGlobals.exports} = ${ - RuntimeGlobals.exports - }${propertyAccess( - Array.isArray(options.export) ? options.export : [options.export] - )};\n`; - return new ConcatSource(source, postfix); - } -} - -module.exports = ExportPropertyLibraryPlugin; diff --git a/webpack-lib/lib/library/JsonpLibraryPlugin.js b/webpack-lib/lib/library/JsonpLibraryPlugin.js deleted file mode 100644 index 9757874232d..00000000000 --- a/webpack-lib/lib/library/JsonpLibraryPlugin.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} JsonpLibraryPluginOptions - * @property {LibraryType} type - */ - -/** - * @typedef {object} JsonpLibraryPluginParsed - * @property {string} name - */ - -/** - * @typedef {JsonpLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class JsonpLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {JsonpLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "JsonpLibraryPlugin", - type: options.type - }); - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name } = library; - if (typeof name !== "string") { - throw new Error( - `Jsonp library name must be a simple string. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string} */ (name); - return { - name: _name - }; - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render(source, { chunk }, { options, compilation }) { - const name = compilation.getPath(options.name, { - chunk - }); - return new ConcatSource(`${name}(`, source, ")"); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Hash} hash hash - * @param {ChunkHashContext} chunkHashContext chunk hash context - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { - hash.update("JsonpLibraryPlugin"); - hash.update(compilation.getPath(options.name, { chunk })); - } -} - -module.exports = JsonpLibraryPlugin; diff --git a/webpack-lib/lib/library/ModernModuleLibraryPlugin.js b/webpack-lib/lib/library/ModernModuleLibraryPlugin.js deleted file mode 100644 index 23a9510c211..00000000000 --- a/webpack-lib/lib/library/ModernModuleLibraryPlugin.js +++ /dev/null @@ -1,144 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const ConcatenatedModule = require("../optimize/ConcatenatedModule"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} ModernModuleLibraryPluginOptions - * @property {LibraryType} type - */ - -/** - * @typedef {object} ModernModuleLibraryPluginParsed - * @property {string} name - */ - -/** - * @typedef {ModernModuleLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class ModernModuleLibraryPlugin extends AbstractLibraryPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - super.apply(compiler); - - compiler.hooks.compilation.tap("ModernModuleLibraryPlugin", compilation => { - const { exportsDefinitions } = - ConcatenatedModule.getCompilationHooks(compilation); - exportsDefinitions.tap("ModernModuleLibraryPlugin", () => true); - }); - } - - /** - * @param {ModernModuleLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "ModernModuleLibraryPlugin", - type: options.type - }); - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name } = library; - if (name) { - throw new Error( - `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string} */ (name); - return { - name: _name - }; - } - - /** - * @param {Source} source source - * @param {Module} module module - * @param {StartupRenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - renderStartup( - source, - module, - { moduleGraph, chunk }, - { options, compilation } - ) { - const result = new ConcatSource(source); - const exportsInfo = moduleGraph.getExportsInfo(module); - const definitions = - /** @type {BuildMeta} */ - (module.buildMeta).exportsFinalName; - const exports = []; - - for (const exportInfo of exportsInfo.orderedExports) { - let shouldContinue = false; - const reexport = exportInfo.findTarget(moduleGraph, _m => true); - - if (reexport) { - const exp = moduleGraph.getExportsInfo(reexport.module); - - for (const reexportInfo of exp.orderedExports) { - if ( - !reexportInfo.provided && - reexportInfo.name === /** @type {string[]} */ (reexport.export)[0] - ) { - shouldContinue = true; - } - } - } - - if (shouldContinue) continue; - - const webpackExportsProperty = exportInfo.getUsedName( - exportInfo.name, - chunk.runtime - ); - const finalName = - definitions[ - /** @type {string} */ - (webpackExportsProperty) - ]; - exports.push( - finalName === exportInfo.name - ? finalName - : `${finalName} as ${exportInfo.name}` - ); - } - - if (exports.length > 0) { - result.add(`export { ${exports.join(", ")} };\n`); - } - - return result; - } -} - -module.exports = ModernModuleLibraryPlugin; diff --git a/webpack-lib/lib/library/ModuleLibraryPlugin.js b/webpack-lib/lib/library/ModuleLibraryPlugin.js deleted file mode 100644 index 57afdc3e1a4..00000000000 --- a/webpack-lib/lib/library/ModuleLibraryPlugin.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const propertyAccess = require("../util/propertyAccess"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} ModuleLibraryPluginOptions - * @property {LibraryType} type - */ - -/** - * @typedef {object} ModuleLibraryPluginParsed - * @property {string} name - */ - -/** - * @typedef {ModuleLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class ModuleLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {ModuleLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "ModuleLibraryPlugin", - type: options.type - }); - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name } = library; - if (name) { - throw new Error( - `Library name must be unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string} */ (name); - return { - name: _name - }; - } - - /** - * @param {Source} source source - * @param {Module} module module - * @param {StartupRenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - renderStartup( - source, - module, - { moduleGraph, chunk }, - { options, compilation } - ) { - const result = new ConcatSource(source); - const exportsInfo = moduleGraph.getExportsInfo(module); - const exports = []; - const isAsync = moduleGraph.isAsync(module); - if (isAsync) { - result.add( - `${RuntimeGlobals.exports} = await ${RuntimeGlobals.exports};\n` - ); - } - for (const exportInfo of exportsInfo.orderedExports) { - if (!exportInfo.provided) continue; - const varName = `${RuntimeGlobals.exports}${Template.toIdentifier( - exportInfo.name - )}`; - result.add( - `var ${varName} = ${RuntimeGlobals.exports}${propertyAccess([ - /** @type {string} */ - (exportInfo.getUsedName(exportInfo.name, chunk.runtime)) - ])};\n` - ); - exports.push(`${varName} as ${exportInfo.name}`); - } - if (exports.length > 0) { - result.add(`export { ${exports.join(", ")} };\n`); - } - return result; - } -} - -module.exports = ModuleLibraryPlugin; diff --git a/webpack-lib/lib/library/SystemLibraryPlugin.js b/webpack-lib/lib/library/SystemLibraryPlugin.js deleted file mode 100644 index 701b870d5ea..00000000000 --- a/webpack-lib/lib/library/SystemLibraryPlugin.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Joel Denning @joeldenning -*/ - -"use strict"; - -const { ConcatSource } = require("webpack-sources"); -const { UsageState } = require("../ExportsInfo"); -const ExternalModule = require("../ExternalModule"); -const Template = require("../Template"); -const propertyAccess = require("../util/propertyAccess"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ - -/** - * @typedef {object} SystemLibraryPluginOptions - * @property {LibraryType} type - */ - -/** - * @typedef {object} SystemLibraryPluginParsed - * @property {string} name - */ - -/** - * @typedef {SystemLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class SystemLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {SystemLibraryPluginOptions} options the plugin options - */ - constructor(options) { - super({ - pluginName: "SystemLibraryPlugin", - type: options.type - }); - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - const { name } = library; - if (name && typeof name !== "string") { - throw new Error( - `System.js library name must be a simple string or unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` - ); - } - const _name = /** @type {string} */ (name); - return { - name: _name - }; - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render(source, { chunkGraph, moduleGraph, chunk }, { options, compilation }) { - const modules = chunkGraph - .getChunkModules(chunk) - .filter(m => m instanceof ExternalModule && m.externalType === "system"); - const externals = /** @type {ExternalModule[]} */ (modules); - - // The name this bundle should be registered as with System - const name = options.name - ? `${JSON.stringify(compilation.getPath(options.name, { chunk }))}, ` - : ""; - - // The array of dependencies that are external to webpack and will be provided by System - const systemDependencies = JSON.stringify( - externals.map(m => - typeof m.request === "object" && !Array.isArray(m.request) - ? m.request.amd - : m.request - ) - ); - - // The name of the variable provided by System for exporting - const dynamicExport = "__WEBPACK_DYNAMIC_EXPORT__"; - - // An array of the internal variable names for the webpack externals - const externalWebpackNames = externals.map( - m => - `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( - `${chunkGraph.getModuleId(m)}` - )}__` - ); - - // Declaring variables for the internal variable names for the webpack externals - const externalVarDeclarations = externalWebpackNames - .map(name => `var ${name} = {};`) - .join("\n"); - - // Define __esModule flag on all internal variables and helpers - /** @type {string[]} */ - const externalVarInitialization = []; - - // The system.register format requires an array of setter functions for externals. - const setters = - externalWebpackNames.length === 0 - ? "" - : Template.asString([ - "setters: [", - Template.indent( - externals - .map((module, i) => { - const external = externalWebpackNames[i]; - const exportsInfo = moduleGraph.getExportsInfo(module); - const otherUnused = - exportsInfo.otherExportsInfo.getUsed(chunk.runtime) === - UsageState.Unused; - const instructions = []; - const handledNames = []; - for (const exportInfo of exportsInfo.orderedExports) { - const used = exportInfo.getUsedName( - undefined, - chunk.runtime - ); - if (used) { - if (otherUnused || used !== exportInfo.name) { - instructions.push( - `${external}${propertyAccess([ - used - ])} = module${propertyAccess([exportInfo.name])};` - ); - handledNames.push(exportInfo.name); - } - } else { - handledNames.push(exportInfo.name); - } - } - if (!otherUnused) { - if ( - !Array.isArray(module.request) || - module.request.length === 1 - ) { - externalVarInitialization.push( - `Object.defineProperty(${external}, "__esModule", { value: true });` - ); - } - if (handledNames.length > 0) { - const name = `${external}handledNames`; - externalVarInitialization.push( - `var ${name} = ${JSON.stringify(handledNames)};` - ); - instructions.push( - Template.asString([ - "Object.keys(module).forEach(function(key) {", - Template.indent([ - `if(${name}.indexOf(key) >= 0)`, - Template.indent(`${external}[key] = module[key];`) - ]), - "});" - ]) - ); - } else { - instructions.push( - Template.asString([ - "Object.keys(module).forEach(function(key) {", - Template.indent([`${external}[key] = module[key];`]), - "});" - ]) - ); - } - } - if (instructions.length === 0) return "function() {}"; - return Template.asString([ - "function(module) {", - Template.indent(instructions), - "}" - ]); - }) - .join(",\n") - ), - "]," - ]); - - return new ConcatSource( - Template.asString([ - `System.register(${name}${systemDependencies}, function(${dynamicExport}, __system_context__) {`, - Template.indent([ - externalVarDeclarations, - Template.asString(externalVarInitialization), - "return {", - Template.indent([ - setters, - "execute: function() {", - Template.indent(`${dynamicExport}(`) - ]) - ]), - "" - ]), - source, - Template.asString([ - "", - Template.indent([ - Template.indent([Template.indent([");"]), "}"]), - "};" - ]), - "})" - ]) - ); - } - - /** - * @param {Chunk} chunk the chunk - * @param {Hash} hash hash - * @param {ChunkHashContext} chunkHashContext chunk hash context - * @param {LibraryContext} libraryContext context - * @returns {void} - */ - chunkHash(chunk, hash, chunkHashContext, { options, compilation }) { - hash.update("SystemLibraryPlugin"); - if (options.name) { - hash.update(compilation.getPath(options.name, { chunk })); - } - } -} - -module.exports = SystemLibraryPlugin; diff --git a/webpack-lib/lib/library/UmdLibraryPlugin.js b/webpack-lib/lib/library/UmdLibraryPlugin.js deleted file mode 100644 index b21066d3934..00000000000 --- a/webpack-lib/lib/library/UmdLibraryPlugin.js +++ /dev/null @@ -1,351 +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 ExternalModule = require("../ExternalModule"); -const Template = require("../Template"); -const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryCustomUmdCommentObject} LibraryCustomUmdCommentObject */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryCustomUmdObject} LibraryCustomUmdObject */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryName} LibraryName */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ -/** @typedef {import("../ExternalModule").RequestRecord} RequestRecord */ -/** @typedef {import("../util/Hash")} Hash */ -/** - * @template T - * @typedef {import("./AbstractLibraryPlugin").LibraryContext} - * LibraryContext - */ - -/** - * @param {string[]} accessor the accessor to convert to path - * @returns {string} the path - */ -const accessorToObjectAccess = accessor => - accessor.map(a => `[${JSON.stringify(a)}]`).join(""); - -/** - * @param {string|undefined} 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 && base === undefined) - return `${a} = typeof ${a} === "object" ? ${a} : {}`; - return `${a} = ${a} || {}`; - }) - .join(joinWith); -}; - -/** @typedef {string | string[] | LibraryCustomUmdObject} UmdLibraryPluginName */ - -/** - * @typedef {object} UmdLibraryPluginOptions - * @property {LibraryType} type - * @property {boolean=} optionalAmdExternalAsGlobal - */ - -/** - * @typedef {object} UmdLibraryPluginParsed - * @property {string | string[] | undefined} name - * @property {LibraryCustomUmdObject} names - * @property {string | LibraryCustomUmdCommentObject | undefined} auxiliaryComment - * @property {boolean | undefined} namedDefine - */ - -/** - * @typedef {UmdLibraryPluginParsed} T - * @extends {AbstractLibraryPlugin} - */ -class UmdLibraryPlugin extends AbstractLibraryPlugin { - /** - * @param {UmdLibraryPluginOptions} options the plugin option - */ - constructor(options) { - super({ - pluginName: "UmdLibraryPlugin", - type: options.type - }); - - this.optionalAmdExternalAsGlobal = options.optionalAmdExternalAsGlobal; - } - - /** - * @param {LibraryOptions} library normalized library option - * @returns {T | false} preprocess as needed by overriding - */ - parseOptions(library) { - /** @type {LibraryName | undefined} */ - let name; - /** @type {LibraryCustomUmdObject} */ - let names; - if (typeof library.name === "object" && !Array.isArray(library.name)) { - name = library.name.root || library.name.amd || library.name.commonjs; - names = library.name; - } else { - name = library.name; - const singleName = Array.isArray(name) ? name[0] : name; - names = { - commonjs: singleName, - root: library.name, - amd: singleName - }; - } - return { - name, - names, - auxiliaryComment: library.auxiliaryComment, - namedDefine: library.umdNamedDefine - }; - } - - /** - * @param {Source} source source - * @param {RenderContext} renderContext render context - * @param {LibraryContext} libraryContext context - * @returns {Source} source with library export - */ - render( - source, - { chunkGraph, runtimeTemplate, chunk, moduleGraph }, - { options, compilation } - ) { - const modules = chunkGraph - .getChunkModules(chunk) - .filter( - m => - m instanceof ExternalModule && - (m.externalType === "umd" || m.externalType === "umd2") - ); - let externals = /** @type {ExternalModule[]} */ (modules); - /** @type {ExternalModule[]} */ - const optionalExternals = []; - /** @type {ExternalModule[]} */ - let requiredExternals = []; - if (this.optionalAmdExternalAsGlobal) { - for (const m of externals) { - if (m.isOptional(moduleGraph)) { - optionalExternals.push(m); - } else { - requiredExternals.push(m); - } - } - externals = requiredExternals.concat(optionalExternals); - } else { - requiredExternals = externals; - } - - /** - * @param {string} str the string to replace - * @returns {string} the replaced keys - */ - const replaceKeys = str => - compilation.getPath(str, { - chunk - }); - - /** - * @param {ExternalModule[]} modules external modules - * @returns {string} result - */ - const externalsDepsArray = modules => - `[${replaceKeys( - modules - .map(m => - JSON.stringify( - typeof m.request === "object" - ? /** @type {RequestRecord} */ - (m.request).amd - : m.request - ) - ) - .join(", ") - )}]`; - - /** - * @param {ExternalModule[]} modules external modules - * @returns {string} result - */ - const externalsRootArray = modules => - replaceKeys( - modules - .map(m => { - let request = m.request; - if (typeof request === "object") - request = - /** @type {RequestRecord} */ - (request).root; - return `root${accessorToObjectAccess(/** @type {string[]} */ ([]).concat(request))}`; - }) - .join(", ") - ); - - /** - * @param {string} type the type - * @returns {string} external require array - */ - const externalsRequireArray = type => - replaceKeys( - externals - .map(m => { - let expr; - let request = m.request; - if (typeof request === "object") { - request = - /** @type {RequestRecord} */ - (request)[type]; - } - if (request === undefined) { - throw new Error( - `Missing external configuration for type:${type}` - ); - } - expr = Array.isArray(request) - ? `require(${JSON.stringify( - request[0] - )})${accessorToObjectAccess(request.slice(1))}` - : `require(${JSON.stringify(request)})`; - if (m.isOptional(moduleGraph)) { - expr = `(function webpackLoadOptionalExternalModule() { try { return ${expr}; } catch(e) {} }())`; - } - return expr; - }) - .join(", ") - ); - - /** - * @param {ExternalModule[]} modules external modules - * @returns {string} arguments - */ - const externalsArguments = modules => - modules - .map( - m => - `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( - `${chunkGraph.getModuleId(m)}` - )}__` - ) - .join(", "); - - /** - * @param {string| string[]} library library name - * @returns {string} stringified library name - */ - const libraryName = library => - JSON.stringify( - replaceKeys( - /** @type {string} */ - (/** @type {string[]} */ ([]).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, namedDefine, names } = options; - - /** - * @param {keyof LibraryCustomUmdCommentObject} type type - * @returns {string} comment - */ - const getAuxiliaryComment = 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${getAuxiliaryComment( - "commonjs2" - )} if(typeof exports === 'object' && typeof module === 'object')\n` + - ` module.exports = factory(${externalsRequireArray( - "commonjs2" - )});\n${getAuxiliaryComment( - "amd" - )} else if(typeof define === 'function' && define.amd)\n${ - requiredExternals.length > 0 - ? names.amd && namedDefine === true - ? ` define(${libraryName(names.amd)}, ${externalsDepsArray( - requiredExternals - )}, ${amdFactory});\n` - : ` define(${externalsDepsArray(requiredExternals)}, ${ - amdFactory - });\n` - : names.amd && namedDefine === true - ? ` define(${libraryName(names.amd)}, [], ${amdFactory});\n` - : ` define([], ${amdFactory});\n` - }${ - names.root || names.commonjs - ? `${getAuxiliaryComment( - "commonjs" - )} else if(typeof exports === 'object')\n` + - ` exports[${libraryName( - /** @type {string | string[]} */ - (names.commonjs || names.root) - )}] = factory(${externalsRequireArray( - "commonjs" - )});\n${getAuxiliaryComment("root")} else\n` + - ` ${replaceKeys( - accessorAccess( - "root", - /** @type {string | string[]} */ - (names.root || 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}, ${ - runtimeTemplate.supportsArrowFunction() - ? `(${externalsArguments(externals)}) =>` - : `function(${externalsArguments(externals)})` - } {\nreturn `, - "webpack/universalModuleDefinition" - ), - source, - ";\n})" - ); - } -} - -module.exports = UmdLibraryPlugin; diff --git a/webpack-lib/lib/logging/Logger.js b/webpack-lib/lib/logging/Logger.js deleted file mode 100644 index 910b16f78e8..00000000000 --- a/webpack-lib/lib/logging/Logger.js +++ /dev/null @@ -1,216 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const LogType = Object.freeze({ - error: /** @type {"error"} */ ("error"), // message, c style arguments - warn: /** @type {"warn"} */ ("warn"), // message, c style arguments - info: /** @type {"info"} */ ("info"), // message, c style arguments - log: /** @type {"log"} */ ("log"), // message, c style arguments - debug: /** @type {"debug"} */ ("debug"), // message, c style arguments - - trace: /** @type {"trace"} */ ("trace"), // no arguments - - group: /** @type {"group"} */ ("group"), // [label] - groupCollapsed: /** @type {"groupCollapsed"} */ ("groupCollapsed"), // [label] - groupEnd: /** @type {"groupEnd"} */ ("groupEnd"), // [label] - - profile: /** @type {"profile"} */ ("profile"), // [profileName] - profileEnd: /** @type {"profileEnd"} */ ("profileEnd"), // [profileName] - - time: /** @type {"time"} */ ("time"), // name, time as [seconds, nanoseconds] - - clear: /** @type {"clear"} */ ("clear"), // no arguments - status: /** @type {"status"} */ ("status") // message, arguments -}); - -module.exports.LogType = LogType; - -/** @typedef {typeof LogType[keyof typeof LogType]} LogTypeEnum */ - -const LOG_SYMBOL = Symbol("webpack logger raw log method"); -const TIMERS_SYMBOL = Symbol("webpack logger times"); -const TIMERS_AGGREGATES_SYMBOL = Symbol("webpack logger aggregated times"); - -class WebpackLogger { - /** - * @param {function(LogTypeEnum, EXPECTED_ANY[]=): void} log log function - * @param {function(string | function(): string): WebpackLogger} getChildLogger function to create child logger - */ - constructor(log, getChildLogger) { - this[LOG_SYMBOL] = log; - this.getChildLogger = getChildLogger; - } - - /** - * @param {...EXPECTED_ANY} args args - */ - error(...args) { - this[LOG_SYMBOL](LogType.error, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - warn(...args) { - this[LOG_SYMBOL](LogType.warn, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - info(...args) { - this[LOG_SYMBOL](LogType.info, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - log(...args) { - this[LOG_SYMBOL](LogType.log, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - debug(...args) { - this[LOG_SYMBOL](LogType.debug, args); - } - - /** - * @param {EXPECTED_ANY} assertion assertion - * @param {...EXPECTED_ANY} args args - */ - assert(assertion, ...args) { - if (!assertion) { - this[LOG_SYMBOL](LogType.error, args); - } - } - - trace() { - this[LOG_SYMBOL](LogType.trace, ["Trace"]); - } - - clear() { - this[LOG_SYMBOL](LogType.clear); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - status(...args) { - this[LOG_SYMBOL](LogType.status, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - group(...args) { - this[LOG_SYMBOL](LogType.group, args); - } - - /** - * @param {...EXPECTED_ANY} args args - */ - groupCollapsed(...args) { - this[LOG_SYMBOL](LogType.groupCollapsed, args); - } - - groupEnd() { - this[LOG_SYMBOL](LogType.groupEnd); - } - - /** - * @param {string=} label label - */ - profile(label) { - this[LOG_SYMBOL](LogType.profile, [label]); - } - - /** - * @param {string=} label label - */ - profileEnd(label) { - this[LOG_SYMBOL](LogType.profileEnd, [label]); - } - - /** - * @param {string} label label - */ - time(label) { - /** @type {Map} */ - this[TIMERS_SYMBOL] = this[TIMERS_SYMBOL] || new Map(); - this[TIMERS_SYMBOL].set(label, process.hrtime()); - } - - /** - * @param {string=} label label - */ - timeLog(label) { - const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); - if (!prev) { - throw new Error(`No such label '${label}' for WebpackLogger.timeLog()`); - } - const time = process.hrtime(prev); - this[LOG_SYMBOL](LogType.time, [label, ...time]); - } - - /** - * @param {string=} label label - */ - timeEnd(label) { - const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); - if (!prev) { - throw new Error(`No such label '${label}' for WebpackLogger.timeEnd()`); - } - const time = process.hrtime(prev); - /** @type {Map} */ - (this[TIMERS_SYMBOL]).delete(label); - this[LOG_SYMBOL](LogType.time, [label, ...time]); - } - - /** - * @param {string=} label label - */ - timeAggregate(label) { - const prev = this[TIMERS_SYMBOL] && this[TIMERS_SYMBOL].get(label); - if (!prev) { - throw new Error( - `No such label '${label}' for WebpackLogger.timeAggregate()` - ); - } - const time = process.hrtime(prev); - /** @type {Map} */ - (this[TIMERS_SYMBOL]).delete(label); - /** @type {Map} */ - this[TIMERS_AGGREGATES_SYMBOL] = - this[TIMERS_AGGREGATES_SYMBOL] || new Map(); - const current = this[TIMERS_AGGREGATES_SYMBOL].get(label); - if (current !== undefined) { - if (time[1] + current[1] > 1e9) { - time[0] += current[0] + 1; - time[1] = time[1] - 1e9 + current[1]; - } else { - time[0] += current[0]; - time[1] += current[1]; - } - } - this[TIMERS_AGGREGATES_SYMBOL].set(label, time); - } - - /** - * @param {string=} label label - */ - timeAggregateEnd(label) { - if (this[TIMERS_AGGREGATES_SYMBOL] === undefined) return; - const time = this[TIMERS_AGGREGATES_SYMBOL].get(label); - if (time === undefined) return; - this[TIMERS_AGGREGATES_SYMBOL].delete(label); - this[LOG_SYMBOL](LogType.time, [label, ...time]); - } -} - -module.exports.Logger = WebpackLogger; diff --git a/webpack-lib/lib/logging/createConsoleLogger.js b/webpack-lib/lib/logging/createConsoleLogger.js deleted file mode 100644 index 3b8ffd83897..00000000000 --- a/webpack-lib/lib/logging/createConsoleLogger.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { LogType } = require("./Logger"); - -/** @typedef {import("../../declarations/WebpackOptions").FilterItemTypes} FilterItemTypes */ -/** @typedef {import("../../declarations/WebpackOptions").FilterTypes} FilterTypes */ -/** @typedef {import("./Logger").LogTypeEnum} LogTypeEnum */ - -/** @typedef {function(string): boolean} FilterFunction */ -/** @typedef {function(string, LogTypeEnum, EXPECTED_ANY[]=): void} LoggingFunction */ - -/** - * @typedef {object} LoggerConsole - * @property {function(): void} clear - * @property {function(): void} trace - * @property {(...args: EXPECTED_ANY[]) => void} info - * @property {(...args: EXPECTED_ANY[]) => void} log - * @property {(...args: EXPECTED_ANY[]) => void} warn - * @property {(...args: EXPECTED_ANY[]) => void} error - * @property {(...args: EXPECTED_ANY[]) => void=} debug - * @property {(...args: EXPECTED_ANY[]) => void=} group - * @property {(...args: EXPECTED_ANY[]) => void=} groupCollapsed - * @property {(...args: EXPECTED_ANY[]) => void=} groupEnd - * @property {(...args: EXPECTED_ANY[]) => void=} status - * @property {(...args: EXPECTED_ANY[]) => void=} profile - * @property {(...args: EXPECTED_ANY[]) => void=} profileEnd - * @property {(...args: EXPECTED_ANY[]) => void=} logTime - */ - -/** - * @typedef {object} LoggerOptions - * @property {false|true|"none"|"error"|"warn"|"info"|"log"|"verbose"} level loglevel - * @property {FilterTypes|boolean} debug filter for debug logging - * @property {LoggerConsole} console the console to log to - */ - -/** - * @param {FilterItemTypes} item an input item - * @returns {FilterFunction | undefined} filter function - */ -const filterToFunction = item => { - if (typeof item === "string") { - const regExp = new RegExp( - `[\\\\/]${item.replace(/[-[\]{}()*+?.\\^$|]/g, "\\$&")}([\\\\/]|$|!|\\?)` - ); - 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; - } -}; - -/** - * @enum {number} - */ -const LogLevel = { - none: 6, - false: 6, - error: 5, - warn: 4, - info: 3, - log: 2, - true: 2, - verbose: 1 -}; - -/** - * @param {LoggerOptions} options options object - * @returns {LoggingFunction} logging function - */ -module.exports = ({ level = "info", debug = false, console }) => { - const debugFilters = - /** @type {FilterFunction[]} */ - ( - typeof debug === "boolean" - ? [() => debug] - : /** @type {FilterItemTypes[]} */ ([]) - .concat(debug) - .map(filterToFunction) - ); - /** @type {number} */ - const loglevel = LogLevel[`${level}`] || 0; - - /** - * @param {string} name name of the logger - * @param {LogTypeEnum} type type of the log entry - * @param {EXPECTED_ANY[]=} args arguments of the log entry - * @returns {void} - */ - const logger = (name, type, args) => { - const labeledArgs = () => { - if (Array.isArray(args)) { - if (args.length > 0 && typeof args[0] === "string") { - return [`[${name}] ${args[0]}`, ...args.slice(1)]; - } - return [`[${name}]`, ...args]; - } - return []; - }; - const debug = debugFilters.some(f => f(name)); - switch (type) { - case LogType.debug: - if (!debug) return; - if (typeof console.debug === "function") { - console.debug(...labeledArgs()); - } else { - console.log(...labeledArgs()); - } - break; - case LogType.log: - if (!debug && loglevel > LogLevel.log) return; - console.log(...labeledArgs()); - break; - case LogType.info: - if (!debug && loglevel > LogLevel.info) return; - console.info(...labeledArgs()); - break; - case LogType.warn: - if (!debug && loglevel > LogLevel.warn) return; - console.warn(...labeledArgs()); - break; - case LogType.error: - if (!debug && loglevel > LogLevel.error) return; - console.error(...labeledArgs()); - break; - case LogType.trace: - if (!debug) return; - console.trace(); - break; - case LogType.groupCollapsed: - if (!debug && loglevel > LogLevel.log) return; - if (!debug && loglevel > LogLevel.verbose) { - if (typeof console.groupCollapsed === "function") { - console.groupCollapsed(...labeledArgs()); - } else { - console.log(...labeledArgs()); - } - break; - } - // falls through - case LogType.group: - if (!debug && loglevel > LogLevel.log) return; - if (typeof console.group === "function") { - console.group(...labeledArgs()); - } else { - console.log(...labeledArgs()); - } - break; - case LogType.groupEnd: - if (!debug && loglevel > LogLevel.log) return; - if (typeof console.groupEnd === "function") { - console.groupEnd(); - } - break; - case LogType.time: { - if (!debug && loglevel > LogLevel.log) return; - const [label, start, end] = - /** @type {[string, number, number]} */ - (args); - const ms = start * 1000 + end / 1000000; - const msg = `[${name}] ${label}: ${ms} ms`; - if (typeof console.logTime === "function") { - console.logTime(msg); - } else { - console.log(msg); - } - break; - } - case LogType.profile: - if (typeof console.profile === "function") { - console.profile(...labeledArgs()); - } - break; - case LogType.profileEnd: - if (typeof console.profileEnd === "function") { - console.profileEnd(...labeledArgs()); - } - break; - case LogType.clear: - if (!debug && loglevel > LogLevel.log) return; - if (typeof console.clear === "function") { - console.clear(); - } - break; - case LogType.status: - if (!debug && loglevel > LogLevel.info) return; - if (typeof console.status === "function") { - if (!args || args.length === 0) { - console.status(); - } else { - console.status(...labeledArgs()); - } - } else if (args && args.length !== 0) { - console.info(...labeledArgs()); - } - break; - default: - throw new Error(`Unexpected LogType ${type}`); - } - }; - return logger; -}; diff --git a/webpack-lib/lib/logging/runtime.js b/webpack-lib/lib/logging/runtime.js deleted file mode 100644 index b0c614081f0..00000000000 --- a/webpack-lib/lib/logging/runtime.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncBailHook } = require("tapable"); -const { Logger } = require("./Logger"); -const createConsoleLogger = require("./createConsoleLogger"); - -/** @type {createConsoleLogger.LoggerOptions} */ -const currentDefaultLoggerOptions = { - level: "info", - debug: false, - console -}; -let currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); - -/** - * @param {string} name name of the logger - * @returns {Logger} a logger - */ -module.exports.getLogger = name => - new Logger( - (type, args) => { - if (module.exports.hooks.log.call(name, type, args) === undefined) { - currentDefaultLogger(name, type, args); - } - }, - childName => module.exports.getLogger(`${name}/${childName}`) - ); - -/** - * @param {createConsoleLogger.LoggerOptions} options new options, merge with old options - * @returns {void} - */ -module.exports.configureDefaultLogger = options => { - Object.assign(currentDefaultLoggerOptions, options); - currentDefaultLogger = createConsoleLogger(currentDefaultLoggerOptions); -}; - -module.exports.hooks = { - log: new SyncBailHook(["origin", "type", "args"]) -}; diff --git a/webpack-lib/lib/logging/truncateArgs.js b/webpack-lib/lib/logging/truncateArgs.js deleted file mode 100644 index 148ac7ae12b..00000000000 --- a/webpack-lib/lib/logging/truncateArgs.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @param {Array} array array of numbers - * @returns {number} sum of all numbers in array - */ -const arraySum = array => { - let sum = 0; - for (const item of array) sum += item; - return sum; -}; - -/** - * @param {EXPECTED_ANY[]} args items to be truncated - * @param {number} maxLength maximum length of args including spaces between - * @returns {string[]} truncated args - */ -const truncateArgs = (args, maxLength) => { - const lengths = args.map(a => `${a}`.length); - const availableLength = maxLength - lengths.length + 1; - - if (availableLength > 0 && args.length === 1) { - if (availableLength >= args[0].length) { - return args; - } else if (availableLength > 3) { - return [`...${args[0].slice(-availableLength + 3)}`]; - } - return [args[0].slice(-availableLength)]; - } - - // Check if there is space for at least 4 chars per arg - if (availableLength < arraySum(lengths.map(i => Math.min(i, 6)))) { - // remove args - if (args.length > 1) return truncateArgs(args.slice(0, -1), maxLength); - return []; - } - - let currentLength = arraySum(lengths); - - // Check if all fits into maxLength - if (currentLength <= availableLength) return args; - - // Try to remove chars from the longest items until it fits - while (currentLength > availableLength) { - const maxLength = Math.max(...lengths); - const shorterItems = lengths.filter(l => l !== maxLength); - const nextToMaxLength = - shorterItems.length > 0 ? Math.max(...shorterItems) : 0; - const maxReduce = maxLength - nextToMaxLength; - let maxItems = lengths.length - shorterItems.length; - let overrun = currentLength - availableLength; - for (let i = 0; i < lengths.length; i++) { - if (lengths[i] === maxLength) { - const reduce = Math.min(Math.floor(overrun / maxItems), maxReduce); - lengths[i] -= reduce; - currentLength -= reduce; - overrun -= reduce; - maxItems--; - } - } - } - - // Return args reduced to length in lengths - return args.map((a, i) => { - const str = `${a}`; - const length = lengths[i]; - if (str.length === length) { - return str; - } else if (length > 5) { - return `...${str.slice(-length + 3)}`; - } else if (length > 0) { - return str.slice(-length); - } - return ""; - }); -}; - -module.exports = truncateArgs; diff --git a/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js b/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js deleted file mode 100644 index cd7f787281a..00000000000 --- a/webpack-lib/lib/node/CommonJsChunkLoadingPlugin.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const StartupChunkDependenciesPlugin = require("../runtime/StartupChunkDependenciesPlugin"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} CommonJsChunkLoadingPluginOptions - * @property {boolean} [asyncChunkLoading] enable async chunk loading - */ - -class CommonJsChunkLoadingPlugin { - /** - * @param {CommonJsChunkLoadingPluginOptions} [options] options - */ - constructor(options = {}) { - this._asyncChunkLoading = options.asyncChunkLoading; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const ChunkLoadingRuntimeModule = this._asyncChunkLoading - ? require("./ReadFileChunkLoadingRuntimeModule") - : require("./RequireChunkLoadingRuntimeModule"); - const chunkLoadingValue = this._asyncChunkLoading - ? "async-node" - : "require"; - new StartupChunkDependenciesPlugin({ - chunkLoading: chunkLoadingValue, - asyncChunkLoading: this._asyncChunkLoading - }).apply(compiler); - compiler.hooks.thisCompilation.tap( - "CommonJsChunkLoadingPlugin", - compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === chunkLoadingValue; - }; - const onceForChunkSet = new WeakSet(); - /** - * @param {Chunk} chunk chunk - * @param {Set} set runtime requirements - */ - const handler = (chunk, set) => { - if (onceForChunkSet.has(chunk)) return; - onceForChunkSet.add(chunk); - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.hasOwnProperty); - compilation.addRuntimeModule( - chunk, - new ChunkLoadingRuntimeModule(set) - ); - }; - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("CommonJsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("CommonJsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("CommonJsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.baseURI) - .tap("CommonJsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.externalInstallChunk) - .tap("CommonJsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.onChunksLoaded) - .tap("CommonJsChunkLoadingPlugin", handler); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.getChunkScriptFilename); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.getChunkUpdateScriptFilename); - set.add(RuntimeGlobals.moduleCache); - set.add(RuntimeGlobals.hmrModuleData); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("CommonJsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.getUpdateManifestFilename); - }); - } - ); - } -} - -module.exports = CommonJsChunkLoadingPlugin; diff --git a/webpack-lib/lib/node/NodeEnvironmentPlugin.js b/webpack-lib/lib/node/NodeEnvironmentPlugin.js deleted file mode 100644 index 221b1af0efa..00000000000 --- a/webpack-lib/lib/node/NodeEnvironmentPlugin.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const CachedInputFileSystem = require("enhanced-resolve").CachedInputFileSystem; -const fs = require("graceful-fs"); -const createConsoleLogger = require("../logging/createConsoleLogger"); -const NodeWatchFileSystem = require("./NodeWatchFileSystem"); -const nodeConsole = require("./nodeConsole"); - -/** @typedef {import("../../declarations/WebpackOptions").InfrastructureLogging} InfrastructureLogging */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ - -class NodeEnvironmentPlugin { - /** - * @param {object} options options - * @param {InfrastructureLogging} options.infrastructureLogging infrastructure logging options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { infrastructureLogging } = this.options; - compiler.infrastructureLogger = createConsoleLogger({ - level: infrastructureLogging.level || "info", - debug: infrastructureLogging.debug || false, - console: - infrastructureLogging.console || - nodeConsole({ - colors: infrastructureLogging.colors, - appendOnly: infrastructureLogging.appendOnly, - stream: - /** @type {NodeJS.WritableStream} */ - (infrastructureLogging.stream) - }) - }); - compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000); - const inputFileSystem = - /** @type {InputFileSystem} */ - (compiler.inputFileSystem); - compiler.outputFileSystem = fs; - compiler.intermediateFileSystem = fs; - compiler.watchFileSystem = new NodeWatchFileSystem(inputFileSystem); - compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => { - if ( - compiler.inputFileSystem === inputFileSystem && - inputFileSystem.purge - ) { - compiler.fsStartTime = Date.now(); - inputFileSystem.purge(); - } - }); - } -} - -module.exports = NodeEnvironmentPlugin; diff --git a/webpack-lib/lib/node/NodeSourcePlugin.js b/webpack-lib/lib/node/NodeSourcePlugin.js deleted file mode 100644 index fca1fc9caaf..00000000000 --- a/webpack-lib/lib/node/NodeSourcePlugin.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../Compiler")} Compiler */ - -class NodeSourcePlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) {} -} - -module.exports = NodeSourcePlugin; diff --git a/webpack-lib/lib/node/NodeTargetPlugin.js b/webpack-lib/lib/node/NodeTargetPlugin.js deleted file mode 100644 index 1cc01810daa..00000000000 --- a/webpack-lib/lib/node/NodeTargetPlugin.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ExternalsPlugin = require("../ExternalsPlugin"); - -/** @typedef {import("../Compiler")} Compiler */ - -const builtins = [ - "assert", - "assert/strict", - "async_hooks", - "buffer", - "child_process", - "cluster", - "console", - "constants", - "crypto", - "dgram", - "diagnostics_channel", - "dns", - "dns/promises", - "domain", - "events", - "fs", - "fs/promises", - "http", - "http2", - "https", - "inspector", - "inspector/promises", - "module", - "net", - "os", - "path", - "path/posix", - "path/win32", - "perf_hooks", - "process", - "punycode", - "querystring", - "readline", - "readline/promises", - "repl", - "stream", - "stream/consumers", - "stream/promises", - "stream/web", - "string_decoder", - "sys", - "timers", - "timers/promises", - "tls", - "trace_events", - "tty", - "url", - "util", - "util/types", - "v8", - "vm", - "wasi", - "worker_threads", - "zlib", - /^node:/, - - // cspell:word pnpapi - // Yarn PnP adds pnpapi as "builtin" - "pnpapi" -]; - -class NodeTargetPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - new ExternalsPlugin("node-commonjs", builtins).apply(compiler); - } -} - -module.exports = NodeTargetPlugin; diff --git a/webpack-lib/lib/node/NodeTemplatePlugin.js b/webpack-lib/lib/node/NodeTemplatePlugin.js deleted file mode 100644 index c784368e373..00000000000 --- a/webpack-lib/lib/node/NodeTemplatePlugin.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const CommonJsChunkFormatPlugin = require("../javascript/CommonJsChunkFormatPlugin"); -const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); - -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} NodeTemplatePluginOptions - * @property {boolean} [asyncChunkLoading] enable async chunk loading - */ - -class NodeTemplatePlugin { - /** - * @param {NodeTemplatePluginOptions} [options] options object - */ - constructor(options = {}) { - this._options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const chunkLoading = this._options.asyncChunkLoading - ? "async-node" - : "require"; - compiler.options.output.chunkLoading = chunkLoading; - new CommonJsChunkFormatPlugin().apply(compiler); - new EnableChunkLoadingPlugin(chunkLoading).apply(compiler); - } -} - -module.exports = NodeTemplatePlugin; diff --git a/webpack-lib/lib/node/NodeWatchFileSystem.js b/webpack-lib/lib/node/NodeWatchFileSystem.js deleted file mode 100644 index 091864a2b94..00000000000 --- a/webpack-lib/lib/node/NodeWatchFileSystem.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const Watchpack = require("watchpack"); - -/** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */ -/** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ -/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("../util/fs").WatchMethod} WatchMethod */ - -class NodeWatchFileSystem { - /** - * @param {InputFileSystem} inputFileSystem input filesystem - */ - constructor(inputFileSystem) { - this.inputFileSystem = inputFileSystem; - this.watcherOptions = { - aggregateTimeout: 0 - }; - /** @type {Watchpack | null} */ - this.watcher = new Watchpack(this.watcherOptions); - } - - /** @type {WatchMethod} */ - watch( - files, - directories, - missing, - startTime, - options, - callback, - callbackUndelayed - ) { - if (!files || typeof files[Symbol.iterator] !== "function") { - throw new Error("Invalid arguments: 'files'"); - } - if (!directories || typeof directories[Symbol.iterator] !== "function") { - throw new Error("Invalid arguments: 'directories'"); - } - if (!missing || typeof missing[Symbol.iterator] !== "function") { - throw new Error("Invalid arguments: 'missing'"); - } - if (typeof callback !== "function") { - throw new Error("Invalid arguments: 'callback'"); - } - if (typeof startTime !== "number" && startTime) { - throw new Error("Invalid arguments: 'startTime'"); - } - if (typeof options !== "object") { - throw new Error("Invalid arguments: 'options'"); - } - if (typeof callbackUndelayed !== "function" && callbackUndelayed) { - throw new Error("Invalid arguments: 'callbackUndelayed'"); - } - const oldWatcher = this.watcher; - this.watcher = new Watchpack(options); - - if (callbackUndelayed) { - this.watcher.once("change", callbackUndelayed); - } - - const fetchTimeInfo = () => { - const fileTimeInfoEntries = new Map(); - const contextTimeInfoEntries = new Map(); - if (this.watcher) { - this.watcher.collectTimeInfoEntries( - fileTimeInfoEntries, - contextTimeInfoEntries - ); - } - return { fileTimeInfoEntries, contextTimeInfoEntries }; - }; - this.watcher.once( - "aggregated", - /** - * @param {Set} changes changes - * @param {Set} removals removals - */ - (changes, removals) => { - // pause emitting events (avoids clearing aggregated changes and removals on timeout) - /** @type {Watchpack} */ - (this.watcher).pause(); - - const fs = this.inputFileSystem; - if (fs && fs.purge) { - for (const item of changes) { - fs.purge(item); - } - for (const item of removals) { - fs.purge(item); - } - } - const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo(); - callback( - null, - fileTimeInfoEntries, - contextTimeInfoEntries, - changes, - removals - ); - } - ); - - this.watcher.watch({ files, directories, missing, startTime }); - - if (oldWatcher) { - oldWatcher.close(); - } - return { - close: () => { - if (this.watcher) { - this.watcher.close(); - this.watcher = null; - } - }, - pause: () => { - if (this.watcher) { - this.watcher.pause(); - } - }, - getAggregatedRemovals: util.deprecate( - () => { - const items = this.watcher && this.watcher.aggregatedRemovals; - const fs = this.inputFileSystem; - if (items && fs && fs.purge) { - for (const item of items) { - fs.purge(item); - } - } - return items; - }, - "Watcher.getAggregatedRemovals is deprecated in favor of Watcher.getInfo since that's more performant.", - "DEP_WEBPACK_WATCHER_GET_AGGREGATED_REMOVALS" - ), - getAggregatedChanges: util.deprecate( - () => { - const items = this.watcher && this.watcher.aggregatedChanges; - const fs = this.inputFileSystem; - if (items && fs && fs.purge) { - for (const item of items) { - fs.purge(item); - } - } - return items; - }, - "Watcher.getAggregatedChanges is deprecated in favor of Watcher.getInfo since that's more performant.", - "DEP_WEBPACK_WATCHER_GET_AGGREGATED_CHANGES" - ), - getFileTimeInfoEntries: util.deprecate( - () => fetchTimeInfo().fileTimeInfoEntries, - "Watcher.getFileTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.", - "DEP_WEBPACK_WATCHER_FILE_TIME_INFO_ENTRIES" - ), - getContextTimeInfoEntries: util.deprecate( - () => fetchTimeInfo().contextTimeInfoEntries, - "Watcher.getContextTimeInfoEntries is deprecated in favor of Watcher.getInfo since that's more performant.", - "DEP_WEBPACK_WATCHER_CONTEXT_TIME_INFO_ENTRIES" - ), - getInfo: () => { - const removals = this.watcher && this.watcher.aggregatedRemovals; - const changes = this.watcher && this.watcher.aggregatedChanges; - const fs = this.inputFileSystem; - if (fs && fs.purge) { - if (removals) { - for (const item of removals) { - fs.purge(item); - } - } - if (changes) { - for (const item of changes) { - fs.purge(item); - } - } - } - const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo(); - return { - changes, - removals, - fileTimeInfoEntries, - contextTimeInfoEntries - }; - } - }; - } -} - -module.exports = NodeWatchFileSystem; diff --git a/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js b/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js deleted file mode 100644 index fd138b2e899..00000000000 --- a/webpack-lib/lib/node/ReadFileChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,301 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - chunkHasJs, - getChunkFilenameTemplate -} = require("../javascript/JavascriptModulesPlugin"); -const { getInitialChunkIds } = require("../javascript/StartupHelpers"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -class ReadFileChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("readFile chunk loading", RuntimeModule.STAGE_ATTACH); - this.runtimeRequirements = runtimeRequirements; - } - - /** - * @private - * @param {Chunk} chunk chunk - * @param {string} rootOutputDir root output directory - * @returns {string} generated code - */ - _generateBaseUri(chunk, rootOutputDir) { - const options = chunk.getEntryOptions(); - if (options && options.baseUri) { - return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; - } - - return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ - rootOutputDir - ? `__dirname + ${JSON.stringify(`/${rootOutputDir}`)}` - : "__filename" - });`; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.ensureChunkHandlers; - const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); - const withExternalInstallChunk = this.runtimeRequirements.has( - RuntimeGlobals.externalInstallChunk - ); - const withOnChunkLoad = this.runtimeRequirements.has( - RuntimeGlobals.onChunksLoaded - ); - const withLoading = this.runtimeRequirements.has( - RuntimeGlobals.ensureChunkHandlers - ); - const withHmr = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const withHmrManifest = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadManifest - ); - const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); - const hasJsMatcher = compileBooleanMatcher(conditionMap); - const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); - - const outputName = compilation.getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ); - const rootOutputDir = getUndoPath( - outputName, - /** @type {string} */ (compilation.outputOptions.path), - false - ); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_readFileVm` - : undefined; - - return Template.asString([ - withBaseURI - ? this._generateBaseUri(chunk, rootOutputDir) - : "// no baseURI", - "", - "// object to store loaded chunks", - '// "0" means "already loaded", Promise means loading', - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( - ",\n" - ) - ), - "};", - "", - withOnChunkLoad - ? `${ - RuntimeGlobals.onChunksLoaded - }.readFileVm = ${runtimeTemplate.returningFunction( - "installedChunks[chunkId] === 0", - "chunkId" - )};` - : "// no on chunks loaded", - "", - withLoading || withExternalInstallChunk - ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ - "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", - "for(var moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent([ - `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` - ]), - "}" - ]), - "}", - `if(runtime) runtime(${RuntimeGlobals.require});`, - "for(var i = 0; i < chunkIds.length; i++) {", - Template.indent([ - "if(installedChunks[chunkIds[i]]) {", - Template.indent(["installedChunks[chunkIds[i]][0]();"]), - "}", - "installedChunks[chunkIds[i]] = 0;" - ]), - "}", - withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" - ])};` - : "// no chunk install function needed", - "", - withLoading - ? Template.asString([ - "// ReadFile + VM.run chunk loading for javascript", - `${fn}.readFileVm = function(chunkId, promises) {`, - hasJsMatcher !== false - ? Template.indent([ - "", - "var installedChunkData = installedChunks[chunkId];", - 'if(installedChunkData !== 0) { // 0 means "already installed".', - Template.indent([ - '// array of [resolve, reject, promise] means "currently loading"', - "if(installedChunkData) {", - Template.indent(["promises.push(installedChunkData[2]);"]), - "} else {", - Template.indent([ - hasJsMatcher === true - ? "if(true) { // all chunks have JS" - : `if(${hasJsMatcher("chunkId")}) {`, - Template.indent([ - "// load the chunk and return promise to it", - "var promise = new Promise(function(resolve, reject) {", - Template.indent([ - "installedChunkData = installedChunks[chunkId] = [resolve, reject];", - `var filename = require('path').join(__dirname, ${JSON.stringify( - rootOutputDir - )} + ${ - RuntimeGlobals.getChunkScriptFilename - }(chunkId));`, - "require('fs').readFile(filename, 'utf-8', function(err, content) {", - Template.indent([ - "if(err) return reject(err);", - "var chunk = {};", - "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + - "(chunk, require, require('path').dirname(filename), filename);", - "installChunk(chunk);" - ]), - "});" - ]), - "});", - "promises.push(installedChunkData[2] = promise);" - ]), - hasJsMatcher === true - ? "}" - : "} else installedChunks[chunkId] = 0;" - ]), - "}" - ]), - "}" - ]) - : Template.indent(["installedChunks[chunkId] = 0;"]), - "};" - ]) - : "// no chunk loading", - "", - withExternalInstallChunk - ? Template.asString([ - `module.exports = ${RuntimeGlobals.require};`, - `${RuntimeGlobals.externalInstallChunk} = installChunk;` - ]) - : "// no external install chunk", - "", - withHmr - ? Template.asString([ - "function loadUpdateChunk(chunkId, updatedModulesList) {", - Template.indent([ - "return new Promise(function(resolve, reject) {", - Template.indent([ - `var filename = require('path').join(__dirname, ${JSON.stringify( - rootOutputDir - )} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId));`, - "require('fs').readFile(filename, 'utf-8', function(err, content) {", - Template.indent([ - "if(err) return reject(err);", - "var update = {};", - "require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" + - "(update, require, require('path').dirname(filename), filename);", - "var updatedModules = update.modules;", - "var runtime = update.runtime;", - "for(var moduleId in updatedModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, - Template.indent([ - "currentUpdate[moduleId] = updatedModules[moduleId];", - "if(updatedModulesList) updatedModulesList.push(moduleId);" - ]), - "}" - ]), - "}", - "if(runtime) currentUpdateRuntime.push(runtime);", - "resolve();" - ]), - "});" - ]), - "});" - ]), - "}", - "", - Template.getFunctionContent( - require("../hmr/JavascriptHotModuleReplacement.runtime.js") - ) - .replace(/\$key\$/g, "readFileVm") - .replace(/\$installedChunks\$/g, "installedChunks") - .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") - .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) - .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) - .replace( - /\$ensureChunkHandlers\$/g, - RuntimeGlobals.ensureChunkHandlers - ) - .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) - .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) - .replace( - /\$hmrDownloadUpdateHandlers\$/g, - RuntimeGlobals.hmrDownloadUpdateHandlers - ) - .replace( - /\$hmrInvalidateModuleHandlers\$/g, - RuntimeGlobals.hmrInvalidateModuleHandlers - ) - ]) - : "// no HMR", - "", - withHmrManifest - ? Template.asString([ - `${RuntimeGlobals.hmrDownloadManifest} = function() {`, - Template.indent([ - "return new Promise(function(resolve, reject) {", - Template.indent([ - `var filename = require('path').join(__dirname, ${JSON.stringify( - rootOutputDir - )} + ${RuntimeGlobals.getUpdateManifestFilename}());`, - "require('fs').readFile(filename, 'utf-8', function(err, content) {", - Template.indent([ - "if(err) {", - Template.indent([ - 'if(err.code === "ENOENT") return resolve();', - "return reject(err);" - ]), - "}", - "try { resolve(JSON.parse(content)); }", - "catch(e) { reject(e); }" - ]), - "});" - ]), - "});" - ]), - "}" - ]) - : "// no HMR manifest" - ]); - } -} - -module.exports = ReadFileChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js b/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js deleted file mode 100644 index d53f1a83dd1..00000000000 --- a/webpack-lib/lib/node/ReadFileCompileAsyncWasmPlugin.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} ReadFileCompileAsyncWasmPluginOptions - * @property {boolean} [import] use import? - */ - -const PLUGIN_NAME = "ReadFileCompileAsyncWasmPlugin"; - -class ReadFileCompileAsyncWasmPlugin { - /** - * @param {ReadFileCompileAsyncWasmPluginOptions} [options] options object - */ - constructor({ import: useImport = false } = {}) { - this._import = useImport; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const globalWasmLoading = compilation.outputOptions.wasmLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const wasmLoading = - options && options.wasmLoading !== undefined - ? options.wasmLoading - : globalWasmLoading; - return wasmLoading === "async-node"; - }; - - /** - * @param {string} path path to wasm file - * @returns {string} generated code to load the wasm file - */ - const generateLoadBinaryCode = this._import - ? path => - Template.asString([ - "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", - Template.indent([ - `readFile(new URL(${path}, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, - Template.indent([ - "if (err) return reject(err);", - "", - "// Fake fetch response", - "resolve({", - Template.indent(["arrayBuffer() { return buffer; }"]), - "});" - ]), - "});" - ]), - "}))" - ]) - : path => - Template.asString([ - "new Promise(function (resolve, reject) {", - Template.indent([ - "try {", - Template.indent([ - "var { readFile } = require('fs');", - "var { join } = require('path');", - "", - `readFile(join(__dirname, ${path}), function(err, buffer){`, - Template.indent([ - "if (err) return reject(err);", - "", - "// Fake fetch response", - "resolve({", - Template.indent(["arrayBuffer() { return buffer; }"]), - "});" - ]), - "});" - ]), - "} catch (err) { reject(err); }" - ]), - "})" - ]); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.instantiateWasm) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC - ) - ) { - return; - } - compilation.addRuntimeModule( - chunk, - new AsyncWasmLoadingRuntimeModule({ - generateLoadBinaryCode, - supportsStreaming: false - }) - ); - }); - }); - } -} - -module.exports = ReadFileCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js b/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js deleted file mode 100644 index 59e58b5f30b..00000000000 --- a/webpack-lib/lib/node/ReadFileCompileWasmPlugin.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const WasmChunkLoadingRuntimeModule = require("../wasm-sync/WasmChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} ReadFileCompileWasmPluginOptions - * @property {boolean} [mangleImports] mangle imports - * @property {boolean} [import] use import? - */ - -// TODO webpack 6 remove - -const PLUGIN_NAME = "ReadFileCompileWasmPlugin"; - -class ReadFileCompileWasmPlugin { - /** - * @param {ReadFileCompileWasmPluginOptions} [options] options object - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const globalWasmLoading = compilation.outputOptions.wasmLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, when wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const wasmLoading = - options && options.wasmLoading !== undefined - ? options.wasmLoading - : globalWasmLoading; - return wasmLoading === "async-node"; - }; - - /** - * @param {string} path path to wasm file - * @returns {string} generated code to load the wasm file - */ - const generateLoadBinaryCode = this.options.import - ? path => - Template.asString([ - "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", - Template.indent([ - `readFile(new URL(${path}, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, - Template.indent([ - "if (err) return reject(err);", - "", - "// Fake fetch response", - "resolve({", - Template.indent(["arrayBuffer() { return buffer; }"]), - "});" - ]), - "});" - ]), - "}))" - ]) - : path => - Template.asString([ - "new Promise(function (resolve, reject) {", - Template.indent([ - "var { readFile } = require('fs');", - "var { join } = require('path');", - "", - "try {", - Template.indent([ - `readFile(join(__dirname, ${path}), function(err, buffer){`, - Template.indent([ - "if (err) return reject(err);", - "", - "// Fake fetch response", - "resolve({", - Template.indent(["arrayBuffer() { return buffer; }"]), - "});" - ]), - "});" - ]), - "} catch (err) { reject(err); }" - ]), - "})" - ]); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => m.type === WEBASSEMBLY_MODULE_TYPE_SYNC - ) - ) { - return; - } - set.add(RuntimeGlobals.moduleCache); - compilation.addRuntimeModule( - chunk, - new WasmChunkLoadingRuntimeModule({ - generateLoadBinaryCode, - supportsStreaming: false, - mangleImports: this.options.mangleImports, - runtimeRequirements: set - }) - ); - }); - }); - } -} - -module.exports = ReadFileCompileWasmPlugin; diff --git a/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js b/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js deleted file mode 100644 index 1d4959459d5..00000000000 --- a/webpack-lib/lib/node/RequireChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,246 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - chunkHasJs, - getChunkFilenameTemplate -} = require("../javascript/JavascriptModulesPlugin"); -const { getInitialChunkIds } = require("../javascript/StartupHelpers"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -class RequireChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("require chunk loading", RuntimeModule.STAGE_ATTACH); - this.runtimeRequirements = runtimeRequirements; - } - - /** - * @private - * @param {Chunk} chunk chunk - * @param {string} rootOutputDir root output directory - * @returns {string} generated code - */ - _generateBaseUri(chunk, rootOutputDir) { - const options = chunk.getEntryOptions(); - if (options && options.baseUri) { - return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; - } - - return `${RuntimeGlobals.baseURI} = require("url").pathToFileURL(${ - rootOutputDir !== "./" - ? `__dirname + ${JSON.stringify(`/${rootOutputDir}`)}` - : "__filename" - });`; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.ensureChunkHandlers; - const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); - const withExternalInstallChunk = this.runtimeRequirements.has( - RuntimeGlobals.externalInstallChunk - ); - const withOnChunkLoad = this.runtimeRequirements.has( - RuntimeGlobals.onChunksLoaded - ); - const withLoading = this.runtimeRequirements.has( - RuntimeGlobals.ensureChunkHandlers - ); - const withHmr = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const withHmrManifest = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadManifest - ); - const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); - const hasJsMatcher = compileBooleanMatcher(conditionMap); - const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); - - const outputName = compilation.getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ); - const rootOutputDir = getUndoPath( - outputName, - /** @type {string} */ (compilation.outputOptions.path), - true - ); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_require` - : undefined; - - return Template.asString([ - withBaseURI - ? this._generateBaseUri(chunk, rootOutputDir) - : "// no baseURI", - "", - "// object to store loaded chunks", - '// "1" means "loaded", otherwise not loaded yet', - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join( - ",\n" - ) - ), - "};", - "", - withOnChunkLoad - ? `${ - RuntimeGlobals.onChunksLoaded - }.require = ${runtimeTemplate.returningFunction( - "installedChunks[chunkId]", - "chunkId" - )};` - : "// no on chunks loaded", - "", - withLoading || withExternalInstallChunk - ? `var installChunk = ${runtimeTemplate.basicFunction("chunk", [ - "var moreModules = chunk.modules, chunkIds = chunk.ids, runtime = chunk.runtime;", - "for(var moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent([ - `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` - ]), - "}" - ]), - "}", - `if(runtime) runtime(${RuntimeGlobals.require});`, - "for(var i = 0; i < chunkIds.length; i++)", - Template.indent("installedChunks[chunkIds[i]] = 1;"), - withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : "" - ])};` - : "// no chunk install function needed", - "", - withLoading - ? Template.asString([ - "// require() chunk loading for javascript", - `${fn}.require = ${runtimeTemplate.basicFunction( - "chunkId, promises", - hasJsMatcher !== false - ? [ - '// "1" is the signal for "already loaded"', - "if(!installedChunks[chunkId]) {", - Template.indent([ - hasJsMatcher === true - ? "if(true) { // all chunks have JS" - : `if(${hasJsMatcher("chunkId")}) {`, - Template.indent([ - `installChunk(require(${JSON.stringify( - rootOutputDir - )} + ${ - RuntimeGlobals.getChunkScriptFilename - }(chunkId)));` - ]), - "} else installedChunks[chunkId] = 1;", - "" - ]), - "}" - ] - : "installedChunks[chunkId] = 1;" - )};` - ]) - : "// no chunk loading", - "", - withExternalInstallChunk - ? Template.asString([ - `module.exports = ${RuntimeGlobals.require};`, - `${RuntimeGlobals.externalInstallChunk} = installChunk;` - ]) - : "// no external install chunk", - "", - withHmr - ? Template.asString([ - "function loadUpdateChunk(chunkId, updatedModulesList) {", - Template.indent([ - `var update = require(${JSON.stringify(rootOutputDir)} + ${ - RuntimeGlobals.getChunkUpdateScriptFilename - }(chunkId));`, - "var updatedModules = update.modules;", - "var runtime = update.runtime;", - "for(var moduleId in updatedModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(updatedModules, moduleId)) {`, - Template.indent([ - "currentUpdate[moduleId] = updatedModules[moduleId];", - "if(updatedModulesList) updatedModulesList.push(moduleId);" - ]), - "}" - ]), - "}", - "if(runtime) currentUpdateRuntime.push(runtime);" - ]), - "}", - "", - Template.getFunctionContent( - require("../hmr/JavascriptHotModuleReplacement.runtime.js") - ) - .replace(/\$key\$/g, "require") - .replace(/\$installedChunks\$/g, "installedChunks") - .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") - .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) - .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) - .replace( - /\$ensureChunkHandlers\$/g, - RuntimeGlobals.ensureChunkHandlers - ) - .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) - .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) - .replace( - /\$hmrDownloadUpdateHandlers\$/g, - RuntimeGlobals.hmrDownloadUpdateHandlers - ) - .replace( - /\$hmrInvalidateModuleHandlers\$/g, - RuntimeGlobals.hmrInvalidateModuleHandlers - ) - ]) - : "// no HMR", - "", - withHmrManifest - ? Template.asString([ - `${RuntimeGlobals.hmrDownloadManifest} = function() {`, - Template.indent([ - "return Promise.resolve().then(function() {", - Template.indent([ - `return require(${JSON.stringify(rootOutputDir)} + ${ - RuntimeGlobals.getUpdateManifestFilename - }());` - ]), - "})['catch'](function(err) { if(err.code !== 'MODULE_NOT_FOUND') throw err; });" - ]), - "}" - ]) - : "// no HMR manifest" - ]); - } -} - -module.exports = RequireChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/node/nodeConsole.js b/webpack-lib/lib/node/nodeConsole.js deleted file mode 100644 index 9a1125ee543..00000000000 --- a/webpack-lib/lib/node/nodeConsole.js +++ /dev/null @@ -1,163 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const truncateArgs = require("../logging/truncateArgs"); - -/** @typedef {import("../logging/createConsoleLogger").LoggerConsole} LoggerConsole */ - -/** - * @param {object} options options - * @param {boolean=} options.colors colors - * @param {boolean=} options.appendOnly append only - * @param {NodeJS.WritableStream} options.stream stream - * @returns {LoggerConsole} logger function - */ -module.exports = ({ colors, appendOnly, stream }) => { - /** @type {string[] | undefined} */ - let currentStatusMessage; - let hasStatusMessage = false; - let currentIndent = ""; - let currentCollapsed = 0; - - /** - * @param {string} str string - * @param {string} prefix prefix - * @param {string} colorPrefix color prefix - * @param {string} colorSuffix color suffix - * @returns {string} indented string - */ - const indent = (str, prefix, colorPrefix, colorSuffix) => { - if (str === "") return str; - prefix = currentIndent + prefix; - if (colors) { - return ( - prefix + - colorPrefix + - str.replace(/\n/g, `${colorSuffix}\n${prefix}${colorPrefix}`) + - colorSuffix - ); - } - - return prefix + str.replace(/\n/g, `\n${prefix}`); - }; - - const clearStatusMessage = () => { - if (hasStatusMessage) { - stream.write("\u001B[2K\r"); - hasStatusMessage = false; - } - }; - - const writeStatusMessage = () => { - if (!currentStatusMessage) return; - const l = /** @type {TODO} */ (stream).columns || 40; - const args = truncateArgs(currentStatusMessage, l - 1); - const str = args.join(" "); - const coloredStr = `\u001B[1m${str}\u001B[39m\u001B[22m`; - stream.write(`\u001B[2K\r${coloredStr}`); - hasStatusMessage = true; - }; - - /** - * @param {string} prefix prefix - * @param {string} colorPrefix color prefix - * @param {string} colorSuffix color suffix - * @returns {(function(...EXPECTED_ANY[]): void)} function to write with colors - */ - const writeColored = - (prefix, colorPrefix, colorSuffix) => - (...args) => { - if (currentCollapsed > 0) return; - clearStatusMessage(); - const str = indent( - util.format(...args), - prefix, - colorPrefix, - colorSuffix - ); - stream.write(`${str}\n`); - writeStatusMessage(); - }; - - const writeGroupMessage = writeColored( - "<-> ", - "\u001B[1m\u001B[36m", - "\u001B[39m\u001B[22m" - ); - - const writeGroupCollapsedMessage = writeColored( - "<+> ", - "\u001B[1m\u001B[36m", - "\u001B[39m\u001B[22m" - ); - - return { - log: writeColored(" ", "\u001B[1m", "\u001B[22m"), - debug: writeColored(" ", "", ""), - trace: writeColored(" ", "", ""), - info: writeColored(" ", "\u001B[1m\u001B[32m", "\u001B[39m\u001B[22m"), - warn: writeColored(" ", "\u001B[1m\u001B[33m", "\u001B[39m\u001B[22m"), - error: writeColored(" ", "\u001B[1m\u001B[31m", "\u001B[39m\u001B[22m"), - logTime: writeColored( - " ", - "\u001B[1m\u001B[35m", - "\u001B[39m\u001B[22m" - ), - group: (...args) => { - writeGroupMessage(...args); - if (currentCollapsed > 0) { - currentCollapsed++; - } else { - currentIndent += " "; - } - }, - groupCollapsed: (...args) => { - writeGroupCollapsedMessage(...args); - currentCollapsed++; - }, - groupEnd: () => { - if (currentCollapsed > 0) currentCollapsed--; - else if (currentIndent.length >= 2) - currentIndent = currentIndent.slice(0, -2); - }, - profile: console.profile && (name => console.profile(name)), - profileEnd: console.profileEnd && (name => console.profileEnd(name)), - clear: - /** @type {() => void} */ - ( - !appendOnly && - console.clear && - (() => { - clearStatusMessage(); - console.clear(); - writeStatusMessage(); - }) - ), - status: appendOnly - ? writeColored(" ", "", "") - : (name, ...args) => { - args = args.filter(Boolean); - if (name === undefined && args.length === 0) { - clearStatusMessage(); - currentStatusMessage = undefined; - } else if ( - typeof name === "string" && - name.startsWith("[webpack.Progress] ") - ) { - currentStatusMessage = [name.slice(19), ...args]; - writeStatusMessage(); - } else if (name === "[webpack.Progress]") { - currentStatusMessage = [...args]; - writeStatusMessage(); - } else { - currentStatusMessage = [name, ...args]; - writeStatusMessage(); - } - } - }; -}; diff --git a/webpack-lib/lib/optimize/AggressiveMergingPlugin.js b/webpack-lib/lib/optimize/AggressiveMergingPlugin.js deleted file mode 100644 index e0d766a7db0..00000000000 --- a/webpack-lib/lib/optimize/AggressiveMergingPlugin.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_ADVANCED } = require("../OptimizationStages"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} AggressiveMergingPluginOptions - * @property {number=} minSizeReduce minimal size reduction to trigger merging - */ - -class AggressiveMergingPlugin { - /** - * @param {AggressiveMergingPluginOptions=} [options] options object - */ - constructor(options) { - if ( - (options !== undefined && typeof options !== "object") || - Array.isArray(options) - ) { - throw new Error( - "Argument should be an options object. To use defaults, pass in nothing.\nFor more info on options, see https://webpack.js.org/plugins/" - ); - } - this.options = options || {}; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - const minSizeReduce = options.minSizeReduce || 1.5; - - compiler.hooks.thisCompilation.tap( - "AggressiveMergingPlugin", - compilation => { - compilation.hooks.optimizeChunks.tap( - { - name: "AggressiveMergingPlugin", - stage: STAGE_ADVANCED - }, - chunks => { - const chunkGraph = compilation.chunkGraph; - /** @type {{a: Chunk, b: Chunk, improvement: number}[]} */ - const combinations = []; - for (const a of chunks) { - if (a.canBeInitial()) continue; - for (const b of chunks) { - if (b.canBeInitial()) continue; - if (b === a) break; - if (!chunkGraph.canChunksBeIntegrated(a, b)) { - continue; - } - const aSize = chunkGraph.getChunkSize(b, { - chunkOverhead: 0 - }); - const bSize = chunkGraph.getChunkSize(a, { - chunkOverhead: 0 - }); - const abSize = chunkGraph.getIntegratedChunksSize(b, a, { - chunkOverhead: 0 - }); - const improvement = (aSize + bSize) / abSize; - combinations.push({ - a, - b, - improvement - }); - } - } - - combinations.sort((a, b) => b.improvement - a.improvement); - - const pair = combinations[0]; - - if (!pair) return; - if (pair.improvement < minSizeReduce) return; - - chunkGraph.integrateChunks(pair.b, pair.a); - compilation.chunks.delete(pair.a); - return true; - } - ); - } - ); - } -} - -module.exports = AggressiveMergingPlugin; diff --git a/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js b/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js deleted file mode 100644 index f17ec25297c..00000000000 --- a/webpack-lib/lib/optimize/AggressiveSplittingPlugin.js +++ /dev/null @@ -1,348 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_ADVANCED } = require("../OptimizationStages"); -const { intersect } = require("../util/SetHelpers"); -const { - compareModulesByIdentifier, - compareChunks -} = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); -const identifierUtils = require("../util/identifier"); - -/** @typedef {import("../../declarations/plugins/optimize/AggressiveSplittingPlugin").AggressiveSplittingPluginOptions} AggressiveSplittingPluginOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.check.js"), - () => - require("../../schemas/plugins/optimize/AggressiveSplittingPlugin.json"), - { - name: "Aggressive Splitting Plugin", - baseDataPath: "options" - } -); - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Chunk} oldChunk the old chunk - * @param {Chunk} newChunk the new chunk - * @returns {(module: Module) => void} function to move module between chunks - */ -const moveModuleBetween = (chunkGraph, oldChunk, newChunk) => module => { - chunkGraph.disconnectChunkAndModule(oldChunk, module); - chunkGraph.connectChunkAndModule(newChunk, module); -}; - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Chunk} chunk the chunk - * @returns {function(Module): boolean} filter for entry module - */ -const isNotAEntryModule = (chunkGraph, chunk) => module => - !chunkGraph.isEntryModuleInChunk(module, chunk); - -/** @type {WeakSet} */ -const recordedChunks = new WeakSet(); - -class AggressiveSplittingPlugin { - /** - * @param {AggressiveSplittingPluginOptions=} options options object - */ - constructor(options = {}) { - validate(options); - - this.options = options; - if (typeof this.options.minSize !== "number") { - this.options.minSize = 30 * 1024; - } - if (typeof this.options.maxSize !== "number") { - this.options.maxSize = 50 * 1024; - } - if (typeof this.options.chunkOverhead !== "number") { - this.options.chunkOverhead = 0; - } - if (typeof this.options.entryChunkMultiplicator !== "number") { - this.options.entryChunkMultiplicator = 1; - } - } - - /** - * @param {Chunk} chunk the chunk to test - * @returns {boolean} true if the chunk was recorded - */ - static wasChunkRecorded(chunk) { - return recordedChunks.has(chunk); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "AggressiveSplittingPlugin", - compilation => { - let needAdditionalSeal = false; - /** @typedef {{ id?: NonNullable, hash?: NonNullable, modules: Module[], size: number }} SplitData */ - /** @type {SplitData[]} */ - let newSplits; - /** @type {Set} */ - let fromAggressiveSplittingSet; - /** @type {Map} */ - let chunkSplitDataMap; - compilation.hooks.optimize.tap("AggressiveSplittingPlugin", () => { - newSplits = []; - fromAggressiveSplittingSet = new Set(); - chunkSplitDataMap = new Map(); - }); - compilation.hooks.optimizeChunks.tap( - { - name: "AggressiveSplittingPlugin", - stage: STAGE_ADVANCED - }, - chunks => { - const chunkGraph = compilation.chunkGraph; - // Precompute stuff - const nameToModuleMap = new Map(); - const moduleToNameMap = new Map(); - const makePathsRelative = - identifierUtils.makePathsRelative.bindContextCache( - compiler.context, - compiler.root - ); - for (const m of compilation.modules) { - const name = makePathsRelative(m.identifier()); - nameToModuleMap.set(name, m); - moduleToNameMap.set(m, name); - } - - // Check used chunk ids - const usedIds = new Set(); - for (const chunk of chunks) { - usedIds.add(chunk.id); - } - - const recordedSplits = - (compilation.records && compilation.records.aggressiveSplits) || - []; - const usedSplits = newSplits - ? recordedSplits.concat(newSplits) - : recordedSplits; - - const minSize = /** @type {number} */ (this.options.minSize); - const maxSize = /** @type {number} */ (this.options.maxSize); - - /** - * @param {SplitData} splitData split data - * @returns {boolean} true when applied, otherwise false - */ - const applySplit = splitData => { - // Cannot split if id is already taken - if (splitData.id !== undefined && usedIds.has(splitData.id)) { - return false; - } - - // Get module objects from names - const selectedModules = splitData.modules.map(name => - nameToModuleMap.get(name) - ); - - // Does the modules exist at all? - if (!selectedModules.every(Boolean)) return false; - - // Check if size matches (faster than waiting for hash) - let size = 0; - for (const m of selectedModules) size += m.size(); - if (size !== splitData.size) return false; - - // get chunks with all modules - const selectedChunks = intersect( - selectedModules.map( - m => new Set(chunkGraph.getModuleChunksIterable(m)) - ) - ); - - // No relevant chunks found - if (selectedChunks.size === 0) return false; - - // The found chunk is already the split or similar - if ( - selectedChunks.size === 1 && - chunkGraph.getNumberOfChunkModules( - Array.from(selectedChunks)[0] - ) === selectedModules.length - ) { - const chunk = Array.from(selectedChunks)[0]; - if (fromAggressiveSplittingSet.has(chunk)) return false; - fromAggressiveSplittingSet.add(chunk); - chunkSplitDataMap.set(chunk, splitData); - return true; - } - - // split the chunk into two parts - const newChunk = compilation.addChunk(); - newChunk.chunkReason = "aggressive splitted"; - for (const chunk of selectedChunks) { - for (const module of selectedModules) { - moveModuleBetween(chunkGraph, chunk, newChunk)(module); - } - chunk.split(newChunk); - chunk.name = /** @type {TODO} */ (null); - } - fromAggressiveSplittingSet.add(newChunk); - chunkSplitDataMap.set(newChunk, splitData); - - if (splitData.id !== null && splitData.id !== undefined) { - newChunk.id = splitData.id; - newChunk.ids = [splitData.id]; - } - return true; - }; - - // try to restore to recorded splitting - let changed = false; - for (let j = 0; j < usedSplits.length; j++) { - const splitData = usedSplits[j]; - if (applySplit(splitData)) changed = true; - } - - // for any chunk which isn't splitted yet, split it and create a new entry - // start with the biggest chunk - const cmpFn = compareChunks(chunkGraph); - const sortedChunks = Array.from(chunks).sort((a, b) => { - const diff1 = - chunkGraph.getChunkModulesSize(b) - - chunkGraph.getChunkModulesSize(a); - if (diff1) return diff1; - const diff2 = - chunkGraph.getNumberOfChunkModules(a) - - chunkGraph.getNumberOfChunkModules(b); - if (diff2) return diff2; - return cmpFn(a, b); - }); - for (const chunk of sortedChunks) { - if (fromAggressiveSplittingSet.has(chunk)) continue; - const size = chunkGraph.getChunkModulesSize(chunk); - if ( - size > maxSize && - chunkGraph.getNumberOfChunkModules(chunk) > 1 - ) { - const modules = chunkGraph - .getOrderedChunkModules(chunk, compareModulesByIdentifier) - .filter(isNotAEntryModule(chunkGraph, chunk)); - const selectedModules = []; - let selectedModulesSize = 0; - for (let k = 0; k < modules.length; k++) { - const module = modules[k]; - const newSize = selectedModulesSize + module.size(); - if (newSize > maxSize && selectedModulesSize >= minSize) { - break; - } - selectedModulesSize = newSize; - selectedModules.push(module); - } - if (selectedModules.length === 0) continue; - /** @type {SplitData} */ - const splitData = { - modules: selectedModules - .map(m => moduleToNameMap.get(m)) - .sort(), - size: selectedModulesSize - }; - - if (applySplit(splitData)) { - newSplits = (newSplits || []).concat(splitData); - changed = true; - } - } - } - if (changed) return true; - } - ); - compilation.hooks.recordHash.tap( - "AggressiveSplittingPlugin", - records => { - // 4. save made splittings to records - const allSplits = new Set(); - /** @type {Set} */ - const invalidSplits = new Set(); - - // Check if some splittings are invalid - // We remove invalid splittings and try again - for (const chunk of compilation.chunks) { - const splitData = chunkSplitDataMap.get(chunk); - if ( - splitData !== undefined && - splitData.hash && - chunk.hash !== splitData.hash - ) { - // Split was successful, but hash doesn't equal - // We can throw away the split since it's useless now - invalidSplits.add(splitData); - } - } - - if (invalidSplits.size > 0) { - records.aggressiveSplits = - /** @type {SplitData[]} */ - (records.aggressiveSplits).filter( - splitData => !invalidSplits.has(splitData) - ); - needAdditionalSeal = true; - } else { - // set hash and id values on all (new) splittings - for (const chunk of compilation.chunks) { - const splitData = chunkSplitDataMap.get(chunk); - if (splitData !== undefined) { - splitData.hash = - /** @type {NonNullable} */ - (chunk.hash); - splitData.id = - /** @type {NonNullable} */ - (chunk.id); - allSplits.add(splitData); - // set flag for stats - recordedChunks.add(chunk); - } - } - - // Also add all unused historical splits (after the used ones) - // They can still be used in some future compilation - const recordedSplits = - compilation.records && compilation.records.aggressiveSplits; - if (recordedSplits) { - for (const splitData of recordedSplits) { - if (!invalidSplits.has(splitData)) allSplits.add(splitData); - } - } - - // record all splits - records.aggressiveSplits = Array.from(allSplits); - - needAdditionalSeal = false; - } - } - ); - compilation.hooks.needAdditionalSeal.tap( - "AggressiveSplittingPlugin", - () => { - if (needAdditionalSeal) { - needAdditionalSeal = false; - return true; - } - } - ); - } - ); - } -} -module.exports = AggressiveSplittingPlugin; diff --git a/webpack-lib/lib/optimize/ConcatenatedModule.js b/webpack-lib/lib/optimize/ConcatenatedModule.js deleted file mode 100644 index c305c3ddedf..00000000000 --- a/webpack-lib/lib/optimize/ConcatenatedModule.js +++ /dev/null @@ -1,1904 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const eslintScope = require("eslint-scope"); -const Referencer = require("eslint-scope/lib/referencer"); -const { SyncBailHook } = require("tapable"); -const { - CachedSource, - ConcatSource, - ReplaceSource -} = require("webpack-sources"); -const ConcatenationScope = require("../ConcatenationScope"); -const { UsageState } = require("../ExportsInfo"); -const Module = require("../Module"); -const { JS_TYPES } = require("../ModuleSourceTypesConstants"); -const { JAVASCRIPT_MODULE_TYPE_ESM } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); -const JavascriptParser = require("../javascript/JavascriptParser"); -const { equals } = require("../util/ArrayHelpers"); -const LazySet = require("../util/LazySet"); -const { concatComparators } = require("../util/comparators"); -const { - RESERVED_NAMES, - findNewName, - addScopeSymbols, - getAllReferences, - getPathInAst, - getUsedNamesInScopeInfo -} = require("../util/concatenate"); -const createHash = require("../util/createHash"); -const { makePathsRelative } = require("../util/identifier"); -const makeSerializable = require("../util/makeSerializable"); -const propertyAccess = require("../util/propertyAccess"); -const { propertyName } = require("../util/propertyName"); -const { - filterRuntime, - intersectRuntime, - mergeRuntimeCondition, - mergeRuntimeConditionNonFalse, - runtimeConditionToString, - subtractRuntimeCondition -} = require("../util/runtime"); - -/** @typedef {import("eslint-scope").Reference} Reference */ -/** @typedef {import("eslint-scope").Scope} Scope */ -/** @typedef {import("eslint-scope").Variable} Variable */ -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("../Module").RuntimeRequirements} RuntimeRequirements */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../ModuleParseError")} ModuleParseError */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").ChunkRenderContext} ChunkRenderContext */ -/** @typedef {import("../javascript/JavascriptParser").Program} Program */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../util/Hash")} Hash */ -/** @typedef {typeof import("../util/Hash")} HashConstructor */ -/** @typedef {import("../util/concatenate").UsedNames} UsedNames */ -/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @template T - * @typedef {import("../InitFragment")} InitFragment - */ - -/** - * @template T - * @typedef {import("../util/comparators").Comparator} Comparator - */ - -// fix eslint-scope to support class properties correctly -// cspell:word Referencer -const ReferencerClass = /** @type {any} */ (Referencer); -if (!ReferencerClass.prototype.PropertyDefinition) { - ReferencerClass.prototype.PropertyDefinition = - ReferencerClass.prototype.Property; -} - -/** - * @typedef {object} ReexportInfo - * @property {Module} module - * @property {string[]} export - */ - -/** @typedef {RawBinding | SymbolBinding} Binding */ - -/** - * @typedef {object} RawBinding - * @property {ModuleInfo} info - * @property {string} rawName - * @property {string=} comment - * @property {string[]} ids - * @property {string[]} exportName - */ - -/** - * @typedef {object} SymbolBinding - * @property {ConcatenatedModuleInfo} info - * @property {string} name - * @property {string=} comment - * @property {string[]} ids - * @property {string[]} exportName - */ - -/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo } ModuleInfo */ -/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo | ReferenceToModuleInfo } ModuleInfoOrReference */ - -/** - * @typedef {object} ConcatenatedModuleInfo - * @property {"concatenated"} type - * @property {Module} module - * @property {number} index - * @property {Program | undefined} ast - * @property {Source | undefined} internalSource - * @property {ReplaceSource | undefined} source - * @property {InitFragment[]=} chunkInitFragments - * @property {ReadOnlyRuntimeRequirements | undefined} runtimeRequirements - * @property {Scope | undefined} globalScope - * @property {Scope | undefined} moduleScope - * @property {Map} internalNames - * @property {Map | undefined} exportMap - * @property {Map | undefined} rawExportMap - * @property {string=} namespaceExportSymbol - * @property {string | undefined} namespaceObjectName - * @property {boolean} interopNamespaceObjectUsed - * @property {string | undefined} interopNamespaceObjectName - * @property {boolean} interopNamespaceObject2Used - * @property {string | undefined} interopNamespaceObject2Name - * @property {boolean} interopDefaultAccessUsed - * @property {string | undefined} interopDefaultAccessName - */ - -/** - * @typedef {object} ExternalModuleInfo - * @property {"external"} type - * @property {Module} module - * @property {RuntimeSpec | boolean} runtimeCondition - * @property {number} index - * @property {string | undefined} name - * @property {boolean} interopNamespaceObjectUsed - * @property {string | undefined} interopNamespaceObjectName - * @property {boolean} interopNamespaceObject2Used - * @property {string | undefined} interopNamespaceObject2Name - * @property {boolean} interopDefaultAccessUsed - * @property {string | undefined} interopDefaultAccessName - */ - -/** - * @typedef {object} ReferenceToModuleInfo - * @property {"reference"} type - * @property {RuntimeSpec | boolean} runtimeCondition - * @property {ModuleInfo} target - */ - -/** - * @template T - * @param {string} property property - * @param {function(T[keyof T], T[keyof T]): 0 | 1 | -1} comparator comparator - * @returns {Comparator} comparator - */ - -const createComparator = (property, comparator) => (a, b) => - comparator( - a[/** @type {keyof T} */ (property)], - b[/** @type {keyof T} */ (property)] - ); - -/** - * @param {number} a a - * @param {number} b b - * @returns {0 | 1 | -1} result - */ -const compareNumbers = (a, b) => { - if (Number.isNaN(a)) { - if (!Number.isNaN(b)) { - return 1; - } - } else { - if (Number.isNaN(b)) { - return -1; - } - if (a !== b) { - return a < b ? -1 : 1; - } - } - return 0; -}; -const bySourceOrder = createComparator("sourceOrder", compareNumbers); -const byRangeStart = createComparator("rangeStart", compareNumbers); - -/** - * @param {Iterable} iterable iterable object - * @returns {string} joined iterable object - */ -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; -}; - -/** - * @typedef {object} ConcatenationEntry - * @property {"concatenated" | "external"} type - * @property {Module} module - * @property {RuntimeSpec | boolean} runtimeCondition - */ - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {ModuleInfo} info module info - * @param {string[]} exportName exportName - * @param {Map} moduleToInfoMap moduleToInfoMap - * @param {RuntimeSpec} runtime for which runtime - * @param {RequestShortener} requestShortener the request shortener - * @param {RuntimeTemplate} runtimeTemplate the runtime template - * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated - * @param {boolean} asCall asCall - * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule - * @param {boolean | undefined} asiSafe asiSafe - * @param {Set} alreadyVisited alreadyVisited - * @returns {Binding} the final variable - */ -const getFinalBinding = ( - moduleGraph, - info, - exportName, - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - asCall, - strictHarmonyModule, - asiSafe, - alreadyVisited = new Set() -) => { - const exportsType = info.module.getExportsType( - moduleGraph, - strictHarmonyModule - ); - if (exportName.length === 0) { - switch (exportsType) { - case "default-only": - info.interopNamespaceObject2Used = true; - return { - info, - rawName: /** @type {string} */ (info.interopNamespaceObject2Name), - ids: exportName, - exportName - }; - case "default-with-named": - info.interopNamespaceObjectUsed = true; - return { - info, - rawName: /** @type {string} */ (info.interopNamespaceObjectName), - ids: exportName, - exportName - }; - case "namespace": - case "dynamic": - break; - default: - throw new Error(`Unexpected exportsType ${exportsType}`); - } - } else { - switch (exportsType) { - case "namespace": - break; - case "default-with-named": - switch (exportName[0]) { - case "default": - exportName = exportName.slice(1); - break; - case "__esModule": - return { - info, - rawName: "/* __esModule */true", - ids: exportName.slice(1), - exportName - }; - } - break; - case "default-only": { - const exportId = exportName[0]; - if (exportId === "__esModule") { - return { - info, - rawName: "/* __esModule */true", - ids: exportName.slice(1), - exportName - }; - } - exportName = exportName.slice(1); - if (exportId !== "default") { - return { - info, - rawName: - "/* non-default import from default-exporting module */undefined", - ids: exportName, - exportName - }; - } - break; - } - case "dynamic": - switch (exportName[0]) { - case "default": { - exportName = exportName.slice(1); - info.interopDefaultAccessUsed = true; - const defaultExport = asCall - ? `${info.interopDefaultAccessName}()` - : asiSafe - ? `(${info.interopDefaultAccessName}())` - : asiSafe === false - ? `;(${info.interopDefaultAccessName}())` - : `${info.interopDefaultAccessName}.a`; - return { - info, - rawName: defaultExport, - ids: exportName, - exportName - }; - } - case "__esModule": - return { - info, - rawName: "/* __esModule */true", - ids: exportName.slice(1), - exportName - }; - } - break; - default: - throw new Error(`Unexpected exportsType ${exportsType}`); - } - } - if (exportName.length === 0) { - switch (info.type) { - case "concatenated": - neededNamespaceObjects.add(info); - return { - info, - rawName: - /** @type {NonNullable} */ - (info.namespaceObjectName), - ids: exportName, - exportName - }; - case "external": - return { - info, - rawName: - /** @type {NonNullable} */ - (info.name), - ids: exportName, - exportName - }; - } - } - const exportsInfo = moduleGraph.getExportsInfo(info.module); - const exportInfo = exportsInfo.getExportInfo(exportName[0]); - if (alreadyVisited.has(exportInfo)) { - return { - info, - rawName: "/* circular reexport */ Object(function x() { x() }())", - ids: [], - exportName - }; - } - alreadyVisited.add(exportInfo); - switch (info.type) { - case "concatenated": { - const exportId = exportName[0]; - if (exportInfo.provided === false) { - // It's not provided, but it could be on the prototype - neededNamespaceObjects.add(info); - return { - info, - rawName: /** @type {string} */ (info.namespaceObjectName), - ids: exportName, - exportName - }; - } - const directExport = info.exportMap && info.exportMap.get(exportId); - if (directExport) { - const usedName = /** @type {string[]} */ ( - exportsInfo.getUsedName(exportName, runtime) - ); - if (!usedName) { - return { - info, - rawName: "/* unused export */ undefined", - ids: exportName.slice(1), - exportName - }; - } - return { - info, - name: directExport, - ids: usedName.slice(1), - exportName - }; - } - const rawExport = info.rawExportMap && info.rawExportMap.get(exportId); - if (rawExport) { - return { - info, - rawName: rawExport, - ids: exportName.slice(1), - exportName - }; - } - const reexport = exportInfo.findTarget(moduleGraph, module => - moduleToInfoMap.has(module) - ); - if (reexport === false) { - throw new Error( - `Target module of reexport from '${info.module.readableIdentifier( - requestShortener - )}' is not part of the concatenation (export '${exportId}')\nModules in the concatenation:\n${Array.from( - moduleToInfoMap, - ([m, info]) => - ` * ${info.type} ${m.readableIdentifier(requestShortener)}` - ).join("\n")}` - ); - } - if (reexport) { - const refInfo = moduleToInfoMap.get(reexport.module); - return getFinalBinding( - moduleGraph, - /** @type {ModuleInfo} */ (refInfo), - reexport.export - ? [...reexport.export, ...exportName.slice(1)] - : exportName.slice(1), - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - asCall, - /** @type {BuildMeta} */ - (info.module.buildMeta).strictHarmonyModule, - asiSafe, - alreadyVisited - ); - } - if (info.namespaceExportSymbol) { - const usedName = /** @type {string[]} */ ( - exportsInfo.getUsedName(exportName, runtime) - ); - return { - info, - rawName: /** @type {string} */ (info.namespaceObjectName), - ids: usedName, - exportName - }; - } - throw new Error( - `Cannot get final name for export '${exportName.join( - "." - )}' of ${info.module.readableIdentifier(requestShortener)}` - ); - } - - case "external": { - const used = /** @type {string[]} */ ( - exportsInfo.getUsedName(exportName, runtime) - ); - if (!used) { - return { - info, - rawName: "/* unused export */ undefined", - ids: exportName.slice(1), - exportName - }; - } - const comment = equals(used, exportName) - ? "" - : Template.toNormalComment(`${exportName.join(".")}`); - return { info, rawName: info.name + comment, ids: used, exportName }; - } - } -}; - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {ModuleInfo} info module info - * @param {string[]} exportName exportName - * @param {Map} moduleToInfoMap moduleToInfoMap - * @param {RuntimeSpec} runtime for which runtime - * @param {RequestShortener} requestShortener the request shortener - * @param {RuntimeTemplate} runtimeTemplate the runtime template - * @param {Set} neededNamespaceObjects modules for which a namespace object should be generated - * @param {boolean} asCall asCall - * @param {boolean | undefined} callContext callContext - * @param {boolean | undefined} strictHarmonyModule strictHarmonyModule - * @param {boolean | undefined} asiSafe asiSafe - * @returns {string} the final name - */ -const getFinalName = ( - moduleGraph, - info, - exportName, - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - asCall, - callContext, - strictHarmonyModule, - asiSafe -) => { - const binding = getFinalBinding( - moduleGraph, - info, - exportName, - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - asCall, - strictHarmonyModule, - asiSafe - ); - { - const { ids, comment } = binding; - let reference; - let isPropertyAccess; - if ("rawName" in binding) { - reference = `${binding.rawName}${comment || ""}${propertyAccess(ids)}`; - isPropertyAccess = ids.length > 0; - } else { - const { info, name: exportId } = binding; - const name = info.internalNames.get(exportId); - if (!name) { - throw new Error( - `The export "${exportId}" in "${info.module.readableIdentifier( - requestShortener - )}" has no internal name (existing names: ${ - Array.from( - info.internalNames, - ([name, symbol]) => `${name}: ${symbol}` - ).join(", ") || "none" - })` - ); - } - reference = `${name}${comment || ""}${propertyAccess(ids)}`; - isPropertyAccess = ids.length > 1; - } - if (isPropertyAccess && asCall && callContext === false) { - return asiSafe - ? `(0,${reference})` - : asiSafe === false - ? `;(0,${reference})` - : `/*#__PURE__*/Object(${reference})`; - } - return reference; - } -}; - -/** - * @typedef {object} ConcatenateModuleHooks - * @property {SyncBailHook<[Record], boolean | void>} exportsDefinitions - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class ConcatenatedModule extends Module { - /** - * @param {Module} rootModule the root module of the concatenation - * @param {Set} modules all modules in the concatenation (including the root module) - * @param {RuntimeSpec} runtime the runtime - * @param {Compilation} compilation the compilation - * @param {object=} associatedObjectForCache object for caching - * @param {string | HashConstructor=} hashFunction hash function to use - * @returns {ConcatenatedModule} the module - */ - static create( - rootModule, - modules, - runtime, - compilation, - associatedObjectForCache, - hashFunction = "md4" - ) { - const identifier = ConcatenatedModule._createIdentifier( - rootModule, - modules, - associatedObjectForCache, - hashFunction - ); - return new ConcatenatedModule({ - identifier, - rootModule, - modules, - runtime, - compilation - }); - } - - /** - * @param {Compilation} compilation the compilation - * @returns {ConcatenateModuleHooks} the attached hooks - */ - static getCompilationHooks(compilation) { - let hooks = compilationHooksMap.get(compilation); - if (hooks === undefined) { - hooks = { - exportsDefinitions: new SyncBailHook(["definitions"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {object} options options - * @param {string} options.identifier the identifier of the module - * @param {Module} options.rootModule the root module of the concatenation - * @param {RuntimeSpec} options.runtime the selected runtime - * @param {Set} options.modules all concatenated modules - * @param {Compilation} options.compilation the compilation - */ - constructor({ identifier, rootModule, modules, runtime, compilation }) { - super(JAVASCRIPT_MODULE_TYPE_ESM, null, rootModule && rootModule.layer); - - // Info from Factory - /** @type {string} */ - this._identifier = identifier; - /** @type {Module} */ - this.rootModule = rootModule; - /** @type {Set} */ - this._modules = modules; - this._runtime = runtime; - this.factoryMeta = rootModule && rootModule.factoryMeta; - /** @type {Compilation | undefined} */ - this.compilation = compilation; - } - - /** - * 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) { - throw new Error("Must not be called"); - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return JS_TYPES; - } - - get modules() { - return Array.from(this._modules); - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return this._identifier; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `${this.rootModule.readableIdentifier( - requestShortener - )} + ${this._modules.size - 1} modules`; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return this.rootModule.libIdent(options); - } - - /** - * @returns {string | null} absolute path which should be used for condition matching (usually the resource path) - */ - nameForCondition() { - return this.rootModule.nameForCondition(); - } - - /** - * @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 this.rootModule.getSideEffectsConnectionState(moduleGraph); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - const { rootModule } = this; - const { moduleArgument, exportsArgument } = - /** @type {BuildInfo} */ - (rootModule.buildInfo); - this.buildInfo = { - strict: true, - cacheable: true, - moduleArgument, - exportsArgument, - fileDependencies: new LazySet(), - contextDependencies: new LazySet(), - missingDependencies: new LazySet(), - topLevelDeclarations: new Set(), - assets: undefined - }; - this.buildMeta = rootModule.buildMeta; - this.clearDependenciesAndBlocks(); - this.clearWarningsAndErrors(); - - for (const m of this._modules) { - // populate cacheable - if (!(/** @type {BuildInfo} */ (m.buildInfo).cacheable)) { - this.buildInfo.cacheable = false; - } - - // populate dependencies - for (const d of m.dependencies.filter( - dep => - !(dep instanceof HarmonyImportDependency) || - !this._modules.has( - /** @type {Module} */ (compilation.moduleGraph.getModule(dep)) - ) - )) { - this.dependencies.push(d); - } - // populate blocks - for (const d of m.blocks) { - this.blocks.push(d); - } - - // populate warnings - const warnings = m.getWarnings(); - if (warnings !== undefined) { - for (const warning of warnings) { - this.addWarning(warning); - } - } - - // populate errors - const errors = m.getErrors(); - if (errors !== undefined) { - for (const error of errors) { - this.addError(error); - } - } - - const { assets, assetsInfo, topLevelDeclarations } = - /** @type {BuildInfo} */ (m.buildInfo); - - // populate topLevelDeclarations - if (topLevelDeclarations) { - const topLevelDeclarations = this.buildInfo.topLevelDeclarations; - if (topLevelDeclarations !== undefined) { - for (const decl of topLevelDeclarations) { - topLevelDeclarations.add(decl); - } - } - } else { - this.buildInfo.topLevelDeclarations = undefined; - } - - // populate assets - if (assets) { - if (this.buildInfo.assets === undefined) { - this.buildInfo.assets = Object.create(null); - } - Object.assign( - /** @type {NonNullable} */ - ( - /** @type {BuildInfo} */ - (this.buildInfo).assets - ), - assets - ); - } - if (assetsInfo) { - if (this.buildInfo.assetsInfo === undefined) { - this.buildInfo.assetsInfo = new Map(); - } - for (const [key, value] of assetsInfo) { - this.buildInfo.assetsInfo.set(key, value); - } - } - } - callback(); - } - - /** - * @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) { - // Guess size from embedded modules - let size = 0; - for (const module of this._modules) { - size += module.size(type); - } - return size; - } - - /** - * @private - * @param {Module} rootModule the root of the concatenation - * @param {Set} modulesSet a set of modules which should be concatenated - * @param {RuntimeSpec} runtime for this runtime - * @param {ModuleGraph} moduleGraph the module graph - * @returns {ConcatenationEntry[]} concatenation list - */ - _createConcatenationList(rootModule, modulesSet, runtime, moduleGraph) { - /** @type {ConcatenationEntry[]} */ - const list = []; - /** @type {Map} */ - const existingEntries = new Map(); - - /** - * @param {Module} module a module - * @returns {Iterable<{ connection: ModuleGraphConnection, runtimeCondition: RuntimeSpec | true }>} imported modules in order - */ - const getConcatenatedImports = module => { - const connections = Array.from( - moduleGraph.getOutgoingConnections(module) - ); - if (module === rootModule) { - for (const c of moduleGraph.getOutgoingConnections(this)) - connections.push(c); - } - /** - * @type {Array<{ connection: ModuleGraphConnection, sourceOrder: number, rangeStart: number }>} - */ - const references = connections - .filter(connection => { - if (!(connection.dependency instanceof HarmonyImportDependency)) - return false; - return ( - connection && - connection.resolvedOriginModule === module && - connection.module && - connection.isTargetActive(runtime) - ); - }) - .map(connection => { - const dep = /** @type {HarmonyImportDependency} */ ( - connection.dependency - ); - return { - connection, - sourceOrder: dep.sourceOrder, - rangeStart: dep.range && dep.range[0] - }; - }); - /** - * bySourceOrder - * @example - * import a from "a"; // sourceOrder=1 - * import b from "b"; // sourceOrder=2 - * - * byRangeStart - * @example - * import {a, b} from "a"; // sourceOrder=1 - * a.a(); // first range - * b.b(); // second range - * - * If there is no reexport, we have the same source. - * If there is reexport, but module has side effects, this will lead to reexport module only. - * If there is side-effects-free reexport, we can get simple deterministic result with range start comparison. - */ - references.sort(concatComparators(bySourceOrder, byRangeStart)); - /** @type {Map} */ - const referencesMap = new Map(); - for (const { connection } of references) { - const runtimeCondition = filterRuntime(runtime, r => - connection.isTargetActive(r) - ); - if (runtimeCondition === false) continue; - const module = connection.module; - const entry = referencesMap.get(module); - if (entry === undefined) { - referencesMap.set(module, { connection, runtimeCondition }); - continue; - } - entry.runtimeCondition = mergeRuntimeConditionNonFalse( - entry.runtimeCondition, - runtimeCondition, - runtime - ); - } - return referencesMap.values(); - }; - - /** - * @param {ModuleGraphConnection} connection graph connection - * @param {RuntimeSpec | true} runtimeCondition runtime condition - * @returns {void} - */ - const enterModule = (connection, runtimeCondition) => { - const module = connection.module; - if (!module) return; - const existingEntry = existingEntries.get(module); - if (existingEntry === true) { - return; - } - if (modulesSet.has(module)) { - existingEntries.set(module, true); - if (runtimeCondition !== true) { - throw new Error( - `Cannot runtime-conditional concatenate a module (${module.identifier()} in ${this.rootModule.identifier()}, ${runtimeConditionToString( - runtimeCondition - )}). This should not happen.` - ); - } - const imports = getConcatenatedImports(module); - for (const { connection, runtimeCondition } of imports) - enterModule(connection, runtimeCondition); - list.push({ - type: "concatenated", - module: connection.module, - runtimeCondition - }); - } else { - if (existingEntry !== undefined) { - const reducedRuntimeCondition = subtractRuntimeCondition( - runtimeCondition, - existingEntry, - runtime - ); - if (reducedRuntimeCondition === false) return; - runtimeCondition = reducedRuntimeCondition; - existingEntries.set( - connection.module, - mergeRuntimeConditionNonFalse( - existingEntry, - runtimeCondition, - runtime - ) - ); - } else { - existingEntries.set(connection.module, runtimeCondition); - } - if (list.length > 0) { - const lastItem = list[list.length - 1]; - if ( - lastItem.type === "external" && - lastItem.module === connection.module - ) { - lastItem.runtimeCondition = mergeRuntimeCondition( - lastItem.runtimeCondition, - runtimeCondition, - runtime - ); - return; - } - } - list.push({ - type: "external", - get module() { - // We need to use a getter here, because the module in the dependency - // could be replaced by some other process (i. e. also replaced with a - // concatenated module) - return connection.module; - }, - runtimeCondition - }); - } - }; - - existingEntries.set(rootModule, true); - const imports = getConcatenatedImports(rootModule); - for (const { connection, runtimeCondition } of imports) - enterModule(connection, runtimeCondition); - list.push({ - type: "concatenated", - module: rootModule, - runtimeCondition: true - }); - - return list; - } - - /** - * @param {Module} rootModule the root module of the concatenation - * @param {Set} modules all modules in the concatenation (including the root module) - * @param {object=} associatedObjectForCache object for caching - * @param {string | HashConstructor=} hashFunction hash function to use - * @returns {string} the identifier - */ - static _createIdentifier( - rootModule, - modules, - associatedObjectForCache, - hashFunction = "md4" - ) { - const cachedMakePathsRelative = makePathsRelative.bindContextCache( - /** @type {string} */ (rootModule.context), - associatedObjectForCache - ); - const identifiers = []; - for (const module of modules) { - identifiers.push(cachedMakePathsRelative(module.identifier())); - } - identifiers.sort(); - const hash = createHash(hashFunction); - hash.update(identifiers.join(" ")); - return `${rootModule.identifier()}|${hash.digest("hex")}`; - } - - /** - * @param {LazySet} fileDependencies set where file dependencies are added to - * @param {LazySet} contextDependencies set where context dependencies are added to - * @param {LazySet} missingDependencies set where missing dependencies are added to - * @param {LazySet} buildDependencies set where build dependencies are added to - */ - addCacheDependencies( - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - ) { - for (const module of this._modules) { - module.addCacheDependencies( - fileDependencies, - contextDependencies, - missingDependencies, - buildDependencies - ); - } - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime: generationRuntime, - codeGenerationResults - }) { - /** @type {RuntimeRequirements} */ - const runtimeRequirements = new Set(); - const runtime = intersectRuntime(generationRuntime, this._runtime); - - const requestShortener = runtimeTemplate.requestShortener; - // Meta info for each module - const [modulesWithInfo, moduleToInfoMap] = this._getModulesWithInfo( - moduleGraph, - runtime - ); - - // Set with modules that need a generated namespace object - /** @type {Set} */ - const neededNamespaceObjects = new Set(); - - // Generate source code and analyse scopes - // Prepare a ReplaceSource for the final source - for (const info of moduleToInfoMap.values()) { - this._analyseModule( - moduleToInfoMap, - info, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime, - /** @type {CodeGenerationResults} */ - (codeGenerationResults) - ); - } - - // List of all used names to avoid conflicts - const allUsedNames = new Set(RESERVED_NAMES); - // Updated Top level declarations are created by renaming - const topLevelDeclarations = new Set(); - - // List of additional names in scope for module references - /** @type {Map }>} */ - const usedNamesInScopeInfo = new Map(); - /** - * @param {string} module module identifier - * @param {string} id export id - * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set }} info - */ - - // Set of already checked scopes - const ignoredScopes = new Set(); - - // get all global names - for (const info of modulesWithInfo) { - if (info.type === "concatenated") { - // ignore symbols from moduleScope - if (info.moduleScope) { - ignoredScopes.add(info.moduleScope); - } - - // The super class expression in class scopes behaves weird - // We get ranges of all super class expressions to make - // renaming to work correctly - const superClassCache = new WeakMap(); - /** - * @param {Scope} scope scope - * @returns {TODO} result - */ - const getSuperClassExpressions = scope => { - const cacheEntry = superClassCache.get(scope); - if (cacheEntry !== undefined) return cacheEntry; - const superClassExpressions = []; - for (const childScope of scope.childScopes) { - if (childScope.type !== "class") continue; - const block = childScope.block; - if ( - (block.type === "ClassDeclaration" || - block.type === "ClassExpression") && - block.superClass - ) { - superClassExpressions.push({ - range: block.superClass.range, - variables: childScope.variables - }); - } - } - superClassCache.set(scope, superClassExpressions); - return superClassExpressions; - }; - - // add global symbols - if (info.globalScope) { - for (const reference of info.globalScope.through) { - const name = reference.identifier.name; - if (ConcatenationScope.isModuleReference(name)) { - const match = ConcatenationScope.matchModuleReference(name); - if (!match) continue; - const referencedInfo = modulesWithInfo[match.index]; - if (referencedInfo.type === "reference") - throw new Error("Module reference can't point to a reference"); - const binding = getFinalBinding( - moduleGraph, - referencedInfo, - match.ids, - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - false, - /** @type {BuildMeta} */ - (info.module.buildMeta).strictHarmonyModule, - true - ); - if (!binding.ids) continue; - const { usedNames, alreadyCheckedScopes } = - getUsedNamesInScopeInfo( - usedNamesInScopeInfo, - binding.info.module.identifier(), - "name" in binding ? binding.name : "" - ); - for (const expr of getSuperClassExpressions(reference.from)) { - if ( - expr.range[0] <= - /** @type {Range} */ (reference.identifier.range)[0] && - expr.range[1] >= - /** @type {Range} */ (reference.identifier.range)[1] - ) { - for (const variable of expr.variables) { - usedNames.add(variable.name); - } - } - } - addScopeSymbols( - reference.from, - usedNames, - alreadyCheckedScopes, - ignoredScopes - ); - } else { - allUsedNames.add(name); - } - } - } - } - } - - // generate names for symbols - for (const info of moduleToInfoMap.values()) { - const { usedNames: namespaceObjectUsedNames } = getUsedNamesInScopeInfo( - usedNamesInScopeInfo, - info.module.identifier(), - "" - ); - switch (info.type) { - case "concatenated": { - const variables = /** @type {Scope} */ (info.moduleScope).variables; - for (const variable of variables) { - const name = variable.name; - const { usedNames, alreadyCheckedScopes } = getUsedNamesInScopeInfo( - usedNamesInScopeInfo, - info.module.identifier(), - name - ); - if (allUsedNames.has(name) || usedNames.has(name)) { - const references = getAllReferences(variable); - for (const ref of references) { - addScopeSymbols( - ref.from, - usedNames, - alreadyCheckedScopes, - ignoredScopes - ); - } - const newName = findNewName( - name, - allUsedNames, - usedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(newName); - info.internalNames.set(name, newName); - topLevelDeclarations.add(newName); - const source = /** @type {ReplaceSource} */ (info.source); - const allIdentifiers = new Set( - references.map(r => r.identifier).concat(variable.identifiers) - ); - for (const identifier of allIdentifiers) { - const r = /** @type {Range} */ (identifier.range); - const path = getPathInAst( - /** @type {NonNullable} */ - (info.ast), - identifier - ); - if (path && path.length > 1) { - const maybeProperty = - path[1].type === "AssignmentPattern" && - path[1].left === path[0] - ? path[2] - : path[1]; - if ( - maybeProperty.type === "Property" && - maybeProperty.shorthand - ) { - source.insert(r[1], `: ${newName}`); - continue; - } - } - source.replace(r[0], r[1] - 1, newName); - } - } else { - allUsedNames.add(name); - info.internalNames.set(name, name); - topLevelDeclarations.add(name); - } - } - let namespaceObjectName; - if (info.namespaceExportSymbol) { - namespaceObjectName = info.internalNames.get( - info.namespaceExportSymbol - ); - } else { - namespaceObjectName = findNewName( - "namespaceObject", - allUsedNames, - namespaceObjectUsedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(namespaceObjectName); - } - info.namespaceObjectName = - /** @type {string} */ - (namespaceObjectName); - topLevelDeclarations.add(namespaceObjectName); - break; - } - case "external": { - const externalName = findNewName( - "", - allUsedNames, - namespaceObjectUsedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(externalName); - info.name = externalName; - topLevelDeclarations.add(externalName); - break; - } - } - const buildMeta = /** @type {BuildMeta} */ (info.module.buildMeta); - if (buildMeta.exportsType !== "namespace") { - const externalNameInterop = findNewName( - "namespaceObject", - allUsedNames, - namespaceObjectUsedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(externalNameInterop); - info.interopNamespaceObjectName = externalNameInterop; - topLevelDeclarations.add(externalNameInterop); - } - if ( - buildMeta.exportsType === "default" && - buildMeta.defaultObject !== "redirect" - ) { - const externalNameInterop = findNewName( - "namespaceObject2", - allUsedNames, - namespaceObjectUsedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(externalNameInterop); - info.interopNamespaceObject2Name = externalNameInterop; - topLevelDeclarations.add(externalNameInterop); - } - if (buildMeta.exportsType === "dynamic" || !buildMeta.exportsType) { - const externalNameInterop = findNewName( - "default", - allUsedNames, - namespaceObjectUsedNames, - info.module.readableIdentifier(requestShortener) - ); - allUsedNames.add(externalNameInterop); - info.interopDefaultAccessName = externalNameInterop; - topLevelDeclarations.add(externalNameInterop); - } - } - - // Find and replace references to modules - for (const info of moduleToInfoMap.values()) { - if (info.type === "concatenated") { - const globalScope = /** @type {Scope} */ (info.globalScope); - for (const reference of globalScope.through) { - const name = reference.identifier.name; - const match = ConcatenationScope.matchModuleReference(name); - if (match) { - const referencedInfo = modulesWithInfo[match.index]; - if (referencedInfo.type === "reference") - throw new Error("Module reference can't point to a reference"); - const finalName = getFinalName( - moduleGraph, - referencedInfo, - match.ids, - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - match.call, - !match.directImport, - /** @type {BuildMeta} */ - (info.module.buildMeta).strictHarmonyModule, - match.asiSafe - ); - const r = /** @type {Range} */ (reference.identifier.range); - const source = /** @type {ReplaceSource} */ (info.source); - // range is extended by 2 chars to cover the appended "._" - source.replace(r[0], r[1] + 1, finalName); - } - } - } - } - - // Map with all root exposed used exports - /** @type {Map} */ - const exportsMap = new Map(); - - // Set with all root exposed unused exports - /** @type {Set} */ - const unusedExports = new Set(); - - const rootInfo = /** @type {ConcatenatedModuleInfo} */ ( - moduleToInfoMap.get(this.rootModule) - ); - const strictHarmonyModule = - /** @type {BuildMeta} */ - (rootInfo.module.buildMeta).strictHarmonyModule; - const exportsInfo = moduleGraph.getExportsInfo(rootInfo.module); - /** @type {Record} */ - const exportsFinalName = {}; - for (const exportInfo of exportsInfo.orderedExports) { - const name = exportInfo.name; - if (exportInfo.provided === false) continue; - const used = exportInfo.getUsedName(undefined, runtime); - if (!used) { - unusedExports.add(name); - continue; - } - exportsMap.set(used, requestShortener => { - try { - const finalName = getFinalName( - moduleGraph, - rootInfo, - [name], - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - false, - false, - strictHarmonyModule, - true - ); - exportsFinalName[used] = finalName; - return `/* ${ - exportInfo.isReexport() ? "reexport" : "binding" - } */ ${finalName}`; - } catch (err) { - /** @type {Error} */ - (err).message += - `\nwhile generating the root export '${name}' (used name: '${used}')`; - throw err; - } - }); - } - - const result = new ConcatSource(); - - // add harmony compatibility flag (must be first because of possible circular dependencies) - let shouldAddHarmonyFlag = false; - if ( - moduleGraph.getExportsInfo(this).otherExportsInfo.getUsed(runtime) !== - UsageState.Unused - ) { - shouldAddHarmonyFlag = true; - } - - // define exports - if (exportsMap.size > 0) { - const { exportsDefinitions } = ConcatenatedModule.getCompilationHooks( - /** @type {Compilation} */ (this.compilation) - ); - - const definitions = []; - for (const [key, value] of exportsMap) { - definitions.push( - `\n ${propertyName(key)}: ${runtimeTemplate.returningFunction( - value(requestShortener) - )}` - ); - } - const shouldSkipRenderDefinitions = - exportsDefinitions.call(exportsFinalName); - - if (!shouldSkipRenderDefinitions) { - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - - if (shouldAddHarmonyFlag) { - result.add("// ESM COMPAT FLAG\n"); - result.add( - runtimeTemplate.defineEsModuleFlagStatement({ - exportsArgument: this.exportsArgument, - runtimeRequirements - }) - ); - } - - result.add("\n// EXPORTS\n"); - result.add( - `${RuntimeGlobals.definePropertyGetters}(${ - this.exportsArgument - }, {${definitions.join(",")}\n});\n` - ); - } else { - /** @type {BuildMeta} */ - (this.buildMeta).exportsFinalName = exportsFinalName; - } - } - - // list unused exports - if (unusedExports.size > 0) { - result.add( - `\n// UNUSED EXPORTS: ${joinIterableWithComma(unusedExports)}\n` - ); - } - - // generate namespace objects - const namespaceObjectSources = new Map(); - for (const info of neededNamespaceObjects) { - if (info.namespaceExportSymbol) continue; - const nsObj = []; - const exportsInfo = moduleGraph.getExportsInfo(info.module); - for (const exportInfo of exportsInfo.orderedExports) { - if (exportInfo.provided === false) continue; - const usedName = exportInfo.getUsedName(undefined, runtime); - if (usedName) { - const finalName = getFinalName( - moduleGraph, - info, - [exportInfo.name], - moduleToInfoMap, - runtime, - requestShortener, - runtimeTemplate, - neededNamespaceObjects, - false, - undefined, - /** @type {BuildMeta} */ - (info.module.buildMeta).strictHarmonyModule, - true - ); - nsObj.push( - `\n ${propertyName(usedName)}: ${runtimeTemplate.returningFunction( - finalName - )}` - ); - } - } - const name = info.namespaceObjectName; - const defineGetters = - nsObj.length > 0 - ? `${RuntimeGlobals.definePropertyGetters}(${name}, {${nsObj.join( - "," - )}\n});\n` - : ""; - if (nsObj.length > 0) - runtimeRequirements.add(RuntimeGlobals.definePropertyGetters); - namespaceObjectSources.set( - info, - ` -// NAMESPACE OBJECT: ${info.module.readableIdentifier(requestShortener)} -var ${name} = {}; -${RuntimeGlobals.makeNamespaceObject}(${name}); -${defineGetters}` - ); - runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); - } - - // define required namespace objects (must be before evaluation modules) - for (const info of modulesWithInfo) { - if (info.type === "concatenated") { - const source = namespaceObjectSources.get(info); - if (!source) continue; - result.add(source); - } - } - - const chunkInitFragments = []; - - // evaluate modules in order - for (const rawInfo of modulesWithInfo) { - let name; - let isConditional = false; - const info = rawInfo.type === "reference" ? rawInfo.target : rawInfo; - switch (info.type) { - case "concatenated": { - result.add( - `\n;// ${info.module.readableIdentifier(requestShortener)}\n` - ); - result.add(/** @type {ReplaceSource} */ (info.source)); - if (info.chunkInitFragments) { - for (const f of info.chunkInitFragments) chunkInitFragments.push(f); - } - if (info.runtimeRequirements) { - for (const r of info.runtimeRequirements) { - runtimeRequirements.add(r); - } - } - name = info.namespaceObjectName; - break; - } - case "external": { - result.add( - `\n// EXTERNAL MODULE: ${info.module.readableIdentifier( - requestShortener - )}\n` - ); - runtimeRequirements.add(RuntimeGlobals.require); - const { runtimeCondition } = - /** @type {ExternalModuleInfo | ReferenceToModuleInfo} */ (rawInfo); - const condition = runtimeTemplate.runtimeConditionExpression({ - chunkGraph, - runtimeCondition, - runtime, - runtimeRequirements - }); - if (condition !== "true") { - isConditional = true; - result.add(`if (${condition}) {\n`); - } - result.add( - `var ${info.name} = ${RuntimeGlobals.require}(${JSON.stringify( - chunkGraph.getModuleId(info.module) - )});` - ); - name = info.name; - break; - } - default: - // @ts-expect-error never is expected here - throw new Error(`Unsupported concatenation entry type ${info.type}`); - } - if (info.interopNamespaceObjectUsed) { - runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); - result.add( - `\nvar ${info.interopNamespaceObjectName} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name}, 2);` - ); - } - if (info.interopNamespaceObject2Used) { - runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); - result.add( - `\nvar ${info.interopNamespaceObject2Name} = /*#__PURE__*/${RuntimeGlobals.createFakeNamespaceObject}(${name});` - ); - } - if (info.interopDefaultAccessUsed) { - runtimeRequirements.add(RuntimeGlobals.compatGetDefaultExport); - result.add( - `\nvar ${info.interopDefaultAccessName} = /*#__PURE__*/${RuntimeGlobals.compatGetDefaultExport}(${name});` - ); - } - if (isConditional) { - result.add("\n}"); - } - } - - const data = new Map(); - if (chunkInitFragments.length > 0) - data.set("chunkInitFragments", chunkInitFragments); - data.set("topLevelDeclarations", topLevelDeclarations); - - /** @type {CodeGenerationResult} */ - const resultEntry = { - sources: new Map([["javascript", new CachedSource(result)]]), - data, - runtimeRequirements - }; - - return resultEntry; - } - - /** - * @param {Map} modulesMap modulesMap - * @param {ModuleInfo} info info - * @param {DependencyTemplates} dependencyTemplates dependencyTemplates - * @param {RuntimeTemplate} runtimeTemplate runtimeTemplate - * @param {ModuleGraph} moduleGraph moduleGraph - * @param {ChunkGraph} chunkGraph chunkGraph - * @param {RuntimeSpec} runtime runtime - * @param {CodeGenerationResults} codeGenerationResults codeGenerationResults - */ - _analyseModule( - modulesMap, - info, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime, - codeGenerationResults - ) { - if (info.type === "concatenated") { - const m = info.module; - try { - // Create a concatenation scope to track and capture information - const concatenationScope = new ConcatenationScope(modulesMap, info); - - // TODO cache codeGeneration results - const codeGenResult = m.codeGeneration({ - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - runtime, - concatenationScope, - codeGenerationResults, - sourceTypes: JS_TYPES - }); - const source = /** @type {Source} */ ( - codeGenResult.sources.get("javascript") - ); - const data = codeGenResult.data; - const chunkInitFragments = data && data.get("chunkInitFragments"); - const code = source.source().toString(); - let ast; - try { - ast = JavascriptParser._parse(code, { - sourceType: "module" - }); - } catch (_err) { - const err = /** @type {TODO} */ (_err); - if ( - err.loc && - typeof err.loc === "object" && - typeof err.loc.line === "number" - ) { - const lineNumber = err.loc.line; - const lines = code.split("\n"); - err.message += `\n| ${lines - .slice(Math.max(0, lineNumber - 3), lineNumber + 2) - .join("\n| ")}`; - } - throw err; - } - const scopeManager = eslintScope.analyze(ast, { - ecmaVersion: 6, - sourceType: "module", - optimistic: true, - ignoreEval: true, - impliedStrict: true - }); - const globalScope = /** @type {Scope} */ (scopeManager.acquire(ast)); - const moduleScope = globalScope.childScopes[0]; - const resultSource = new ReplaceSource(source); - info.runtimeRequirements = - /** @type {ReadOnlyRuntimeRequirements} */ - (codeGenResult.runtimeRequirements); - info.ast = ast; - info.internalSource = source; - info.source = resultSource; - info.chunkInitFragments = chunkInitFragments; - info.globalScope = globalScope; - info.moduleScope = moduleScope; - } catch (err) { - /** @type {Error} */ - (err).message += - `\nwhile analyzing module ${m.identifier()} for concatenation`; - throw err; - } - } - } - - /** - * @param {ModuleGraph} moduleGraph the module graph - * @param {RuntimeSpec} runtime the runtime - * @returns {[ModuleInfoOrReference[], Map]} module info items - */ - _getModulesWithInfo(moduleGraph, runtime) { - const orderedConcatenationList = this._createConcatenationList( - this.rootModule, - this._modules, - runtime, - moduleGraph - ); - /** @type {Map} */ - const map = new Map(); - const list = orderedConcatenationList.map((info, index) => { - let item = map.get(info.module); - if (item === undefined) { - switch (info.type) { - case "concatenated": - item = { - type: "concatenated", - module: info.module, - index, - ast: undefined, - internalSource: undefined, - runtimeRequirements: undefined, - source: undefined, - globalScope: undefined, - moduleScope: undefined, - internalNames: new Map(), - exportMap: undefined, - rawExportMap: undefined, - namespaceExportSymbol: undefined, - namespaceObjectName: undefined, - interopNamespaceObjectUsed: false, - interopNamespaceObjectName: undefined, - interopNamespaceObject2Used: false, - interopNamespaceObject2Name: undefined, - interopDefaultAccessUsed: false, - interopDefaultAccessName: undefined - }; - break; - case "external": - item = { - type: "external", - module: info.module, - runtimeCondition: info.runtimeCondition, - index, - name: undefined, - interopNamespaceObjectUsed: false, - interopNamespaceObjectName: undefined, - interopNamespaceObject2Used: false, - interopNamespaceObject2Name: undefined, - interopDefaultAccessUsed: false, - interopDefaultAccessName: undefined - }; - break; - default: - throw new Error( - `Unsupported concatenation entry type ${info.type}` - ); - } - map.set( - /** @type {ModuleInfo} */ (item).module, - /** @type {ModuleInfo} */ (item) - ); - return /** @type {ModuleInfo} */ (item); - } - /** @type {ReferenceToModuleInfo} */ - const ref = { - type: "reference", - runtimeCondition: info.runtimeCondition, - target: item - }; - return ref; - }); - return [list, map]; - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - const { chunkGraph, runtime } = context; - for (const info of this._createConcatenationList( - this.rootModule, - this._modules, - intersectRuntime(runtime, this._runtime), - chunkGraph.moduleGraph - )) { - switch (info.type) { - case "concatenated": - info.module.updateHash(hash, context); - break; - case "external": - hash.update(`${chunkGraph.getModuleId(info.module)}`); - // TODO runtimeCondition - break; - } - } - super.updateHash(hash, context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ConcatenatedModule} ConcatenatedModule - */ - static deserialize(context) { - const obj = new ConcatenatedModule({ - identifier: /** @type {EXPECTED_ANY} */ (undefined), - rootModule: /** @type {EXPECTED_ANY} */ (undefined), - modules: /** @type {EXPECTED_ANY} */ (undefined), - runtime: undefined, - compilation: /** @type {EXPECTED_ANY} */ (undefined) - }); - obj.deserialize(context); - return obj; - } -} - -makeSerializable(ConcatenatedModule, "webpack/lib/optimize/ConcatenatedModule"); - -module.exports = ConcatenatedModule; diff --git a/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js b/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js deleted file mode 100644 index 0bc8a384bb6..00000000000 --- a/webpack-lib/lib/optimize/EnsureChunkConditionsPlugin.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_BASIC } = require("../OptimizationStages"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compiler")} Compiler */ - -class EnsureChunkConditionsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "EnsureChunkConditionsPlugin", - compilation => { - /** - * @param {Iterable} chunks the chunks - */ - const handler = chunks => { - const chunkGraph = compilation.chunkGraph; - // These sets are hoisted here to save memory - // They are cleared at the end of every loop - /** @type {Set} */ - const sourceChunks = new Set(); - /** @type {Set} */ - const chunkGroups = new Set(); - for (const module of compilation.modules) { - if (!module.hasChunkCondition()) continue; - for (const chunk of chunkGraph.getModuleChunksIterable(module)) { - if (!module.chunkCondition(chunk, compilation)) { - sourceChunks.add(chunk); - for (const group of chunk.groupsIterable) { - chunkGroups.add(group); - } - } - } - if (sourceChunks.size === 0) continue; - /** @type {Set} */ - const targetChunks = new Set(); - chunkGroupLoop: for (const chunkGroup of chunkGroups) { - // Can module be placed in a chunk of this group? - for (const chunk of chunkGroup.chunks) { - if (module.chunkCondition(chunk, compilation)) { - targetChunks.add(chunk); - continue chunkGroupLoop; - } - } - // We reached the entrypoint: fail - if (chunkGroup.isInitial()) { - throw new Error( - `Cannot fulfil chunk condition of ${module.identifier()}` - ); - } - // Try placing in all parents - for (const group of chunkGroup.parentsIterable) { - chunkGroups.add(group); - } - } - for (const sourceChunk of sourceChunks) { - chunkGraph.disconnectChunkAndModule(sourceChunk, module); - } - for (const targetChunk of targetChunks) { - chunkGraph.connectChunkAndModule(targetChunk, module); - } - sourceChunks.clear(); - chunkGroups.clear(); - } - }; - compilation.hooks.optimizeChunks.tap( - { - name: "EnsureChunkConditionsPlugin", - stage: STAGE_BASIC - }, - handler - ); - } - ); - } -} -module.exports = EnsureChunkConditionsPlugin; diff --git a/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js b/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js deleted file mode 100644 index 2e4adb84e72..00000000000 --- a/webpack-lib/lib/optimize/FlagIncludedChunksPlugin.js +++ /dev/null @@ -1,130 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareIds } = require("../util/comparators"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -class FlagIncludedChunksPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("FlagIncludedChunksPlugin", compilation => { - compilation.hooks.optimizeChunkIds.tap( - "FlagIncludedChunksPlugin", - chunks => { - const chunkGraph = compilation.chunkGraph; - - // prepare two bit integers for each module - // 2^31 is the max number represented as SMI in v8 - // we want the bits distributed this way: - // the bit 2^31 is pretty rar and only one module should get it - // so it has a probability of 1 / modulesCount - // the first bit (2^0) is the easiest and every module could get it - // if it doesn't get a better bit - // from bit 2^n to 2^(n+1) there is a probability of p - // so 1 / modulesCount == p^31 - // <=> p = sqrt31(1 / modulesCount) - // so we use a modulo of 1 / sqrt31(1 / modulesCount) - /** @type {WeakMap} */ - const moduleBits = new WeakMap(); - const modulesCount = compilation.modules.size; - - // precalculate the modulo values for each bit - const modulo = 1 / (1 / modulesCount) ** (1 / 31); - const modulos = Array.from( - { length: 31 }, - (x, i) => (modulo ** i) | 0 - ); - - // iterate all modules to generate bit values - let i = 0; - for (const module of compilation.modules) { - let bit = 30; - while (i % modulos[bit] !== 0) { - bit--; - } - moduleBits.set(module, 1 << bit); - i++; - } - - // iterate all chunks to generate bitmaps - /** @type {WeakMap} */ - const chunkModulesHash = new WeakMap(); - for (const chunk of chunks) { - let hash = 0; - for (const module of chunkGraph.getChunkModulesIterable(chunk)) { - hash |= /** @type {number} */ (moduleBits.get(module)); - } - chunkModulesHash.set(chunk, hash); - } - - for (const chunkA of chunks) { - const chunkAHash = - /** @type {number} */ - (chunkModulesHash.get(chunkA)); - const chunkAModulesCount = - chunkGraph.getNumberOfChunkModules(chunkA); - if (chunkAModulesCount === 0) continue; - let bestModule; - for (const module of chunkGraph.getChunkModulesIterable(chunkA)) { - if ( - bestModule === undefined || - chunkGraph.getNumberOfModuleChunks(bestModule) > - chunkGraph.getNumberOfModuleChunks(module) - ) - bestModule = module; - } - loopB: for (const chunkB of chunkGraph.getModuleChunksIterable( - /** @type {Module} */ (bestModule) - )) { - // as we iterate the same iterables twice - // skip if we find ourselves - if (chunkA === chunkB) continue; - - const chunkBModulesCount = - chunkGraph.getNumberOfChunkModules(chunkB); - - // ids for empty chunks are not included - if (chunkBModulesCount === 0) continue; - - // instead of swapping A and B just bail - // as we loop twice the current A will be B and B then A - if (chunkAModulesCount > chunkBModulesCount) continue; - - // is chunkA in chunkB? - - // we do a cheap check for the hash value - const chunkBHash = - /** @type {number} */ - (chunkModulesHash.get(chunkB)); - if ((chunkBHash & chunkAHash) !== chunkAHash) continue; - - // compare all modules - for (const m of chunkGraph.getChunkModulesIterable(chunkA)) { - if (!chunkGraph.isModuleInChunk(m, chunkB)) continue loopB; - } - - /** @type {ChunkId[]} */ - (chunkB.ids).push(/** @type {ChunkId} */ (chunkA.id)); - // https://github.com/webpack/webpack/issues/18837 - /** @type {ChunkId[]} */ - (chunkB.ids).sort(compareIds); - } - } - } - ); - }); - } -} -module.exports = FlagIncludedChunksPlugin; diff --git a/webpack-lib/lib/optimize/InnerGraph.js b/webpack-lib/lib/optimize/InnerGraph.js deleted file mode 100644 index 099c5eb1847..00000000000 --- a/webpack-lib/lib/optimize/InnerGraph.js +++ /dev/null @@ -1,351 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); - -/** @typedef {import("estree").Node} AnyNode */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleGraphConnection").ConnectionState} ConnectionState */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** @typedef {Map | true | undefined>} InnerGraph */ -/** @typedef {function(boolean | Set | undefined): void} UsageCallback */ - -/** - * @typedef {object} StateObject - * @property {InnerGraph} innerGraph - * @property {TopLevelSymbol=} currentTopLevelSymbol - * @property {Map>} usageCallbackMap - */ - -/** @typedef {false|StateObject} State */ - -/** @type {WeakMap} */ -const parserStateMap = new WeakMap(); -const topLevelSymbolTag = Symbol("top level symbol"); - -/** - * @param {ParserState} parserState parser state - * @returns {State | undefined} state - */ -function getState(parserState) { - return parserStateMap.get(parserState); -} - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.bailout = parserState => { - parserStateMap.set(parserState, false); -}; - -/** - * @param {ParserState} parserState parser state - * @returns {void} - */ -module.exports.enable = parserState => { - const state = parserStateMap.get(parserState); - if (state === false) { - return; - } - parserStateMap.set(parserState, { - innerGraph: new Map(), - currentTopLevelSymbol: undefined, - usageCallbackMap: new Map() - }); -}; - -/** - * @param {ParserState} parserState parser state - * @returns {boolean} true, when enabled - */ -module.exports.isEnabled = parserState => { - const state = parserStateMap.get(parserState); - return Boolean(state); -}; - -/** - * @param {ParserState} state parser state - * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols - * @param {string | TopLevelSymbol | true} usage usage data - * @returns {void} - */ -module.exports.addUsage = (state, symbol, usage) => { - const innerGraphState = getState(state); - - if (innerGraphState) { - const { innerGraph } = innerGraphState; - const info = innerGraph.get(symbol); - if (usage === true) { - innerGraph.set(symbol, true); - } else if (info === undefined) { - innerGraph.set(symbol, new Set([usage])); - } else if (info !== true) { - info.add(usage); - } - } -}; - -/** - * @param {JavascriptParser} parser the parser - * @param {string} name name of variable - * @param {string | TopLevelSymbol | true} usage usage data - * @returns {void} - */ -module.exports.addVariableUsage = (parser, name, usage) => { - const symbol = - /** @type {TopLevelSymbol} */ ( - parser.getTagData(name, topLevelSymbolTag) - ) || module.exports.tagTopLevelSymbol(parser, name); - if (symbol) { - module.exports.addUsage(parser.state, symbol, usage); - } -}; - -/** - * @param {ParserState} state parser state - * @returns {void} - */ -module.exports.inferDependencyUsage = state => { - const innerGraphState = getState(state); - - if (!innerGraphState) { - return; - } - - const { innerGraph, usageCallbackMap } = innerGraphState; - const processed = new Map(); - // flatten graph to terminal nodes (string, undefined or true) - const nonTerminal = new Set(innerGraph.keys()); - while (nonTerminal.size > 0) { - for (const key of nonTerminal) { - /** @type {Set | true} */ - let newSet = new Set(); - let isTerminal = true; - const value = innerGraph.get(key); - let alreadyProcessed = processed.get(key); - if (alreadyProcessed === undefined) { - alreadyProcessed = new Set(); - processed.set(key, alreadyProcessed); - } - if (value !== true && value !== undefined) { - for (const item of value) { - alreadyProcessed.add(item); - } - for (const item of value) { - if (typeof item === "string") { - newSet.add(item); - } else { - const itemValue = innerGraph.get(item); - if (itemValue === true) { - newSet = true; - break; - } - if (itemValue !== undefined) { - for (const i of itemValue) { - if (i === key) continue; - if (alreadyProcessed.has(i)) continue; - newSet.add(i); - if (typeof i !== "string") { - isTerminal = false; - } - } - } - } - } - if (newSet === true) { - innerGraph.set(key, true); - } else if (newSet.size === 0) { - innerGraph.set(key, undefined); - } else { - innerGraph.set(key, newSet); - } - } - if (isTerminal) { - nonTerminal.delete(key); - - // For the global key, merge with all other keys - if (key === null) { - const globalValue = innerGraph.get(null); - if (globalValue) { - for (const [key, value] of innerGraph) { - if (key !== null && value !== true) { - if (globalValue === true) { - innerGraph.set(key, true); - } else { - const newSet = new Set(value); - for (const item of globalValue) { - newSet.add(item); - } - innerGraph.set(key, newSet); - } - } - } - } - } - } - } - } - - /** @type {Map>} */ - for (const [symbol, callbacks] of usageCallbackMap) { - const usage = /** @type {true | Set | undefined} */ ( - innerGraph.get(symbol) - ); - for (const callback of callbacks) { - callback(usage === undefined ? false : usage); - } - } -}; - -/** - * @param {ParserState} state parser state - * @param {UsageCallback} onUsageCallback on usage callback - */ -module.exports.onUsage = (state, onUsageCallback) => { - const innerGraphState = getState(state); - - if (innerGraphState) { - const { usageCallbackMap, currentTopLevelSymbol } = innerGraphState; - if (currentTopLevelSymbol) { - let callbacks = usageCallbackMap.get(currentTopLevelSymbol); - - if (callbacks === undefined) { - callbacks = new Set(); - usageCallbackMap.set(currentTopLevelSymbol, callbacks); - } - - callbacks.add(onUsageCallback); - } else { - onUsageCallback(true); - } - } else { - onUsageCallback(undefined); - } -}; - -/** - * @param {ParserState} state parser state - * @param {TopLevelSymbol | undefined} symbol the symbol - */ -module.exports.setTopLevelSymbol = (state, symbol) => { - const innerGraphState = getState(state); - - if (innerGraphState) { - innerGraphState.currentTopLevelSymbol = symbol; - } -}; - -/** - * @param {ParserState} state parser state - * @returns {TopLevelSymbol|void} usage data - */ -module.exports.getTopLevelSymbol = state => { - const innerGraphState = getState(state); - - if (innerGraphState) { - return innerGraphState.currentTopLevelSymbol; - } -}; - -/** - * @param {JavascriptParser} parser parser - * @param {string} name name of variable - * @returns {TopLevelSymbol | undefined} symbol - */ -module.exports.tagTopLevelSymbol = (parser, name) => { - const innerGraphState = getState(parser.state); - if (!innerGraphState) return; - - parser.defineVariable(name); - - const existingTag = /** @type {TopLevelSymbol} */ ( - parser.getTagData(name, topLevelSymbolTag) - ); - if (existingTag) { - return existingTag; - } - - const fn = new TopLevelSymbol(name); - parser.tagVariable(name, topLevelSymbolTag, fn); - return fn; -}; - -/** - * @param {Dependency} dependency the dependency - * @param {Set | boolean} usedByExports usedByExports info - * @param {ModuleGraph} moduleGraph moduleGraph - * @param {RuntimeSpec} runtime runtime - * @returns {boolean} false, when unused. Otherwise true - */ -module.exports.isDependencyUsedByExports = ( - dependency, - usedByExports, - moduleGraph, - runtime -) => { - if (usedByExports === false) return false; - if (usedByExports !== true && usedByExports !== undefined) { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(dependency)); - const exportsInfo = moduleGraph.getExportsInfo(selfModule); - let used = false; - for (const exportName of usedByExports) { - if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) - used = true; - } - if (!used) return false; - } - return true; -}; - -/** - * @param {Dependency} dependency the dependency - * @param {Set | boolean | undefined} usedByExports usedByExports info - * @param {ModuleGraph} moduleGraph moduleGraph - * @returns {null | false | function(ModuleGraphConnection, RuntimeSpec): ConnectionState} function to determine if the connection is active - */ -module.exports.getDependencyUsedByExportsCondition = ( - dependency, - usedByExports, - moduleGraph -) => { - if (usedByExports === false) return false; - if (usedByExports !== true && usedByExports !== undefined) { - const selfModule = - /** @type {Module} */ - (moduleGraph.getParentModule(dependency)); - const exportsInfo = moduleGraph.getExportsInfo(selfModule); - return (connections, runtime) => { - for (const exportName of usedByExports) { - if (exportsInfo.getUsed(exportName, runtime) !== UsageState.Unused) - return true; - } - return false; - }; - } - return null; -}; - -class TopLevelSymbol { - /** - * @param {string} name name of the variable - */ - constructor(name) { - this.name = name; - } -} - -module.exports.TopLevelSymbol = TopLevelSymbol; -module.exports.topLevelSymbolTag = topLevelSymbolTag; diff --git a/webpack-lib/lib/optimize/InnerGraphPlugin.js b/webpack-lib/lib/optimize/InnerGraphPlugin.js deleted file mode 100644 index 7900a4160da..00000000000 --- a/webpack-lib/lib/optimize/InnerGraphPlugin.js +++ /dev/null @@ -1,450 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM -} = require("../ModuleTypeConstants"); -const PureExpressionDependency = require("../dependencies/PureExpressionDependency"); -const InnerGraph = require("./InnerGraph"); - -/** @typedef {import("estree").ClassDeclaration} ClassDeclaration */ -/** @typedef {import("estree").ClassExpression} ClassExpression */ -/** @typedef {import("estree").Expression} Expression */ -/** @typedef {import("estree").Node} Node */ -/** @typedef {import("estree").VariableDeclarator} VariableDeclaratorNode */ -/** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("./InnerGraph").InnerGraph} InnerGraph */ -/** @typedef {import("./InnerGraph").TopLevelSymbol} TopLevelSymbol */ - -const { topLevelSymbolTag } = InnerGraph; - -const PLUGIN_NAME = "InnerGraphPlugin"; - -class InnerGraphPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const logger = compilation.getLogger("webpack.InnerGraphPlugin"); - - compilation.dependencyTemplates.set( - PureExpressionDependency, - new PureExpressionDependency.Template() - ); - - /** - * @param {JavascriptParser} parser the parser - * @param {JavascriptParserOptions} parserOptions options - * @returns {void} - */ - const handler = (parser, parserOptions) => { - /** - * @param {Expression} sup sup - */ - const onUsageSuper = sup => { - InnerGraph.onUsage(parser.state, usedByExports => { - switch (usedByExports) { - case undefined: - case true: - return; - default: { - const dep = new PureExpressionDependency( - /** @type {Range} */ - (sup.range) - ); - dep.loc = /** @type {DependencyLocation} */ (sup.loc); - dep.usedByExports = usedByExports; - parser.state.module.addDependency(dep); - break; - } - } - }); - }; - - parser.hooks.program.tap(PLUGIN_NAME, () => { - InnerGraph.enable(parser.state); - }); - - parser.hooks.finish.tap(PLUGIN_NAME, () => { - if (!InnerGraph.isEnabled(parser.state)) return; - - logger.time("infer dependency usage"); - InnerGraph.inferDependencyUsage(parser.state); - logger.timeAggregate("infer dependency usage"); - }); - - // During prewalking the following datastructures are filled with - // nodes that have a TopLevelSymbol assigned and - // variables are tagged with the assigned TopLevelSymbol - - // We differ 3 types of nodes: - // 1. full statements (export default, function declaration) - // 2. classes (class declaration, class expression) - // 3. variable declarators (const x = ...) - - /** @type {WeakMap} */ - const statementWithTopLevelSymbol = new WeakMap(); - /** @type {WeakMap} */ - const statementPurePart = new WeakMap(); - - /** @type {WeakMap} */ - const classWithTopLevelSymbol = new WeakMap(); - - /** @type {WeakMap} */ - const declWithTopLevelSymbol = new WeakMap(); - /** @type {WeakSet} */ - const pureDeclarators = new WeakSet(); - - // The following hooks are used during prewalking: - - parser.hooks.preStatement.tap(PLUGIN_NAME, statement => { - if (!InnerGraph.isEnabled(parser.state)) return; - - if ( - parser.scope.topLevelScope === true && - statement.type === "FunctionDeclaration" - ) { - const name = statement.id ? statement.id.name : "*default*"; - const fn = - /** @type {TopLevelSymbol} */ - (InnerGraph.tagTopLevelSymbol(parser, name)); - statementWithTopLevelSymbol.set(statement, fn); - return true; - } - }); - - parser.hooks.blockPreStatement.tap(PLUGIN_NAME, statement => { - if (!InnerGraph.isEnabled(parser.state)) return; - - if (parser.scope.topLevelScope === true) { - if ( - statement.type === "ClassDeclaration" && - parser.isPure( - statement, - /** @type {Range} */ (statement.range)[0] - ) - ) { - const name = statement.id ? statement.id.name : "*default*"; - const fn = /** @type {TopLevelSymbol} */ ( - InnerGraph.tagTopLevelSymbol(parser, name) - ); - classWithTopLevelSymbol.set(statement, fn); - return true; - } - if (statement.type === "ExportDefaultDeclaration") { - const name = "*default*"; - const fn = - /** @type {TopLevelSymbol} */ - (InnerGraph.tagTopLevelSymbol(parser, name)); - const decl = statement.declaration; - if ( - (decl.type === "ClassExpression" || - decl.type === "ClassDeclaration") && - parser.isPure( - /** @type {ClassExpression | ClassDeclaration} */ - (decl), - /** @type {Range} */ - (decl.range)[0] - ) - ) { - classWithTopLevelSymbol.set( - /** @type {ClassExpression | ClassDeclaration} */ - (decl), - fn - ); - } else if ( - parser.isPure( - /** @type {Expression} */ - (decl), - /** @type {Range} */ - (statement.range)[0] - ) - ) { - statementWithTopLevelSymbol.set(statement, fn); - if ( - !decl.type.endsWith("FunctionExpression") && - !decl.type.endsWith("Declaration") && - decl.type !== "Literal" - ) { - statementPurePart.set( - statement, - /** @type {Expression} */ - (decl) - ); - } - } - } - } - }); - - parser.hooks.preDeclarator.tap(PLUGIN_NAME, (decl, statement) => { - if (!InnerGraph.isEnabled(parser.state)) return; - if ( - parser.scope.topLevelScope === true && - decl.init && - decl.id.type === "Identifier" - ) { - const name = decl.id.name; - if ( - decl.init.type === "ClassExpression" && - parser.isPure( - decl.init, - /** @type {Range} */ (decl.id.range)[1] - ) - ) { - const fn = - /** @type {TopLevelSymbol} */ - (InnerGraph.tagTopLevelSymbol(parser, name)); - classWithTopLevelSymbol.set(decl.init, fn); - } else if ( - parser.isPure( - decl.init, - /** @type {Range} */ (decl.id.range)[1] - ) - ) { - const fn = - /** @type {TopLevelSymbol} */ - (InnerGraph.tagTopLevelSymbol(parser, name)); - declWithTopLevelSymbol.set(decl, fn); - if ( - !decl.init.type.endsWith("FunctionExpression") && - decl.init.type !== "Literal" - ) { - pureDeclarators.add(decl); - } - } - } - }); - - // During real walking we set the TopLevelSymbol state to the assigned - // TopLevelSymbol by using the fill datastructures. - - // In addition to tracking TopLevelSymbols, we sometimes need to - // add a PureExpressionDependency. This is needed to skip execution - // of pure expressions, even when they are not dropped due to - // minimizing. Otherwise symbols used there might not exist anymore - // as they are removed as unused by this optimization - - // When we find a reference to a TopLevelSymbol, we register a - // TopLevelSymbol dependency from TopLevelSymbol in state to the - // referenced TopLevelSymbol. This way we get a graph of all - // TopLevelSymbols. - - // The following hooks are called during walking: - - parser.hooks.statement.tap(PLUGIN_NAME, statement => { - if (!InnerGraph.isEnabled(parser.state)) return; - if (parser.scope.topLevelScope === true) { - InnerGraph.setTopLevelSymbol(parser.state, undefined); - - const fn = statementWithTopLevelSymbol.get(statement); - if (fn) { - InnerGraph.setTopLevelSymbol(parser.state, fn); - const purePart = statementPurePart.get(statement); - if (purePart) { - InnerGraph.onUsage(parser.state, usedByExports => { - switch (usedByExports) { - case undefined: - case true: - return; - default: { - const dep = new PureExpressionDependency( - /** @type {Range} */ (purePart.range) - ); - dep.loc = - /** @type {DependencyLocation} */ - (statement.loc); - dep.usedByExports = usedByExports; - parser.state.module.addDependency(dep); - break; - } - } - }); - } - } - } - }); - - parser.hooks.classExtendsExpression.tap( - PLUGIN_NAME, - (expr, statement) => { - if (!InnerGraph.isEnabled(parser.state)) return; - if (parser.scope.topLevelScope === true) { - const fn = classWithTopLevelSymbol.get(statement); - if ( - fn && - parser.isPure( - expr, - statement.id - ? /** @type {Range} */ (statement.id.range)[1] - : /** @type {Range} */ (statement.range)[0] - ) - ) { - InnerGraph.setTopLevelSymbol(parser.state, fn); - onUsageSuper(expr); - } - } - } - ); - - parser.hooks.classBodyElement.tap( - PLUGIN_NAME, - (element, classDefinition) => { - if (!InnerGraph.isEnabled(parser.state)) return; - if (parser.scope.topLevelScope === true) { - const fn = classWithTopLevelSymbol.get(classDefinition); - if (fn) { - InnerGraph.setTopLevelSymbol(parser.state, undefined); - } - } - } - ); - - parser.hooks.classBodyValue.tap( - PLUGIN_NAME, - (expression, element, classDefinition) => { - if (!InnerGraph.isEnabled(parser.state)) return; - if (parser.scope.topLevelScope === true) { - const fn = classWithTopLevelSymbol.get(classDefinition); - if (fn) { - if ( - !element.static || - parser.isPure( - expression, - element.key - ? /** @type {Range} */ (element.key.range)[1] - : /** @type {Range} */ (element.range)[0] - ) - ) { - InnerGraph.setTopLevelSymbol(parser.state, fn); - if (element.type !== "MethodDefinition" && element.static) { - InnerGraph.onUsage(parser.state, usedByExports => { - switch (usedByExports) { - case undefined: - case true: - return; - default: { - const dep = new PureExpressionDependency( - /** @type {Range} */ (expression.range) - ); - dep.loc = - /** @type {DependencyLocation} */ - (expression.loc); - dep.usedByExports = usedByExports; - parser.state.module.addDependency(dep); - break; - } - } - }); - } - } else { - InnerGraph.setTopLevelSymbol(parser.state, undefined); - } - } - } - } - ); - - parser.hooks.declarator.tap(PLUGIN_NAME, (decl, statement) => { - if (!InnerGraph.isEnabled(parser.state)) return; - const fn = declWithTopLevelSymbol.get(decl); - - if (fn) { - InnerGraph.setTopLevelSymbol(parser.state, fn); - if (pureDeclarators.has(decl)) { - if ( - /** @type {ClassExpression} */ - (decl.init).type === "ClassExpression" - ) { - if (decl.init.superClass) { - onUsageSuper(decl.init.superClass); - } - } else { - InnerGraph.onUsage(parser.state, usedByExports => { - switch (usedByExports) { - case undefined: - case true: - return; - default: { - const dep = new PureExpressionDependency( - /** @type {Range} */ ( - /** @type {ClassExpression} */ - (decl.init).range - ) - ); - dep.loc = /** @type {DependencyLocation} */ (decl.loc); - dep.usedByExports = usedByExports; - parser.state.module.addDependency(dep); - break; - } - } - }); - } - } - parser.walkExpression(decl.init); - InnerGraph.setTopLevelSymbol(parser.state, undefined); - return true; - } else if ( - decl.id.type === "Identifier" && - decl.init && - decl.init.type === "ClassExpression" && - classWithTopLevelSymbol.has(decl.init) - ) { - parser.walkExpression(decl.init); - InnerGraph.setTopLevelSymbol(parser.state, undefined); - return true; - } - }); - - parser.hooks.expression - .for(topLevelSymbolTag) - .tap(PLUGIN_NAME, () => { - const topLevelSymbol = /** @type {TopLevelSymbol} */ ( - parser.currentTagData - ); - const currentTopLevelSymbol = InnerGraph.getTopLevelSymbol( - parser.state - ); - InnerGraph.addUsage( - parser.state, - topLevelSymbol, - currentTopLevelSymbol || true - ); - }); - parser.hooks.assign.for(topLevelSymbolTag).tap(PLUGIN_NAME, expr => { - if (!InnerGraph.isEnabled(parser.state)) return; - if (expr.operator === "=") return true; - }); - }; - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_AUTO) - .tap(PLUGIN_NAME, handler); - normalModuleFactory.hooks.parser - .for(JAVASCRIPT_MODULE_TYPE_ESM) - .tap(PLUGIN_NAME, handler); - - compilation.hooks.finishModules.tap(PLUGIN_NAME, () => { - logger.timeAggregateEnd("infer dependency usage"); - }); - } - ); - } -} - -module.exports = InnerGraphPlugin; diff --git a/webpack-lib/lib/optimize/LimitChunkCountPlugin.js b/webpack-lib/lib/optimize/LimitChunkCountPlugin.js deleted file mode 100644 index 9b18c9b3b27..00000000000 --- a/webpack-lib/lib/optimize/LimitChunkCountPlugin.js +++ /dev/null @@ -1,277 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_ADVANCED } = require("../OptimizationStages"); -const LazyBucketSortedSet = require("../util/LazyBucketSortedSet"); -const { compareChunks } = require("../util/comparators"); -const createSchemaValidation = require("../util/create-schema-validation"); - -/** @typedef {import("../../declarations/plugins/optimize/LimitChunkCountPlugin").LimitChunkCountPluginOptions} LimitChunkCountPluginOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/optimize/LimitChunkCountPlugin.check.js"), - () => require("../../schemas/plugins/optimize/LimitChunkCountPlugin.json"), - { - name: "Limit Chunk Count Plugin", - baseDataPath: "options" - } -); - -/** - * @typedef {object} ChunkCombination - * @property {boolean} deleted this is set to true when combination was removed - * @property {number} sizeDiff - * @property {number} integratedSize - * @property {Chunk} a - * @property {Chunk} b - * @property {number} aIdx - * @property {number} bIdx - * @property {number} aSize - * @property {number} bSize - */ - -/** - * @template K, V - * @param {Map>} map map - * @param {K} key key - * @param {V} value value - */ -const addToSetMap = (map, key, value) => { - const set = map.get(key); - if (set === undefined) { - map.set(key, new Set([value])); - } else { - set.add(value); - } -}; - -class LimitChunkCountPlugin { - /** - * @param {LimitChunkCountPluginOptions=} options options object - */ - constructor(options) { - validate(options); - this.options = /** @type {LimitChunkCountPluginOptions} */ (options); - } - - /** - * @param {Compiler} compiler the webpack compiler - * @returns {void} - */ - apply(compiler) { - const options = this.options; - compiler.hooks.compilation.tap("LimitChunkCountPlugin", compilation => { - compilation.hooks.optimizeChunks.tap( - { - name: "LimitChunkCountPlugin", - stage: STAGE_ADVANCED - }, - chunks => { - const chunkGraph = compilation.chunkGraph; - const maxChunks = options.maxChunks; - if (!maxChunks) return; - if (maxChunks < 1) return; - if (compilation.chunks.size <= maxChunks) return; - - let remainingChunksToMerge = compilation.chunks.size - maxChunks; - - // order chunks in a deterministic way - const compareChunksWithGraph = compareChunks(chunkGraph); - const orderedChunks = Array.from(chunks).sort(compareChunksWithGraph); - - // create a lazy sorted data structure to keep all combinations - // this is large. Size = chunks * (chunks - 1) / 2 - // It uses a multi layer bucket sort plus normal sort in the last layer - // It's also lazy so only accessed buckets are sorted - const combinations = new LazyBucketSortedSet( - // Layer 1: ordered by largest size benefit - c => c.sizeDiff, - (a, b) => b - a, - // Layer 2: ordered by smallest combined size - /** - * @param {ChunkCombination} c combination - * @returns {number} integrated size - */ - c => c.integratedSize, - (a, b) => a - b, - // Layer 3: ordered by position difference in orderedChunk (-> to be deterministic) - /** - * @param {ChunkCombination} c combination - * @returns {number} position difference - */ - c => c.bIdx - c.aIdx, - (a, b) => a - b, - // Layer 4: ordered by position in orderedChunk (-> to be deterministic) - (a, b) => a.bIdx - b.bIdx - ); - - // we keep a mapping from chunk to all combinations - // but this mapping is not kept up-to-date with deletions - // so `deleted` flag need to be considered when iterating this - /** @type {Map>} */ - const combinationsByChunk = new Map(); - - for (const [bIdx, b] of orderedChunks.entries()) { - // create combination pairs with size and integrated size - for (let aIdx = 0; aIdx < bIdx; aIdx++) { - const a = orderedChunks[aIdx]; - // filter pairs that can not be integrated! - if (!chunkGraph.canChunksBeIntegrated(a, b)) continue; - - const integratedSize = chunkGraph.getIntegratedChunksSize( - a, - b, - options - ); - - const aSize = chunkGraph.getChunkSize(a, options); - const bSize = chunkGraph.getChunkSize(b, options); - const c = { - deleted: false, - sizeDiff: aSize + bSize - integratedSize, - integratedSize, - a, - b, - aIdx, - bIdx, - aSize, - bSize - }; - combinations.add(c); - addToSetMap(combinationsByChunk, a, c); - addToSetMap(combinationsByChunk, b, c); - } - } - - // list of modified chunks during this run - // combinations affected by this change are skipped to allow - // further optimizations - /** @type {Set} */ - const modifiedChunks = new Set(); - - let changed = false; - loop: while (true) { - const combination = combinations.popFirst(); - if (combination === undefined) break; - - combination.deleted = true; - const { a, b, integratedSize } = combination; - - // skip over pair when - // one of the already merged chunks is a parent of one of the chunks - if (modifiedChunks.size > 0) { - const queue = new Set(a.groupsIterable); - for (const group of b.groupsIterable) { - queue.add(group); - } - for (const group of queue) { - for (const mChunk of modifiedChunks) { - if (mChunk !== a && mChunk !== b && mChunk.isInGroup(group)) { - // This is a potential pair which needs recalculation - // We can't do that now, but it merge before following pairs - // so we leave space for it, and consider chunks as modified - // just for the worse case - remainingChunksToMerge--; - if (remainingChunksToMerge <= 0) break loop; - modifiedChunks.add(a); - modifiedChunks.add(b); - continue loop; - } - } - for (const parent of group.parentsIterable) { - queue.add(parent); - } - } - } - - // merge the chunks - if (chunkGraph.canChunksBeIntegrated(a, b)) { - chunkGraph.integrateChunks(a, b); - compilation.chunks.delete(b); - - // flag chunk a as modified as further optimization are possible for all children here - modifiedChunks.add(a); - - changed = true; - remainingChunksToMerge--; - if (remainingChunksToMerge <= 0) break; - - // Update all affected combinations - // delete all combination with the removed chunk - // we will use combinations with the kept chunk instead - for (const combination of /** @type {Set} */ ( - combinationsByChunk.get(a) - )) { - if (combination.deleted) continue; - combination.deleted = true; - combinations.delete(combination); - } - - // Update combinations with the kept chunk with new sizes - for (const combination of /** @type {Set} */ ( - combinationsByChunk.get(b) - )) { - if (combination.deleted) continue; - if (combination.a === b) { - if (!chunkGraph.canChunksBeIntegrated(a, combination.b)) { - combination.deleted = true; - combinations.delete(combination); - continue; - } - // Update size - const newIntegratedSize = chunkGraph.getIntegratedChunksSize( - a, - combination.b, - options - ); - const finishUpdate = combinations.startUpdate(combination); - combination.a = a; - combination.integratedSize = newIntegratedSize; - combination.aSize = integratedSize; - combination.sizeDiff = - combination.bSize + integratedSize - newIntegratedSize; - finishUpdate(); - } else if (combination.b === b) { - if (!chunkGraph.canChunksBeIntegrated(combination.a, a)) { - combination.deleted = true; - combinations.delete(combination); - continue; - } - // Update size - const newIntegratedSize = chunkGraph.getIntegratedChunksSize( - combination.a, - a, - options - ); - - const finishUpdate = combinations.startUpdate(combination); - combination.b = a; - combination.integratedSize = newIntegratedSize; - combination.bSize = integratedSize; - combination.sizeDiff = - integratedSize + combination.aSize - newIntegratedSize; - finishUpdate(); - } - } - combinationsByChunk.set( - a, - /** @type {Set} */ ( - combinationsByChunk.get(b) - ) - ); - combinationsByChunk.delete(b); - } - } - if (changed) return true; - } - ); - }); - } -} -module.exports = LimitChunkCountPlugin; diff --git a/webpack-lib/lib/optimize/MangleExportsPlugin.js b/webpack-lib/lib/optimize/MangleExportsPlugin.js deleted file mode 100644 index b1dbff26989..00000000000 --- a/webpack-lib/lib/optimize/MangleExportsPlugin.js +++ /dev/null @@ -1,182 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { UsageState } = require("../ExportsInfo"); -const { - numberToIdentifier, - NUMBER_OF_IDENTIFIER_START_CHARS, - NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS -} = require("../Template"); -const { assignDeterministicIds } = require("../ids/IdHelpers"); -const { compareSelect, compareStringsNumeric } = require("../util/comparators"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../ExportsInfo")} ExportsInfo */ -/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */ - -/** - * @param {ExportsInfo} exportsInfo exports info - * @returns {boolean} mangle is possible - */ -const canMangle = exportsInfo => { - if (exportsInfo.otherExportsInfo.getUsed(undefined) !== UsageState.Unused) - return false; - let hasSomethingToMangle = false; - for (const exportInfo of exportsInfo.exports) { - if (exportInfo.canMangle === true) { - hasSomethingToMangle = true; - } - } - return hasSomethingToMangle; -}; - -// Sort by name -const comparator = compareSelect(e => e.name, compareStringsNumeric); -/** - * @param {boolean} deterministic use deterministic names - * @param {ExportsInfo} exportsInfo exports info - * @param {boolean | undefined} isNamespace is namespace object - * @returns {void} - */ -const mangleExportsInfo = (deterministic, exportsInfo, isNamespace) => { - if (!canMangle(exportsInfo)) return; - const usedNames = new Set(); - /** @type {ExportInfo[]} */ - const mangleableExports = []; - - // Avoid to renamed exports that are not provided when - // 1. it's not a namespace export: non-provided exports can be found in prototype chain - // 2. there are other provided exports and deterministic mode is chosen: - // non-provided exports would break the determinism - let avoidMangleNonProvided = !isNamespace; - if (!avoidMangleNonProvided && deterministic) { - for (const exportInfo of exportsInfo.ownedExports) { - if (exportInfo.provided !== false) { - avoidMangleNonProvided = true; - break; - } - } - } - for (const exportInfo of exportsInfo.ownedExports) { - const name = exportInfo.name; - if (!exportInfo.hasUsedName()) { - if ( - // Can the export be mangled? - exportInfo.canMangle !== true || - // Never rename 1 char exports - (name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) || - // Don't rename 2 char exports in deterministic mode - (deterministic && - name.length === 2 && - /^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) || - // Don't rename exports that are not provided - (avoidMangleNonProvided && exportInfo.provided !== true) - ) { - exportInfo.setUsedName(name); - usedNames.add(name); - } else { - mangleableExports.push(exportInfo); - } - } - if (exportInfo.exportsInfoOwned) { - const used = exportInfo.getUsed(undefined); - if ( - used === UsageState.OnlyPropertiesUsed || - used === UsageState.Unused - ) { - mangleExportsInfo( - deterministic, - /** @type {ExportsInfo} */ (exportInfo.exportsInfo), - false - ); - } - } - } - if (deterministic) { - assignDeterministicIds( - mangleableExports, - e => e.name, - comparator, - (e, id) => { - const name = numberToIdentifier(id); - const size = usedNames.size; - usedNames.add(name); - if (size === usedNames.size) return false; - e.setUsedName(name); - return true; - }, - [ - NUMBER_OF_IDENTIFIER_START_CHARS, - NUMBER_OF_IDENTIFIER_START_CHARS * - NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS - ], - NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS, - usedNames.size - ); - } else { - const usedExports = []; - const unusedExports = []; - for (const exportInfo of mangleableExports) { - if (exportInfo.getUsed(undefined) === UsageState.Unused) { - unusedExports.push(exportInfo); - } else { - usedExports.push(exportInfo); - } - } - usedExports.sort(comparator); - unusedExports.sort(comparator); - let i = 0; - for (const list of [usedExports, unusedExports]) { - for (const exportInfo of list) { - let name; - do { - name = numberToIdentifier(i++); - } while (usedNames.has(name)); - exportInfo.setUsedName(name); - } - } - } -}; - -class MangleExportsPlugin { - /** - * @param {boolean} deterministic use deterministic names - */ - constructor(deterministic) { - this._deterministic = deterministic; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _deterministic: deterministic } = this; - compiler.hooks.compilation.tap("MangleExportsPlugin", compilation => { - const moduleGraph = compilation.moduleGraph; - compilation.hooks.optimizeCodeGeneration.tap( - "MangleExportsPlugin", - modules => { - if (compilation.moduleMemCaches) { - throw new Error( - "optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect" - ); - } - for (const module of modules) { - const isNamespace = - module.buildMeta && module.buildMeta.exportsType === "namespace"; - const exportsInfo = moduleGraph.getExportsInfo(module); - mangleExportsInfo(deterministic, exportsInfo, isNamespace); - } - } - ); - }); - } -} - -module.exports = MangleExportsPlugin; diff --git a/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js b/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js deleted file mode 100644 index 08db56823a2..00000000000 --- a/webpack-lib/lib/optimize/MergeDuplicateChunksPlugin.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_BASIC } = require("../OptimizationStages"); -const createSchemaValidation = require("../util/create-schema-validation"); -const { runtimeEqual } = require("../util/runtime"); - -/** @typedef {import("../../declarations/plugins/optimize/MergeDuplicateChunksPlugin").MergeDuplicateChunksPluginOptions} MergeDuplicateChunksPluginOptions */ -/** @typedef {import("../Compiler")} Compiler */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.check.js"), - () => - require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.json"), - { - name: "Merge Duplicate Chunks Plugin", - baseDataPath: "options" - } -); - -class MergeDuplicateChunksPlugin { - /** - * @param {MergeDuplicateChunksPluginOptions} options options object - */ - constructor(options = { stage: STAGE_BASIC }) { - validate(options); - this.options = options; - } - - /** - * @param {Compiler} compiler the compiler - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "MergeDuplicateChunksPlugin", - compilation => { - compilation.hooks.optimizeChunks.tap( - { - name: "MergeDuplicateChunksPlugin", - stage: this.options.stage - }, - chunks => { - const { chunkGraph, moduleGraph } = compilation; - - // remember already tested chunks for performance - const notDuplicates = new Set(); - - // for each chunk - for (const chunk of chunks) { - // track a Set of all chunk that could be duplicates - let possibleDuplicates; - for (const module of chunkGraph.getChunkModulesIterable(chunk)) { - if (possibleDuplicates === undefined) { - // when possibleDuplicates is not yet set, - // create a new Set from chunks of the current module - // including only chunks with the same number of modules - for (const dup of chunkGraph.getModuleChunksIterable( - module - )) { - if ( - dup !== chunk && - chunkGraph.getNumberOfChunkModules(chunk) === - chunkGraph.getNumberOfChunkModules(dup) && - !notDuplicates.has(dup) - ) { - // delay allocating the new Set until here, reduce memory pressure - if (possibleDuplicates === undefined) { - possibleDuplicates = new Set(); - } - possibleDuplicates.add(dup); - } - } - // when no chunk is possible we can break here - if (possibleDuplicates === undefined) break; - } else { - // validate existing possible duplicates - for (const dup of possibleDuplicates) { - // remove possible duplicate when module is not contained - if (!chunkGraph.isModuleInChunk(module, dup)) { - possibleDuplicates.delete(dup); - } - } - // when all chunks has been removed we can break here - if (possibleDuplicates.size === 0) break; - } - } - - // when we found duplicates - if ( - possibleDuplicates !== undefined && - possibleDuplicates.size > 0 - ) { - outer: for (const otherChunk of possibleDuplicates) { - if (otherChunk.hasRuntime() !== chunk.hasRuntime()) continue; - if (chunkGraph.getNumberOfEntryModules(chunk) > 0) continue; - if (chunkGraph.getNumberOfEntryModules(otherChunk) > 0) - continue; - if (!runtimeEqual(chunk.runtime, otherChunk.runtime)) { - for (const module of chunkGraph.getChunkModulesIterable( - chunk - )) { - const exportsInfo = moduleGraph.getExportsInfo(module); - if ( - !exportsInfo.isEquallyUsed( - chunk.runtime, - otherChunk.runtime - ) - ) { - continue outer; - } - } - } - // merge them - if (chunkGraph.canChunksBeIntegrated(chunk, otherChunk)) { - chunkGraph.integrateChunks(chunk, otherChunk); - compilation.chunks.delete(otherChunk); - } - } - } - - // don't check already processed chunks twice - notDuplicates.add(chunk); - } - } - ); - } - ); - } -} -module.exports = MergeDuplicateChunksPlugin; diff --git a/webpack-lib/lib/optimize/MinChunkSizePlugin.js b/webpack-lib/lib/optimize/MinChunkSizePlugin.js deleted file mode 100644 index b51164c27d9..00000000000 --- a/webpack-lib/lib/optimize/MinChunkSizePlugin.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_ADVANCED } = require("../OptimizationStages"); -const createSchemaValidation = require("../util/create-schema-validation"); - -/** @typedef {import("../../declarations/plugins/optimize/MinChunkSizePlugin").MinChunkSizePluginOptions} MinChunkSizePluginOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/optimize/MinChunkSizePlugin.check.js"), - () => require("../../schemas/plugins/optimize/MinChunkSizePlugin.json"), - { - name: "Min Chunk Size Plugin", - baseDataPath: "options" - } -); - -class MinChunkSizePlugin { - /** - * @param {MinChunkSizePluginOptions} options options object - */ - constructor(options) { - validate(options); - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const options = this.options; - const minChunkSize = options.minChunkSize; - compiler.hooks.compilation.tap("MinChunkSizePlugin", compilation => { - compilation.hooks.optimizeChunks.tap( - { - name: "MinChunkSizePlugin", - stage: STAGE_ADVANCED - }, - chunks => { - const chunkGraph = compilation.chunkGraph; - const equalOptions = { - chunkOverhead: 1, - entryChunkMultiplicator: 1 - }; - - const chunkSizesMap = new Map(); - /** @type {[Chunk, Chunk][]} */ - const combinations = []; - /** @type {Chunk[]} */ - const smallChunks = []; - const visitedChunks = []; - for (const a of chunks) { - // check if one of the chunks sizes is smaller than the minChunkSize - // and filter pairs that can NOT be integrated! - if (chunkGraph.getChunkSize(a, equalOptions) < minChunkSize) { - smallChunks.push(a); - for (const b of visitedChunks) { - if (chunkGraph.canChunksBeIntegrated(b, a)) - combinations.push([b, a]); - } - } else { - for (const b of smallChunks) { - if (chunkGraph.canChunksBeIntegrated(b, a)) - combinations.push([b, a]); - } - } - chunkSizesMap.set(a, chunkGraph.getChunkSize(a, options)); - visitedChunks.push(a); - } - - const sortedSizeFilteredExtendedPairCombinations = combinations - .map(pair => { - // extend combination pairs with size and integrated size - const a = chunkSizesMap.get(pair[0]); - const b = chunkSizesMap.get(pair[1]); - const ab = chunkGraph.getIntegratedChunksSize( - pair[0], - pair[1], - options - ); - /** @type {[number, number, Chunk, Chunk]} */ - const extendedPair = [a + b - ab, ab, pair[0], pair[1]]; - return extendedPair; - }) - .sort((a, b) => { - // sadly javascript does an in place sort here - // sort by size - const diff = b[0] - a[0]; - if (diff !== 0) return diff; - return a[1] - b[1]; - }); - - if (sortedSizeFilteredExtendedPairCombinations.length === 0) return; - - const pair = sortedSizeFilteredExtendedPairCombinations[0]; - - chunkGraph.integrateChunks(pair[2], pair[3]); - compilation.chunks.delete(pair[3]); - return true; - } - ); - }); - } -} -module.exports = MinChunkSizePlugin; diff --git a/webpack-lib/lib/optimize/MinMaxSizeWarning.js b/webpack-lib/lib/optimize/MinMaxSizeWarning.js deleted file mode 100644 index 2cc845eb9f0..00000000000 --- a/webpack-lib/lib/optimize/MinMaxSizeWarning.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const SizeFormatHelpers = require("../SizeFormatHelpers"); -const WebpackError = require("../WebpackError"); - -class MinMaxSizeWarning extends WebpackError { - /** - * @param {string[] | undefined} keys keys - * @param {number} minSize minimum size - * @param {number} maxSize maximum size - */ - constructor(keys, minSize, maxSize) { - let keysMessage = "Fallback cache group"; - if (keys) { - keysMessage = - keys.length > 1 - ? `Cache groups ${keys.sort().join(", ")}` - : `Cache group ${keys[0]}`; - } - super( - "SplitChunksPlugin\n" + - `${keysMessage}\n` + - `Configured minSize (${SizeFormatHelpers.formatSize(minSize)}) is ` + - `bigger than maxSize (${SizeFormatHelpers.formatSize(maxSize)}).\n` + - "This seem to be a invalid optimization.splitChunks configuration." - ); - } -} - -module.exports = MinMaxSizeWarning; diff --git a/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js b/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js deleted file mode 100644 index 1dc33af9dd6..00000000000 --- a/webpack-lib/lib/optimize/ModuleConcatenationPlugin.js +++ /dev/null @@ -1,932 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const asyncLib = require("neo-async"); -const ChunkGraph = require("../ChunkGraph"); -const ModuleGraph = require("../ModuleGraph"); -const { STAGE_DEFAULT } = require("../OptimizationStages"); -const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const { - intersectRuntime, - mergeRuntimeOwned, - filterRuntime, - runtimeToString, - mergeRuntime -} = require("../util/runtime"); -const ConcatenatedModule = require("./ConcatenatedModule"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -/** - * @typedef {object} Statistics - * @property {number} cached - * @property {number} alreadyInConfig - * @property {number} invalidModule - * @property {number} incorrectChunks - * @property {number} incorrectDependency - * @property {number} incorrectModuleDependency - * @property {number} incorrectChunksOfImporter - * @property {number} incorrectRuntimeCondition - * @property {number} importerFailed - * @property {number} added - */ - -/** - * @param {string} msg message - * @returns {string} formatted message - */ -const formatBailoutReason = msg => `ModuleConcatenation bailout: ${msg}`; - -class ModuleConcatenationPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { _backCompat: backCompat } = compiler; - compiler.hooks.compilation.tap("ModuleConcatenationPlugin", compilation => { - if (compilation.moduleMemCaches) { - throw new Error( - "optimization.concatenateModules can't be used with cacheUnaffected as module concatenation is a global effect" - ); - } - const moduleGraph = compilation.moduleGraph; - /** @type {Map string)>} */ - const bailoutReasonMap = new Map(); - - /** - * @param {Module} module the module - * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason - */ - const setBailoutReason = (module, reason) => { - setInnerBailoutReason(module, reason); - moduleGraph - .getOptimizationBailout(module) - .push( - typeof reason === "function" - ? rs => formatBailoutReason(reason(rs)) - : formatBailoutReason(reason) - ); - }; - - /** - * @param {Module} module the module - * @param {string | ((requestShortener: RequestShortener) => string)} reason the reason - */ - const setInnerBailoutReason = (module, reason) => { - bailoutReasonMap.set(module, reason); - }; - - /** - * @param {Module} module the module - * @param {RequestShortener} requestShortener the request shortener - * @returns {string | ((requestShortener: RequestShortener) => string) | undefined} the reason - */ - const getInnerBailoutReason = (module, requestShortener) => { - const reason = bailoutReasonMap.get(module); - if (typeof reason === "function") return reason(requestShortener); - return reason; - }; - - /** - * @param {Module} module the module - * @param {Module | function(RequestShortener): string} problem the problem - * @returns {(requestShortener: RequestShortener) => string} the reason - */ - const formatBailoutWarning = (module, problem) => requestShortener => { - if (typeof problem === "function") { - return formatBailoutReason( - `Cannot concat with ${module.readableIdentifier( - requestShortener - )}: ${problem(requestShortener)}` - ); - } - const reason = getInnerBailoutReason(module, requestShortener); - const reasonWithPrefix = reason ? `: ${reason}` : ""; - if (module === problem) { - return formatBailoutReason( - `Cannot concat with ${module.readableIdentifier( - requestShortener - )}${reasonWithPrefix}` - ); - } - return formatBailoutReason( - `Cannot concat with ${module.readableIdentifier( - requestShortener - )} because of ${problem.readableIdentifier( - requestShortener - )}${reasonWithPrefix}` - ); - }; - - compilation.hooks.optimizeChunkModules.tapAsync( - { - name: "ModuleConcatenationPlugin", - stage: STAGE_DEFAULT - }, - (allChunks, modules, callback) => { - const logger = compilation.getLogger( - "webpack.ModuleConcatenationPlugin" - ); - const { chunkGraph, moduleGraph } = compilation; - const relevantModules = []; - const possibleInners = new Set(); - const context = { - chunkGraph, - moduleGraph - }; - logger.time("select relevant modules"); - for (const module of modules) { - let canBeRoot = true; - let canBeInner = true; - - const bailoutReason = module.getConcatenationBailoutReason(context); - if (bailoutReason) { - setBailoutReason(module, bailoutReason); - continue; - } - - // Must not be an async module - if (moduleGraph.isAsync(module)) { - setBailoutReason(module, "Module is async"); - continue; - } - - // Must be in strict mode - if (!(/** @type {BuildInfo} */ (module.buildInfo).strict)) { - setBailoutReason(module, "Module is not in strict mode"); - continue; - } - - // Module must be in any chunk (we don't want to do useless work) - if (chunkGraph.getNumberOfModuleChunks(module) === 0) { - setBailoutReason(module, "Module is not in any chunk"); - continue; - } - - // Exports must be known (and not dynamic) - const exportsInfo = moduleGraph.getExportsInfo(module); - const relevantExports = exportsInfo.getRelevantExports(undefined); - const unknownReexports = relevantExports.filter( - exportInfo => - exportInfo.isReexport() && !exportInfo.getTarget(moduleGraph) - ); - if (unknownReexports.length > 0) { - setBailoutReason( - module, - `Reexports in this module do not have a static target (${Array.from( - unknownReexports, - exportInfo => - `${ - exportInfo.name || "other exports" - }: ${exportInfo.getUsedInfo()}` - ).join(", ")})` - ); - continue; - } - - // Root modules must have a static list of exports - const unknownProvidedExports = relevantExports.filter( - exportInfo => exportInfo.provided !== true - ); - if (unknownProvidedExports.length > 0) { - setBailoutReason( - module, - `List of module exports is dynamic (${Array.from( - unknownProvidedExports, - exportInfo => - `${ - exportInfo.name || "other exports" - }: ${exportInfo.getProvidedInfo()} and ${exportInfo.getUsedInfo()}` - ).join(", ")})` - ); - canBeRoot = false; - } - - // Module must not be an entry point - if (chunkGraph.isEntryModule(module)) { - setInnerBailoutReason(module, "Module is an entry point"); - canBeInner = false; - } - - if (canBeRoot) relevantModules.push(module); - if (canBeInner) possibleInners.add(module); - } - logger.timeEnd("select relevant modules"); - logger.debug( - `${relevantModules.length} potential root modules, ${possibleInners.size} potential inner modules` - ); - // sort by depth - // modules with lower depth are more likely suited as roots - // this improves performance, because modules already selected as inner are skipped - logger.time("sort relevant modules"); - relevantModules.sort( - (a, b) => - /** @type {number} */ (moduleGraph.getDepth(a)) - - /** @type {number} */ (moduleGraph.getDepth(b)) - ); - logger.timeEnd("sort relevant modules"); - - /** @type {Statistics} */ - const stats = { - cached: 0, - alreadyInConfig: 0, - invalidModule: 0, - incorrectChunks: 0, - incorrectDependency: 0, - incorrectModuleDependency: 0, - incorrectChunksOfImporter: 0, - incorrectRuntimeCondition: 0, - importerFailed: 0, - added: 0 - }; - let statsCandidates = 0; - let statsSizeSum = 0; - let statsEmptyConfigurations = 0; - - logger.time("find modules to concatenate"); - const concatConfigurations = []; - const usedAsInner = new Set(); - for (const currentRoot of relevantModules) { - // when used by another configuration as inner: - // the other configuration is better and we can skip this one - // TODO reconsider that when it's only used in a different runtime - if (usedAsInner.has(currentRoot)) continue; - - let chunkRuntime; - for (const r of chunkGraph.getModuleRuntimes(currentRoot)) { - chunkRuntime = mergeRuntimeOwned(chunkRuntime, r); - } - const exportsInfo = moduleGraph.getExportsInfo(currentRoot); - const filteredRuntime = filterRuntime(chunkRuntime, r => - exportsInfo.isModuleUsed(r) - ); - const activeRuntime = - filteredRuntime === true - ? chunkRuntime - : filteredRuntime === false - ? undefined - : filteredRuntime; - - // create a configuration with the root - const currentConfiguration = new ConcatConfiguration( - currentRoot, - activeRuntime - ); - - // cache failures to add modules - const failureCache = new Map(); - - // potential optional import candidates - /** @type {Set} */ - const candidates = new Set(); - - // try to add all imports - for (const imp of this._getImports( - compilation, - currentRoot, - activeRuntime - )) { - candidates.add(imp); - } - - for (const imp of candidates) { - const impCandidates = new Set(); - const problem = this._tryToAdd( - compilation, - currentConfiguration, - imp, - chunkRuntime, - activeRuntime, - possibleInners, - impCandidates, - failureCache, - chunkGraph, - true, - stats - ); - if (problem) { - failureCache.set(imp, problem); - currentConfiguration.addWarning(imp, problem); - } else { - for (const c of impCandidates) { - candidates.add(c); - } - } - } - statsCandidates += candidates.size; - if (!currentConfiguration.isEmpty()) { - const modules = currentConfiguration.getModules(); - statsSizeSum += modules.size; - concatConfigurations.push(currentConfiguration); - for (const module of modules) { - if (module !== currentConfiguration.rootModule) { - usedAsInner.add(module); - } - } - } else { - statsEmptyConfigurations++; - const optimizationBailouts = - moduleGraph.getOptimizationBailout(currentRoot); - for (const warning of currentConfiguration.getWarningsSorted()) { - optimizationBailouts.push( - formatBailoutWarning(warning[0], warning[1]) - ); - } - } - } - logger.timeEnd("find modules to concatenate"); - logger.debug( - `${ - concatConfigurations.length - } successful concat configurations (avg size: ${ - statsSizeSum / concatConfigurations.length - }), ${statsEmptyConfigurations} bailed out completely` - ); - logger.debug( - `${statsCandidates} candidates were considered for adding (${stats.cached} cached failure, ${stats.alreadyInConfig} already in config, ${stats.invalidModule} invalid module, ${stats.incorrectChunks} incorrect chunks, ${stats.incorrectDependency} incorrect dependency, ${stats.incorrectChunksOfImporter} incorrect chunks of importer, ${stats.incorrectModuleDependency} incorrect module dependency, ${stats.incorrectRuntimeCondition} incorrect runtime condition, ${stats.importerFailed} importer failed, ${stats.added} added)` - ); - // HACK: Sort configurations by length and start with the longest one - // to get the biggest groups possible. Used modules are marked with usedModules - // TODO: Allow to reuse existing configuration while trying to add dependencies. - // This would improve performance. O(n^2) -> O(n) - logger.time("sort concat configurations"); - concatConfigurations.sort((a, b) => b.modules.size - a.modules.size); - logger.timeEnd("sort concat configurations"); - const usedModules = new Set(); - - logger.time("create concatenated modules"); - asyncLib.each( - concatConfigurations, - (concatConfiguration, callback) => { - const rootModule = concatConfiguration.rootModule; - - // Avoid overlapping configurations - // TODO: remove this when todo above is fixed - if (usedModules.has(rootModule)) return callback(); - const modules = concatConfiguration.getModules(); - for (const m of modules) { - usedModules.add(m); - } - - // Create a new ConcatenatedModule - ConcatenatedModule.getCompilationHooks(compilation); - const newModule = ConcatenatedModule.create( - rootModule, - modules, - concatConfiguration.runtime, - compilation, - compiler.root, - compilation.outputOptions.hashFunction - ); - - const build = () => { - newModule.build( - compiler.options, - compilation, - /** @type {TODO} */ - (null), - /** @type {TODO} */ - (null), - err => { - if (err) { - if (!err.module) { - err.module = newModule; - } - return callback(err); - } - integrate(); - } - ); - }; - - const integrate = () => { - if (backCompat) { - ChunkGraph.setChunkGraphForModule(newModule, chunkGraph); - ModuleGraph.setModuleGraphForModule(newModule, moduleGraph); - } - - for (const warning of concatConfiguration.getWarningsSorted()) { - moduleGraph - .getOptimizationBailout(newModule) - .push(formatBailoutWarning(warning[0], warning[1])); - } - moduleGraph.cloneModuleAttributes(rootModule, newModule); - for (const m of modules) { - // add to builtModules when one of the included modules was built - if (compilation.builtModules.has(m)) { - compilation.builtModules.add(newModule); - } - if (m !== rootModule) { - // attach external references to the concatenated module too - moduleGraph.copyOutgoingModuleConnections( - m, - newModule, - c => - c.originModule === m && - !( - c.dependency instanceof HarmonyImportDependency && - modules.has(c.module) - ) - ); - // remove module from chunk - for (const chunk of chunkGraph.getModuleChunksIterable( - rootModule - )) { - const sourceTypes = chunkGraph.getChunkModuleSourceTypes( - chunk, - m - ); - if (sourceTypes.size === 1) { - chunkGraph.disconnectChunkAndModule(chunk, m); - } else { - const newSourceTypes = new Set(sourceTypes); - newSourceTypes.delete("javascript"); - chunkGraph.setChunkModuleSourceTypes( - chunk, - m, - newSourceTypes - ); - } - } - } - } - compilation.modules.delete(rootModule); - ChunkGraph.clearChunkGraphForModule(rootModule); - ModuleGraph.clearModuleGraphForModule(rootModule); - - // remove module from chunk - chunkGraph.replaceModule(rootModule, newModule); - // replace module references with the concatenated module - moduleGraph.moveModuleConnections(rootModule, newModule, c => { - const otherModule = - c.module === rootModule ? c.originModule : c.module; - const innerConnection = - c.dependency instanceof HarmonyImportDependency && - modules.has(/** @type {Module} */ (otherModule)); - return !innerConnection; - }); - // add concatenated module to the compilation - compilation.modules.add(newModule); - - callback(); - }; - - build(); - }, - err => { - logger.timeEnd("create concatenated modules"); - process.nextTick(callback.bind(null, err)); - } - ); - } - ); - }); - } - - /** - * @param {Compilation} compilation the compilation - * @param {Module} module the module to be added - * @param {RuntimeSpec} runtime the runtime scope - * @returns {Set} the imported modules - */ - _getImports(compilation, module, runtime) { - const moduleGraph = compilation.moduleGraph; - const set = new Set(); - for (const dep of module.dependencies) { - // Get reference info only for harmony Dependencies - if (!(dep instanceof HarmonyImportDependency)) continue; - - const connection = moduleGraph.getConnection(dep); - // Reference is valid and has a module - if ( - !connection || - !connection.module || - !connection.isTargetActive(runtime) - ) { - continue; - } - - const importedNames = compilation.getDependencyReferencedExports( - dep, - undefined - ); - - if ( - importedNames.every(i => - Array.isArray(i) ? i.length > 0 : i.name.length > 0 - ) || - Array.isArray(moduleGraph.getProvidedExports(module)) - ) { - set.add(connection.module); - } - } - return set; - } - - /** - * @param {Compilation} compilation webpack compilation - * @param {ConcatConfiguration} config concat configuration (will be modified when added) - * @param {Module} module the module to be added - * @param {RuntimeSpec} runtime the runtime scope of the generated code - * @param {RuntimeSpec} activeRuntime the runtime scope of the root module - * @param {Set} possibleModules modules that are candidates - * @param {Set} candidates list of potential candidates (will be added to) - * @param {Map} failureCache cache for problematic modules to be more performant - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {boolean} avoidMutateOnFailure avoid mutating the config when adding fails - * @param {Statistics} statistics gathering metrics - * @returns {null | Module | function(RequestShortener): string} the problematic module - */ - _tryToAdd( - compilation, - config, - module, - runtime, - activeRuntime, - possibleModules, - candidates, - failureCache, - chunkGraph, - avoidMutateOnFailure, - statistics - ) { - const cacheEntry = failureCache.get(module); - if (cacheEntry) { - statistics.cached++; - return cacheEntry; - } - - // Already added? - if (config.has(module)) { - statistics.alreadyInConfig++; - return null; - } - - // Not possible to add? - if (!possibleModules.has(module)) { - statistics.invalidModule++; - failureCache.set(module, module); // cache failures for performance - return module; - } - - // Module must be in the correct chunks - const missingChunks = Array.from( - chunkGraph.getModuleChunksIterable(config.rootModule) - ).filter(chunk => !chunkGraph.isModuleInChunk(module, chunk)); - if (missingChunks.length > 0) { - /** - * @param {RequestShortener} requestShortener request shortener - * @returns {string} problem description - */ - const problem = requestShortener => { - const missingChunksList = Array.from( - new Set(missingChunks.map(chunk => chunk.name || "unnamed chunk(s)")) - ).sort(); - const chunks = Array.from( - new Set( - Array.from(chunkGraph.getModuleChunksIterable(module)).map( - chunk => chunk.name || "unnamed chunk(s)" - ) - ) - ).sort(); - return `Module ${module.readableIdentifier( - requestShortener - )} is not in the same chunk(s) (expected in chunk(s) ${missingChunksList.join( - ", " - )}, module is in chunk(s) ${chunks.join(", ")})`; - }; - statistics.incorrectChunks++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - - const moduleGraph = compilation.moduleGraph; - - const incomingConnections = - moduleGraph.getIncomingConnectionsByOriginModule(module); - - const incomingConnectionsFromNonModules = - incomingConnections.get(null) || incomingConnections.get(undefined); - if (incomingConnectionsFromNonModules) { - const activeNonModulesConnections = - incomingConnectionsFromNonModules.filter(connection => - // We are not interested in inactive connections - // or connections without dependency - connection.isActive(runtime) - ); - if (activeNonModulesConnections.length > 0) { - /** - * @param {RequestShortener} requestShortener request shortener - * @returns {string} problem description - */ - const problem = requestShortener => { - const importingExplanations = new Set( - activeNonModulesConnections.map(c => c.explanation).filter(Boolean) - ); - const explanations = Array.from(importingExplanations).sort(); - return `Module ${module.readableIdentifier( - requestShortener - )} is referenced ${ - explanations.length > 0 - ? `by: ${explanations.join(", ")}` - : "in an unsupported way" - }`; - }; - statistics.incorrectDependency++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - } - - /** @type {Map} */ - const incomingConnectionsFromModules = new Map(); - for (const [originModule, connections] of incomingConnections) { - if (originModule) { - // Ignore connection from orphan modules - if (chunkGraph.getNumberOfModuleChunks(originModule) === 0) continue; - - // We don't care for connections from other runtimes - let originRuntime; - for (const r of chunkGraph.getModuleRuntimes(originModule)) { - originRuntime = mergeRuntimeOwned(originRuntime, r); - } - - if (!intersectRuntime(runtime, originRuntime)) continue; - - // We are not interested in inactive connections - const activeConnections = connections.filter(connection => - connection.isActive(runtime) - ); - if (activeConnections.length > 0) - incomingConnectionsFromModules.set(originModule, activeConnections); - } - } - - const incomingModules = Array.from(incomingConnectionsFromModules.keys()); - - // Module must be in the same chunks like the referencing module - const otherChunkModules = incomingModules.filter(originModule => { - for (const chunk of chunkGraph.getModuleChunksIterable( - config.rootModule - )) { - if (!chunkGraph.isModuleInChunk(originModule, chunk)) { - return true; - } - } - return false; - }); - if (otherChunkModules.length > 0) { - /** - * @param {RequestShortener} requestShortener request shortener - * @returns {string} problem description - */ - const problem = requestShortener => { - const names = otherChunkModules - .map(m => m.readableIdentifier(requestShortener)) - .sort(); - return `Module ${module.readableIdentifier( - requestShortener - )} is referenced from different chunks by these modules: ${names.join( - ", " - )}`; - }; - statistics.incorrectChunksOfImporter++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - - /** @type {Map} */ - const nonHarmonyConnections = new Map(); - for (const [originModule, connections] of incomingConnectionsFromModules) { - const selected = connections.filter( - connection => - !connection.dependency || - !(connection.dependency instanceof HarmonyImportDependency) - ); - if (selected.length > 0) - nonHarmonyConnections.set(originModule, connections); - } - if (nonHarmonyConnections.size > 0) { - /** - * @param {RequestShortener} requestShortener request shortener - * @returns {string} problem description - */ - const problem = requestShortener => { - const names = Array.from(nonHarmonyConnections) - .map( - ([originModule, connections]) => - `${originModule.readableIdentifier( - requestShortener - )} (referenced with ${Array.from( - new Set( - connections - .map(c => c.dependency && c.dependency.type) - .filter(Boolean) - ) - ) - .sort() - .join(", ")})` - ) - .sort(); - return `Module ${module.readableIdentifier( - requestShortener - )} is referenced from these modules with unsupported syntax: ${names.join( - ", " - )}`; - }; - statistics.incorrectModuleDependency++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - - if (runtime !== undefined && typeof runtime !== "string") { - // Module must be consistently referenced in the same runtimes - /** @type {{ originModule: Module, runtimeCondition: RuntimeSpec }[]} */ - const otherRuntimeConnections = []; - outer: for (const [ - originModule, - connections - ] of incomingConnectionsFromModules) { - /** @type {false | RuntimeSpec} */ - let currentRuntimeCondition = false; - for (const connection of connections) { - const runtimeCondition = filterRuntime(runtime, runtime => - connection.isTargetActive(runtime) - ); - if (runtimeCondition === false) continue; - if (runtimeCondition === true) continue outer; - currentRuntimeCondition = - currentRuntimeCondition !== false - ? mergeRuntime(currentRuntimeCondition, runtimeCondition) - : runtimeCondition; - } - if (currentRuntimeCondition !== false) { - otherRuntimeConnections.push({ - originModule, - runtimeCondition: currentRuntimeCondition - }); - } - } - if (otherRuntimeConnections.length > 0) { - /** - * @param {RequestShortener} requestShortener request shortener - * @returns {string} problem description - */ - const problem = requestShortener => - `Module ${module.readableIdentifier( - requestShortener - )} is runtime-dependent referenced by these modules: ${Array.from( - otherRuntimeConnections, - ({ originModule, runtimeCondition }) => - `${originModule.readableIdentifier( - requestShortener - )} (expected runtime ${runtimeToString( - runtime - )}, module is only referenced in ${runtimeToString( - /** @type {RuntimeSpec} */ (runtimeCondition) - )})` - ).join(", ")}`; - statistics.incorrectRuntimeCondition++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - } - - let backup; - if (avoidMutateOnFailure) { - backup = config.snapshot(); - } - - // Add the module - config.add(module); - - incomingModules.sort(compareModulesByIdentifier); - - // Every module which depends on the added module must be in the configuration too. - for (const originModule of incomingModules) { - const problem = this._tryToAdd( - compilation, - config, - originModule, - runtime, - activeRuntime, - possibleModules, - candidates, - failureCache, - chunkGraph, - false, - statistics - ); - if (problem) { - if (backup !== undefined) config.rollback(backup); - statistics.importerFailed++; - failureCache.set(module, problem); // cache failures for performance - return problem; - } - } - - // Add imports to possible candidates list - for (const imp of this._getImports(compilation, module, runtime)) { - candidates.add(imp); - } - statistics.added++; - return null; - } -} - -class ConcatConfiguration { - /** - * @param {Module} rootModule the root module - * @param {RuntimeSpec} runtime the runtime - */ - constructor(rootModule, runtime) { - this.rootModule = rootModule; - this.runtime = runtime; - /** @type {Set} */ - this.modules = new Set(); - this.modules.add(rootModule); - /** @type {Map} */ - this.warnings = new Map(); - } - - /** - * @param {Module} module the module - */ - add(module) { - this.modules.add(module); - } - - /** - * @param {Module} module the module - * @returns {boolean} true, when the module is in the module set - */ - has(module) { - return this.modules.has(module); - } - - isEmpty() { - return this.modules.size === 1; - } - - /** - * @param {Module} module the module - * @param {Module | function(RequestShortener): string} problem the problem - */ - addWarning(module, problem) { - this.warnings.set(module, problem); - } - - /** - * @returns {Map} warnings - */ - getWarningsSorted() { - return new Map( - Array.from(this.warnings).sort((a, b) => { - const ai = a[0].identifier(); - const bi = b[0].identifier(); - if (ai < bi) return -1; - if (ai > bi) return 1; - return 0; - }) - ); - } - - /** - * @returns {Set} modules as set - */ - getModules() { - return this.modules; - } - - snapshot() { - return this.modules.size; - } - - /** - * @param {number} snapshot snapshot - */ - rollback(snapshot) { - const modules = this.modules; - for (const m of modules) { - if (snapshot === 0) { - modules.delete(m); - } else { - snapshot--; - } - } - } -} - -module.exports = ModuleConcatenationPlugin; diff --git a/webpack-lib/lib/optimize/RealContentHashPlugin.js b/webpack-lib/lib/optimize/RealContentHashPlugin.js deleted file mode 100644 index 8b0ab056525..00000000000 --- a/webpack-lib/lib/optimize/RealContentHashPlugin.js +++ /dev/null @@ -1,464 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncBailHook } = require("tapable"); -const { RawSource, CachedSource, CompatSource } = require("webpack-sources"); -const Compilation = require("../Compilation"); -const WebpackError = require("../WebpackError"); -const { compareSelect, compareStrings } = require("../util/comparators"); -const createHash = require("../util/createHash"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Cache").Etag} Etag */ -/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {typeof import("../util/Hash")} Hash */ - -const EMPTY_SET = new Set(); - -/** - * @template T - * @param {T | T[]} itemOrItems item or items - * @param {Set} list list - */ -const addToList = (itemOrItems, list) => { - if (Array.isArray(itemOrItems)) { - for (const item of itemOrItems) { - list.add(item); - } - } else if (itemOrItems) { - list.add(itemOrItems); - } -}; - -/** - * @template T - * @param {T[]} input list - * @param {function(T): Buffer} fn map function - * @returns {Buffer[]} buffers without duplicates - */ -const mapAndDeduplicateBuffers = (input, fn) => { - // Buffer.equals compares size first so this should be efficient enough - // If it becomes a performance problem we can use a map and group by size - // instead of looping over all assets. - const result = []; - outer: for (const value of input) { - const buf = fn(value); - for (const other of result) { - if (buf.equals(other)) continue outer; - } - result.push(buf); - } - return result; -}; - -/** - * Escapes regular expression metacharacters - * @param {string} str String to quote - * @returns {string} Escaped string - */ -const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); - -const cachedSourceMap = new WeakMap(); - -/** - * @param {Source} source source - * @returns {CachedSource} cached source - */ -const toCachedSource = source => { - if (source instanceof CachedSource) { - return source; - } - const entry = cachedSourceMap.get(source); - if (entry !== undefined) return entry; - const newSource = new CachedSource(CompatSource.from(source)); - cachedSourceMap.set(source, newSource); - return newSource; -}; - -/** @typedef {Set} OwnHashes */ -/** @typedef {Set} ReferencedHashes */ -/** @typedef {Set} Hashes */ - -/** - * @typedef {object} AssetInfoForRealContentHash - * @property {string} name - * @property {AssetInfo} info - * @property {Source} source - * @property {RawSource | undefined} newSource - * @property {RawSource | undefined} newSourceWithoutOwn - * @property {string} content - * @property {OwnHashes | undefined} ownHashes - * @property {Promise | undefined} contentComputePromise - * @property {Promise | undefined} contentComputeWithoutOwnPromise - * @property {ReferencedHashes | undefined} referencedHashes - * @property {Hashes} hashes - */ - -/** - * @typedef {object} CompilationHooks - * @property {SyncBailHook<[Buffer[], string], string | void>} updateHash - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class RealContentHashPlugin { - /** - * @param {Compilation} compilation the compilation - * @returns {CompilationHooks} 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 = { - updateHash: new SyncBailHook(["content", "oldHash"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {object} options options object - * @param {string | Hash} options.hashFunction the hash function to use - * @param {string} options.hashDigest the hash digest to use - */ - constructor({ hashFunction, hashDigest }) { - this._hashFunction = hashFunction; - this._hashDigest = hashDigest; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("RealContentHashPlugin", compilation => { - const cacheAnalyse = compilation.getCache( - "RealContentHashPlugin|analyse" - ); - const cacheGenerate = compilation.getCache( - "RealContentHashPlugin|generate" - ); - const hooks = RealContentHashPlugin.getCompilationHooks(compilation); - compilation.hooks.processAssets.tapPromise( - { - name: "RealContentHashPlugin", - stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH - }, - async () => { - const assets = compilation.getAssets(); - /** @type {AssetInfoForRealContentHash[]} */ - const assetsWithInfo = []; - /** @type {Map} */ - const hashToAssets = new Map(); - for (const { source, info, name } of assets) { - const cachedSource = toCachedSource(source); - const content = /** @type {string} */ (cachedSource.source()); - /** @type {Hashes} */ - const hashes = new Set(); - addToList(info.contenthash, hashes); - /** @type {AssetInfoForRealContentHash} */ - const data = { - name, - info, - source: cachedSource, - newSource: undefined, - newSourceWithoutOwn: undefined, - content, - ownHashes: undefined, - contentComputePromise: undefined, - contentComputeWithoutOwnPromise: undefined, - referencedHashes: undefined, - hashes - }; - assetsWithInfo.push(data); - for (const hash of hashes) { - const list = hashToAssets.get(hash); - if (list === undefined) { - hashToAssets.set(hash, [data]); - } else { - list.push(data); - } - } - } - if (hashToAssets.size === 0) return; - const hashRegExp = new RegExp( - Array.from(hashToAssets.keys(), quoteMeta).join("|"), - "g" - ); - await Promise.all( - assetsWithInfo.map(async asset => { - const { name, source, content, hashes } = asset; - if (Buffer.isBuffer(content)) { - asset.referencedHashes = EMPTY_SET; - asset.ownHashes = EMPTY_SET; - return; - } - const etag = cacheAnalyse.mergeEtags( - cacheAnalyse.getLazyHashedEtag(source), - Array.from(hashes).join("|") - ); - [asset.referencedHashes, asset.ownHashes] = - await cacheAnalyse.providePromise(name, etag, () => { - const referencedHashes = new Set(); - const ownHashes = new Set(); - const inContent = content.match(hashRegExp); - if (inContent) { - for (const hash of inContent) { - if (hashes.has(hash)) { - ownHashes.add(hash); - continue; - } - referencedHashes.add(hash); - } - } - return [referencedHashes, ownHashes]; - }); - }) - ); - /** - * @param {string} hash the hash - * @returns {undefined | ReferencedHashes} the referenced hashes - */ - const getDependencies = hash => { - const assets = hashToAssets.get(hash); - if (!assets) { - const referencingAssets = assetsWithInfo.filter(asset => - /** @type {ReferencedHashes} */ (asset.referencedHashes).has( - hash - ) - ); - const err = new WebpackError(`RealContentHashPlugin -Some kind of unexpected caching problem occurred. -An asset was cached with a reference to another asset (${hash}) that's not in the compilation anymore. -Either the asset was incorrectly cached, or the referenced asset should also be restored from cache. -Referenced by: -${referencingAssets - .map(a => { - const match = new RegExp(`.{0,20}${quoteMeta(hash)}.{0,20}`).exec( - a.content - ); - return ` - ${a.name}: ...${match ? match[0] : "???"}...`; - }) - .join("\n")}`); - compilation.errors.push(err); - return; - } - const hashes = new Set(); - for (const { referencedHashes, ownHashes } of assets) { - if (!(/** @type {OwnHashes} */ (ownHashes).has(hash))) { - for (const hash of /** @type {OwnHashes} */ (ownHashes)) { - hashes.add(hash); - } - } - for (const hash of /** @type {ReferencedHashes} */ ( - referencedHashes - )) { - hashes.add(hash); - } - } - return hashes; - }; - /** - * @param {string} hash the hash - * @returns {string} the hash info - */ - const hashInfo = hash => { - const assets = hashToAssets.get(hash); - return `${hash} (${Array.from( - /** @type {AssetInfoForRealContentHash[]} */ (assets), - a => a.name - )})`; - }; - const hashesInOrder = new Set(); - for (const hash of hashToAssets.keys()) { - /** - * @param {string} hash the hash - * @param {Set} stack stack of hashes - */ - const add = (hash, stack) => { - const deps = getDependencies(hash); - if (!deps) return; - stack.add(hash); - for (const dep of deps) { - if (hashesInOrder.has(dep)) continue; - if (stack.has(dep)) { - throw new Error( - `Circular hash dependency ${Array.from( - stack, - hashInfo - ).join(" -> ")} -> ${hashInfo(dep)}` - ); - } - add(dep, stack); - } - hashesInOrder.add(hash); - stack.delete(hash); - }; - if (hashesInOrder.has(hash)) continue; - add(hash, new Set()); - } - const hashToNewHash = new Map(); - /** - * @param {AssetInfoForRealContentHash} asset asset info - * @returns {Etag} etag - */ - const getEtag = asset => - cacheGenerate.mergeEtags( - cacheGenerate.getLazyHashedEtag(asset.source), - Array.from( - /** @type {ReferencedHashes} */ (asset.referencedHashes), - hash => hashToNewHash.get(hash) - ).join("|") - ); - /** - * @param {AssetInfoForRealContentHash} asset asset info - * @returns {Promise} - */ - const computeNewContent = asset => { - if (asset.contentComputePromise) return asset.contentComputePromise; - return (asset.contentComputePromise = (async () => { - if ( - /** @type {OwnHashes} */ (asset.ownHashes).size > 0 || - Array.from( - /** @type {ReferencedHashes} */ - (asset.referencedHashes) - ).some(hash => hashToNewHash.get(hash) !== hash) - ) { - const identifier = asset.name; - const etag = getEtag(asset); - asset.newSource = await cacheGenerate.providePromise( - identifier, - etag, - () => { - const newContent = asset.content.replace(hashRegExp, hash => - hashToNewHash.get(hash) - ); - return new RawSource(newContent); - } - ); - } - })()); - }; - /** - * @param {AssetInfoForRealContentHash} asset asset info - * @returns {Promise} - */ - const computeNewContentWithoutOwn = asset => { - if (asset.contentComputeWithoutOwnPromise) - return asset.contentComputeWithoutOwnPromise; - return (asset.contentComputeWithoutOwnPromise = (async () => { - if ( - /** @type {OwnHashes} */ (asset.ownHashes).size > 0 || - Array.from( - /** @type {ReferencedHashes} */ - (asset.referencedHashes) - ).some(hash => hashToNewHash.get(hash) !== hash) - ) { - const identifier = `${asset.name}|without-own`; - const etag = getEtag(asset); - asset.newSourceWithoutOwn = await cacheGenerate.providePromise( - identifier, - etag, - () => { - const newContent = asset.content.replace( - hashRegExp, - hash => { - if ( - /** @type {OwnHashes} */ (asset.ownHashes).has(hash) - ) { - return ""; - } - return hashToNewHash.get(hash); - } - ); - return new RawSource(newContent); - } - ); - } - })()); - }; - const comparator = compareSelect(a => a.name, compareStrings); - for (const oldHash of hashesInOrder) { - const assets = - /** @type {AssetInfoForRealContentHash[]} */ - (hashToAssets.get(oldHash)); - assets.sort(comparator); - await Promise.all( - assets.map(asset => - /** @type {OwnHashes} */ (asset.ownHashes).has(oldHash) - ? computeNewContentWithoutOwn(asset) - : computeNewContent(asset) - ) - ); - const assetsContent = mapAndDeduplicateBuffers(assets, asset => { - if (/** @type {OwnHashes} */ (asset.ownHashes).has(oldHash)) { - return asset.newSourceWithoutOwn - ? asset.newSourceWithoutOwn.buffer() - : asset.source.buffer(); - } - return asset.newSource - ? asset.newSource.buffer() - : asset.source.buffer(); - }); - let newHash = hooks.updateHash.call(assetsContent, oldHash); - if (!newHash) { - const hash = createHash(this._hashFunction); - if (compilation.outputOptions.hashSalt) { - hash.update(compilation.outputOptions.hashSalt); - } - for (const content of assetsContent) { - hash.update(content); - } - const digest = hash.digest(this._hashDigest); - newHash = /** @type {string} */ (digest.slice(0, oldHash.length)); - } - hashToNewHash.set(oldHash, newHash); - } - await Promise.all( - assetsWithInfo.map(async asset => { - await computeNewContent(asset); - const newName = asset.name.replace(hashRegExp, hash => - hashToNewHash.get(hash) - ); - - const infoUpdate = {}; - const hash = asset.info.contenthash; - infoUpdate.contenthash = Array.isArray(hash) - ? hash.map(hash => hashToNewHash.get(hash)) - : hashToNewHash.get(hash); - - if (asset.newSource !== undefined) { - compilation.updateAsset( - asset.name, - asset.newSource, - infoUpdate - ); - } else { - compilation.updateAsset(asset.name, asset.source, infoUpdate); - } - - if (asset.name !== newName) { - compilation.renameAsset(asset.name, newName); - } - }) - ); - } - ); - }); - } -} - -module.exports = RealContentHashPlugin; diff --git a/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js b/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js deleted file mode 100644 index 6dbc2ae6aa0..00000000000 --- a/webpack-lib/lib/optimize/RemoveEmptyChunksPlugin.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_BASIC, STAGE_ADVANCED } = require("../OptimizationStages"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -class RemoveEmptyChunksPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("RemoveEmptyChunksPlugin", compilation => { - /** - * @param {Iterable} chunks the chunks array - * @returns {void} - */ - const handler = chunks => { - const chunkGraph = compilation.chunkGraph; - for (const chunk of chunks) { - if ( - chunkGraph.getNumberOfChunkModules(chunk) === 0 && - !chunk.hasRuntime() && - chunkGraph.getNumberOfEntryModules(chunk) === 0 - ) { - compilation.chunkGraph.disconnectChunk(chunk); - compilation.chunks.delete(chunk); - } - } - }; - - // TODO do it once - compilation.hooks.optimizeChunks.tap( - { - name: "RemoveEmptyChunksPlugin", - stage: STAGE_BASIC - }, - handler - ); - compilation.hooks.optimizeChunks.tap( - { - name: "RemoveEmptyChunksPlugin", - stage: STAGE_ADVANCED - }, - handler - ); - }); - } -} -module.exports = RemoveEmptyChunksPlugin; diff --git a/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js b/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js deleted file mode 100644 index 8c244ec5077..00000000000 --- a/webpack-lib/lib/optimize/RemoveParentModulesPlugin.js +++ /dev/null @@ -1,204 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { STAGE_BASIC } = require("../OptimizationStages"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ - -/** - * Intersects multiple masks represented as bigints - * @param {bigint[]} masks The module masks to intersect - * @returns {bigint} The intersection of all masks - */ -function intersectMasks(masks) { - let result = masks[0]; - for (let i = masks.length - 1; i >= 1; i--) { - result &= masks[i]; - } - return result; -} - -const ZERO_BIGINT = BigInt(0); -const ONE_BIGINT = BigInt(1); -const THIRTY_TWO_BIGINT = BigInt(32); - -/** - * Parses the module mask and returns the modules represented by it - * @param {bigint} mask the module mask - * @param {Module[]} ordinalModules the modules in the order they were added to the mask (LSB is index 0) - * @returns {Generator} the modules represented by the mask - */ -function* getModulesFromMask(mask, ordinalModules) { - let offset = 31; - while (mask !== ZERO_BIGINT) { - // Consider the last 32 bits, since that's what Math.clz32 can handle - let last32 = Number(BigInt.asUintN(32, mask)); - while (last32 > 0) { - const last = Math.clz32(last32); - // The number of trailing zeros is the number trimmed off the input mask + 31 - the number of leading zeros - // The 32 is baked into the initial value of offset - const moduleIndex = offset - last; - // The number of trailing zeros is the index into the array generated by getOrCreateModuleMask - const module = ordinalModules[moduleIndex]; - yield module; - // Remove the matched module from the mask - // Since we can only count leading zeros, not trailing, we can't just downshift the mask - last32 &= ~(1 << (31 - last)); - } - - // Remove the processed chunk from the mask - mask >>= THIRTY_TWO_BIGINT; - offset += 32; - } -} - -class RemoveParentModulesPlugin { - /** - * @param {Compiler} compiler the compiler - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("RemoveParentModulesPlugin", compilation => { - /** - * @param {Iterable} chunks the chunks - * @param {ChunkGroup[]} chunkGroups the chunk groups - */ - const handler = (chunks, chunkGroups) => { - const chunkGraph = compilation.chunkGraph; - const queue = new Set(); - const availableModulesMap = new WeakMap(); - - let nextModuleMask = ONE_BIGINT; - const maskByModule = new WeakMap(); - /** @type {Module[]} */ - const ordinalModules = []; - - /** - * Gets or creates a unique mask for a module - * @param {Module} mod the module to get the mask for - * @returns {bigint} the module mask to uniquely identify the module - */ - const getOrCreateModuleMask = mod => { - let id = maskByModule.get(mod); - if (id === undefined) { - id = nextModuleMask; - ordinalModules.push(mod); - maskByModule.set(mod, id); - nextModuleMask <<= ONE_BIGINT; - } - return id; - }; - - // Initialize masks by chunk and by chunk group for quicker comparisons - const chunkMasks = new WeakMap(); - for (const chunk of chunks) { - let mask = ZERO_BIGINT; - for (const m of chunkGraph.getChunkModulesIterable(chunk)) { - const id = getOrCreateModuleMask(m); - mask |= id; - } - chunkMasks.set(chunk, mask); - } - - const chunkGroupMasks = new WeakMap(); - for (const chunkGroup of chunkGroups) { - let mask = ZERO_BIGINT; - for (const chunk of chunkGroup.chunks) { - const chunkMask = chunkMasks.get(chunk); - if (chunkMask !== undefined) { - mask |= chunkMask; - } - } - chunkGroupMasks.set(chunkGroup, mask); - } - - for (const chunkGroup of compilation.entrypoints.values()) { - // initialize available modules for chunks without parents - availableModulesMap.set(chunkGroup, ZERO_BIGINT); - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - for (const chunkGroup of compilation.asyncEntrypoints) { - // initialize available modules for chunks without parents - availableModulesMap.set(chunkGroup, ZERO_BIGINT); - for (const child of chunkGroup.childrenIterable) { - queue.add(child); - } - } - - for (const chunkGroup of queue) { - let availableModulesMask = availableModulesMap.get(chunkGroup); - let changed = false; - for (const parent of chunkGroup.parentsIterable) { - const availableModulesInParent = availableModulesMap.get(parent); - if (availableModulesInParent !== undefined) { - const parentMask = - availableModulesInParent | chunkGroupMasks.get(parent); - // If we know the available modules in parent: process these - if (availableModulesMask === undefined) { - // if we have not own info yet: create new entry - availableModulesMask = parentMask; - changed = true; - } else { - const newMask = availableModulesMask & parentMask; - if (newMask !== availableModulesMask) { - changed = true; - availableModulesMask = newMask; - } - } - } - } - - if (changed) { - availableModulesMap.set(chunkGroup, availableModulesMask); - // if something changed: enqueue our children - for (const child of chunkGroup.childrenIterable) { - // Push the child to the end of the queue - queue.delete(child); - queue.add(child); - } - } - } - - // now we have available modules for every chunk - for (const chunk of chunks) { - const chunkMask = chunkMasks.get(chunk); - if (chunkMask === undefined) continue; // No info about this chunk - - const availableModulesSets = Array.from( - chunk.groupsIterable, - chunkGroup => availableModulesMap.get(chunkGroup) - ); - if (availableModulesSets.includes(undefined)) continue; // No info about this chunk group - - const availableModulesMask = intersectMasks(availableModulesSets); - const toRemoveMask = chunkMask & availableModulesMask; - if (toRemoveMask !== ZERO_BIGINT) { - for (const module of getModulesFromMask( - toRemoveMask, - ordinalModules - )) { - chunkGraph.disconnectChunkAndModule(chunk, module); - } - } - } - }; - compilation.hooks.optimizeChunks.tap( - { - name: "RemoveParentModulesPlugin", - stage: STAGE_BASIC - }, - handler - ); - }); - } -} -module.exports = RemoveParentModulesPlugin; diff --git a/webpack-lib/lib/optimize/RuntimeChunkPlugin.js b/webpack-lib/lib/optimize/RuntimeChunkPlugin.js deleted file mode 100644 index 1923e468303..00000000000 --- a/webpack-lib/lib/optimize/RuntimeChunkPlugin.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../Compilation").EntryData} EntryData */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Entrypoint")} Entrypoint */ - -class RuntimeChunkPlugin { - /** - * @param {{ name?: (entrypoint: { name: string }) => string }} options options - */ - constructor(options) { - this.options = { - /** - * @param {Entrypoint} entrypoint entrypoint name - * @returns {string} runtime chunk name - */ - name: entrypoint => `runtime~${entrypoint.name}`, - ...options - }; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap("RuntimeChunkPlugin", compilation => { - compilation.hooks.addEntry.tap( - "RuntimeChunkPlugin", - (_, { name: entryName }) => { - if (entryName === undefined) return; - const data = - /** @type {EntryData} */ - (compilation.entries.get(entryName)); - if (data.options.runtime === undefined && !data.options.dependOn) { - // Determine runtime chunk name - let name = - /** @type {string | ((entrypoint: { name: string }) => string)} */ - (this.options.name); - if (typeof name === "function") { - name = name({ name: entryName }); - } - data.options.runtime = name; - } - } - ); - }); - } -} - -module.exports = RuntimeChunkPlugin; diff --git a/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js b/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js deleted file mode 100644 index 0edb048db26..00000000000 --- a/webpack-lib/lib/optimize/SideEffectsFlagPlugin.js +++ /dev/null @@ -1,396 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const glob2regexp = require("glob-to-regexp"); -const { - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM, - JAVASCRIPT_MODULE_TYPE_DYNAMIC -} = require("../ModuleTypeConstants"); -const { STAGE_DEFAULT } = require("../OptimizationStages"); -const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency"); -const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency"); -const formatLocation = require("../formatLocation"); - -/** @typedef {import("estree").ModuleDeclaration} ModuleDeclaration */ -/** @typedef {import("estree").Statement} Statement */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../NormalModuleFactory").ModuleSettings} ModuleSettings */ -/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -/** - * @typedef {object} ExportInModule - * @property {Module} module the module - * @property {string} exportName the name of the export - * @property {boolean} checked if the export is conditional - */ - -/** - * @typedef {object} ReexportInfo - * @property {Map} static - * @property {Map>} dynamic - */ - -/** @typedef {Map} CacheItem */ - -/** @type {WeakMap} */ -const globToRegexpCache = new WeakMap(); - -/** - * @param {string} glob the pattern - * @param {Map} cache the glob to RegExp cache - * @returns {RegExp} a regular expression - */ -const globToRegexp = (glob, cache) => { - const cacheEntry = cache.get(glob); - if (cacheEntry !== undefined) return cacheEntry; - if (!glob.includes("/")) { - glob = `**/${glob}`; - } - const baseRegexp = glob2regexp(glob, { globstar: true, extended: true }); - const regexpSource = baseRegexp.source; - const regexp = new RegExp(`^(\\./)?${regexpSource.slice(1)}`); - cache.set(glob, regexp); - return regexp; -}; - -const PLUGIN_NAME = "SideEffectsFlagPlugin"; - -class SideEffectsFlagPlugin { - /** - * @param {boolean} analyseSource analyse source code for side effects - */ - constructor(analyseSource = true) { - this._analyseSource = analyseSource; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - let cache = globToRegexpCache.get(compiler.root); - if (cache === undefined) { - cache = new Map(); - globToRegexpCache.set(compiler.root, cache); - } - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const moduleGraph = compilation.moduleGraph; - normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => { - const resolveData = data.resourceResolveData; - if ( - resolveData && - resolveData.descriptionFileData && - resolveData.relativePath - ) { - const sideEffects = resolveData.descriptionFileData.sideEffects; - if (sideEffects !== undefined) { - if (module.factoryMeta === undefined) { - module.factoryMeta = {}; - } - const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects( - resolveData.relativePath, - sideEffects, - /** @type {CacheItem} */ (cache) - ); - module.factoryMeta.sideEffectFree = !hasSideEffects; - } - } - - return module; - }); - normalModuleFactory.hooks.module.tap(PLUGIN_NAME, (module, data) => { - const settings = /** @type {ModuleSettings} */ (data.settings); - if (typeof settings.sideEffects === "boolean") { - if (module.factoryMeta === undefined) { - module.factoryMeta = {}; - } - module.factoryMeta.sideEffectFree = !settings.sideEffects; - } - return module; - }); - if (this._analyseSource) { - /** - * @param {JavascriptParser} parser the parser - * @returns {void} - */ - const parserHandler = parser => { - /** @type {undefined | Statement | ModuleDeclaration} */ - let sideEffectsStatement; - parser.hooks.program.tap(PLUGIN_NAME, () => { - sideEffectsStatement = undefined; - }); - parser.hooks.statement.tap( - { name: PLUGIN_NAME, stage: -100 }, - statement => { - if (sideEffectsStatement) return; - if (parser.scope.topLevelScope !== true) return; - switch (statement.type) { - case "ExpressionStatement": - if ( - !parser.isPure( - statement.expression, - /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - break; - case "IfStatement": - case "WhileStatement": - case "DoWhileStatement": - if ( - !parser.isPure( - statement.test, - /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - // statement hook will be called for child statements too - break; - case "ForStatement": - if ( - !parser.isPure( - statement.init, - /** @type {Range} */ (statement.range)[0] - ) || - !parser.isPure( - statement.test, - statement.init - ? /** @type {Range} */ (statement.init.range)[1] - : /** @type {Range} */ (statement.range)[0] - ) || - !parser.isPure( - statement.update, - statement.test - ? /** @type {Range} */ (statement.test.range)[1] - : statement.init - ? /** @type {Range} */ (statement.init.range)[1] - : /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - // statement hook will be called for child statements too - break; - case "SwitchStatement": - if ( - !parser.isPure( - statement.discriminant, - /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - // statement hook will be called for child statements too - break; - case "VariableDeclaration": - case "ClassDeclaration": - case "FunctionDeclaration": - if ( - !parser.isPure( - statement, - /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - break; - case "ExportNamedDeclaration": - case "ExportDefaultDeclaration": - if ( - !parser.isPure( - /** @type {TODO} */ - (statement.declaration), - /** @type {Range} */ (statement.range)[0] - ) - ) { - sideEffectsStatement = statement; - } - break; - case "LabeledStatement": - case "BlockStatement": - // statement hook will be called for child statements too - break; - case "EmptyStatement": - break; - case "ExportAllDeclaration": - case "ImportDeclaration": - // imports will be handled by the dependencies - break; - default: - sideEffectsStatement = statement; - break; - } - } - ); - parser.hooks.finish.tap(PLUGIN_NAME, () => { - if (sideEffectsStatement === undefined) { - /** @type {BuildMeta} */ - (parser.state.module.buildMeta).sideEffectFree = true; - } else { - const { loc, type } = sideEffectsStatement; - moduleGraph - .getOptimizationBailout(parser.state.module) - .push( - () => - `Statement (${type}) with side effects in source code at ${formatLocation( - /** @type {DependencyLocation} */ (loc) - )}` - ); - } - }); - }; - for (const key of [ - JAVASCRIPT_MODULE_TYPE_AUTO, - JAVASCRIPT_MODULE_TYPE_ESM, - JAVASCRIPT_MODULE_TYPE_DYNAMIC - ]) { - normalModuleFactory.hooks.parser - .for(key) - .tap(PLUGIN_NAME, parserHandler); - } - } - compilation.hooks.optimizeDependencies.tap( - { - name: PLUGIN_NAME, - stage: STAGE_DEFAULT - }, - modules => { - const logger = compilation.getLogger( - "webpack.SideEffectsFlagPlugin" - ); - - logger.time("update dependencies"); - - const optimizedModules = new Set(); - - /** - * @param {Module} module module - */ - const optimizeIncomingConnections = module => { - if (optimizedModules.has(module)) return; - optimizedModules.add(module); - if (module.getSideEffectsConnectionState(moduleGraph) === false) { - const exportsInfo = moduleGraph.getExportsInfo(module); - for (const connection of moduleGraph.getIncomingConnections( - module - )) { - const dep = connection.dependency; - let isReexport; - if ( - (isReexport = - dep instanceof - HarmonyExportImportedSpecifierDependency) || - (dep instanceof HarmonyImportSpecifierDependency && - !dep.namespaceObjectAsContext) - ) { - if (connection.originModule !== null) { - optimizeIncomingConnections(connection.originModule); - } - // TODO improve for export * - if (isReexport && dep.name) { - const exportInfo = moduleGraph.getExportInfo( - /** @type {Module} */ (connection.originModule), - dep.name - ); - exportInfo.moveTarget( - moduleGraph, - ({ module }) => - module.getSideEffectsConnectionState(moduleGraph) === - false, - ({ module: newModule, export: exportName }) => { - moduleGraph.updateModule(dep, newModule); - moduleGraph.addExplanation( - dep, - "(skipped side-effect-free modules)" - ); - const ids = dep.getIds(moduleGraph); - dep.setIds( - moduleGraph, - exportName - ? [...exportName, ...ids.slice(1)] - : ids.slice(1) - ); - return /** @type {ModuleGraphConnection} */ ( - moduleGraph.getConnection(dep) - ); - } - ); - continue; - } - // TODO improve for nested imports - const ids = dep.getIds(moduleGraph); - if (ids.length > 0) { - const exportInfo = exportsInfo.getExportInfo(ids[0]); - const target = exportInfo.getTarget( - moduleGraph, - ({ module }) => - module.getSideEffectsConnectionState(moduleGraph) === - false - ); - if (!target) continue; - - moduleGraph.updateModule(dep, target.module); - moduleGraph.addExplanation( - dep, - "(skipped side-effect-free modules)" - ); - dep.setIds( - moduleGraph, - target.export - ? [...target.export, ...ids.slice(1)] - : ids.slice(1) - ); - } - } - } - } - }; - - for (const module of modules) { - optimizeIncomingConnections(module); - } - logger.timeEnd("update dependencies"); - } - ); - } - ); - } - - /** - * @param {string} moduleName the module name - * @param {undefined | boolean | string | string[]} flagValue the flag value - * @param {Map} cache cache for glob to regexp - * @returns {boolean | undefined} true, when the module has side effects, undefined or false when not - */ - static moduleHasSideEffects(moduleName, flagValue, cache) { - switch (typeof flagValue) { - case "undefined": - return true; - case "boolean": - return flagValue; - case "string": - return globToRegexp(flagValue, cache).test(moduleName); - case "object": - return flagValue.some(glob => - SideEffectsFlagPlugin.moduleHasSideEffects(moduleName, glob, cache) - ); - } - } -} -module.exports = SideEffectsFlagPlugin; diff --git a/webpack-lib/lib/optimize/SplitChunksPlugin.js b/webpack-lib/lib/optimize/SplitChunksPlugin.js deleted file mode 100644 index c37d200e5d4..00000000000 --- a/webpack-lib/lib/optimize/SplitChunksPlugin.js +++ /dev/null @@ -1,1767 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Chunk = require("../Chunk"); -const { STAGE_ADVANCED } = require("../OptimizationStages"); -const WebpackError = require("../WebpackError"); -const { requestToId } = require("../ids/IdHelpers"); -const { isSubset } = require("../util/SetHelpers"); -const SortableSet = require("../util/SortableSet"); -const { - compareModulesByIdentifier, - compareIterables -} = require("../util/comparators"); -const createHash = require("../util/createHash"); -const deterministicGrouping = require("../util/deterministicGrouping"); -const { makePathsRelative } = require("../util/identifier"); -const memoize = require("../util/memoize"); -const MinMaxSizeWarning = require("./MinMaxSizeWarning"); - -/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksCacheGroup} OptimizationSplitChunksCacheGroup */ -/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksGetCacheGroups} OptimizationSplitChunksGetCacheGroups */ -/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksOptions} OptimizationSplitChunksOptions */ -/** @typedef {import("../../declarations/WebpackOptions").OptimizationSplitChunksSizes} OptimizationSplitChunksSizes */ -/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** @typedef {import("../util/deterministicGrouping").GroupedItems} DeterministicGroupingGroupedItemsForModule */ -/** @typedef {import("../util/deterministicGrouping").Options} DeterministicGroupingOptionsForModule */ - -/** @typedef {Record} SplitChunksSizes */ - -/** - * @callback ChunkFilterFunction - * @param {Chunk} chunk - * @returns {boolean | undefined} - */ - -/** - * @callback CombineSizeFunction - * @param {number} a - * @param {number} b - * @returns {number} - */ - -/** - * @typedef {object} CacheGroupSource - * @property {string=} key - * @property {number=} priority - * @property {GetName=} getName - * @property {ChunkFilterFunction=} chunksFilter - * @property {boolean=} enforce - * @property {SplitChunksSizes} minSize - * @property {SplitChunksSizes} minSizeReduction - * @property {SplitChunksSizes} minRemainingSize - * @property {SplitChunksSizes} enforceSizeThreshold - * @property {SplitChunksSizes} maxAsyncSize - * @property {SplitChunksSizes} maxInitialSize - * @property {number=} minChunks - * @property {number=} maxAsyncRequests - * @property {number=} maxInitialRequests - * @property {TemplatePath=} filename - * @property {string=} idHint - * @property {string=} automaticNameDelimiter - * @property {boolean=} reuseExistingChunk - * @property {boolean=} usedExports - */ - -/** - * @typedef {object} CacheGroup - * @property {string} key - * @property {number=} priority - * @property {GetName=} getName - * @property {ChunkFilterFunction=} chunksFilter - * @property {SplitChunksSizes} minSize - * @property {SplitChunksSizes} minSizeReduction - * @property {SplitChunksSizes} minRemainingSize - * @property {SplitChunksSizes} enforceSizeThreshold - * @property {SplitChunksSizes} maxAsyncSize - * @property {SplitChunksSizes} maxInitialSize - * @property {number=} minChunks - * @property {number=} maxAsyncRequests - * @property {number=} maxInitialRequests - * @property {TemplatePath=} filename - * @property {string=} idHint - * @property {string} automaticNameDelimiter - * @property {boolean} reuseExistingChunk - * @property {boolean} usedExports - * @property {boolean} _validateSize - * @property {boolean} _validateRemainingSize - * @property {SplitChunksSizes} _minSizeForMaxSize - * @property {boolean} _conditionalEnforce - */ - -/** - * @typedef {object} FallbackCacheGroup - * @property {ChunkFilterFunction} chunksFilter - * @property {SplitChunksSizes} minSize - * @property {SplitChunksSizes} maxAsyncSize - * @property {SplitChunksSizes} maxInitialSize - * @property {string} automaticNameDelimiter - */ - -/** - * @typedef {object} CacheGroupsContext - * @property {ModuleGraph} moduleGraph - * @property {ChunkGraph} chunkGraph - */ - -/** - * @callback GetCacheGroups - * @param {Module} module - * @param {CacheGroupsContext} context - * @returns {CacheGroupSource[]} - */ - -/** - * @callback GetName - * @param {Module=} module - * @param {Chunk[]=} chunks - * @param {string=} key - * @returns {string=} - */ - -/** - * @typedef {object} SplitChunksOptions - * @property {ChunkFilterFunction} chunksFilter - * @property {string[]} defaultSizeTypes - * @property {SplitChunksSizes} minSize - * @property {SplitChunksSizes} minSizeReduction - * @property {SplitChunksSizes} minRemainingSize - * @property {SplitChunksSizes} enforceSizeThreshold - * @property {SplitChunksSizes} maxInitialSize - * @property {SplitChunksSizes} maxAsyncSize - * @property {number} minChunks - * @property {number} maxAsyncRequests - * @property {number} maxInitialRequests - * @property {boolean} hidePathInfo - * @property {TemplatePath} filename - * @property {string} automaticNameDelimiter - * @property {GetCacheGroups} getCacheGroups - * @property {GetName} getName - * @property {boolean} usedExports - * @property {FallbackCacheGroup} fallbackCacheGroup - */ - -/** - * @typedef {object} ChunksInfoItem - * @property {SortableSet} modules - * @property {CacheGroup} cacheGroup - * @property {number} cacheGroupIndex - * @property {string} name - * @property {Record} sizes - * @property {Set} chunks - * @property {Set} reusableChunks - * @property {Set} chunksKeys - */ - -const defaultGetName = /** @type {GetName} */ (() => {}); - -const deterministicGroupingForModules = - /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ - (deterministicGrouping); - -/** @type {WeakMap} */ -const getKeyCache = new WeakMap(); - -/** - * @param {string} name a filename to hash - * @param {OutputOptions} outputOptions hash function used - * @returns {string} hashed filename - */ -const hashFilename = (name, outputOptions) => { - const digest = - /** @type {string} */ - ( - createHash(outputOptions.hashFunction) - .update(name) - .digest(outputOptions.hashDigest) - ); - return digest.slice(0, 8); -}; - -/** - * @param {Chunk} chunk the chunk - * @returns {number} the number of requests - */ -const getRequests = chunk => { - let requests = 0; - for (const chunkGroup of chunk.groupsIterable) { - requests = Math.max(requests, chunkGroup.chunks.length); - } - return requests; -}; - -/** - * @template {object} T - * @template {object} R - * @param {T} obj obj an object - * @param {function(T[keyof T], keyof T): T[keyof T]} fn fn - * @returns {T} result - */ -const mapObject = (obj, fn) => { - const newObj = Object.create(null); - for (const key of Object.keys(obj)) { - newObj[key] = fn( - obj[/** @type {keyof T} */ (key)], - /** @type {keyof T} */ - (key) - ); - } - return newObj; -}; - -/** - * @template T - * @param {Set} a set - * @param {Set} b other set - * @returns {boolean} true if at least one item of a is in b - */ -const isOverlap = (a, b) => { - for (const item of a) { - if (b.has(item)) return true; - } - return false; -}; - -const compareModuleIterables = compareIterables(compareModulesByIdentifier); - -/** - * @param {ChunksInfoItem} a item - * @param {ChunksInfoItem} b item - * @returns {number} compare result - */ -const compareEntries = (a, b) => { - // 1. by priority - const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority; - if (diffPriority) return diffPriority; - // 2. by number of chunks - const diffCount = a.chunks.size - b.chunks.size; - if (diffCount) return diffCount; - // 3. by size reduction - const aSizeReduce = totalSize(a.sizes) * (a.chunks.size - 1); - const bSizeReduce = totalSize(b.sizes) * (b.chunks.size - 1); - const diffSizeReduce = aSizeReduce - bSizeReduce; - if (diffSizeReduce) return diffSizeReduce; - // 4. by cache group index - const indexDiff = b.cacheGroupIndex - a.cacheGroupIndex; - if (indexDiff) return indexDiff; - // 5. by number of modules (to be able to compare by identifier) - const modulesA = a.modules; - const modulesB = b.modules; - const diff = modulesA.size - modulesB.size; - if (diff) return diff; - // 6. by module identifiers - modulesA.sort(); - modulesB.sort(); - return compareModuleIterables(modulesA, modulesB); -}; - -/** - * @param {Chunk} chunk the chunk - * @returns {boolean} true, if the chunk is an entry chunk - */ -const INITIAL_CHUNK_FILTER = chunk => chunk.canBeInitial(); -/** - * @param {Chunk} chunk the chunk - * @returns {boolean} true, if the chunk is an async chunk - */ -const ASYNC_CHUNK_FILTER = chunk => !chunk.canBeInitial(); -/** - * @param {Chunk} chunk the chunk - * @returns {boolean} always true - */ -const ALL_CHUNK_FILTER = chunk => true; - -/** - * @param {OptimizationSplitChunksSizes | undefined} value the sizes - * @param {string[]} defaultSizeTypes the default size types - * @returns {SplitChunksSizes} normalized representation - */ -const normalizeSizes = (value, defaultSizeTypes) => { - if (typeof value === "number") { - /** @type {Record} */ - const o = {}; - for (const sizeType of defaultSizeTypes) o[sizeType] = value; - return o; - } else if (typeof value === "object" && value !== null) { - return { ...value }; - } - return {}; -}; - -/** - * @param {...(SplitChunksSizes | undefined)} sizes the sizes - * @returns {SplitChunksSizes} the merged sizes - */ -const mergeSizes = (...sizes) => { - /** @type {SplitChunksSizes} */ - let merged = {}; - for (let i = sizes.length - 1; i >= 0; i--) { - merged = Object.assign(merged, sizes[i]); - } - return merged; -}; - -/** - * @param {SplitChunksSizes} sizes the sizes - * @returns {boolean} true, if there are sizes > 0 - */ -const hasNonZeroSizes = sizes => { - for (const key of Object.keys(sizes)) { - if (sizes[key] > 0) return true; - } - return false; -}; - -/** - * @param {SplitChunksSizes} a first sizes - * @param {SplitChunksSizes} b second sizes - * @param {CombineSizeFunction} combine a function to combine sizes - * @returns {SplitChunksSizes} the combine sizes - */ -const combineSizes = (a, b, combine) => { - const aKeys = new Set(Object.keys(a)); - const bKeys = new Set(Object.keys(b)); - /** @type {SplitChunksSizes} */ - const result = {}; - for (const key of aKeys) { - result[key] = bKeys.has(key) ? combine(a[key], b[key]) : a[key]; - } - for (const key of bKeys) { - if (!aKeys.has(key)) { - result[key] = b[key]; - } - } - return result; -}; - -/** - * @param {SplitChunksSizes} sizes the sizes - * @param {SplitChunksSizes} minSize the min sizes - * @returns {boolean} true if there are sizes and all existing sizes are at least `minSize` - */ -const checkMinSize = (sizes, minSize) => { - for (const key of Object.keys(minSize)) { - const size = sizes[key]; - if (size === undefined || size === 0) continue; - if (size < minSize[key]) return false; - } - return true; -}; - -/** - * @param {SplitChunksSizes} sizes the sizes - * @param {SplitChunksSizes} minSizeReduction the min sizes - * @param {number} chunkCount number of chunks - * @returns {boolean} true if there are sizes and all existing sizes are at least `minSizeReduction` - */ -const checkMinSizeReduction = (sizes, minSizeReduction, chunkCount) => { - for (const key of Object.keys(minSizeReduction)) { - const size = sizes[key]; - if (size === undefined || size === 0) continue; - if (size * chunkCount < minSizeReduction[key]) return false; - } - return true; -}; - -/** - * @param {SplitChunksSizes} sizes the sizes - * @param {SplitChunksSizes} minSize the min sizes - * @returns {undefined | string[]} list of size types that are below min size - */ -const getViolatingMinSizes = (sizes, minSize) => { - let list; - for (const key of Object.keys(minSize)) { - const size = sizes[key]; - if (size === undefined || size === 0) continue; - if (size < minSize[key]) { - if (list === undefined) list = [key]; - else list.push(key); - } - } - return list; -}; - -/** - * @param {SplitChunksSizes} sizes the sizes - * @returns {number} the total size - */ -const totalSize = sizes => { - let size = 0; - for (const key of Object.keys(sizes)) { - size += sizes[key]; - } - return size; -}; - -/** - * @param {false|string|Function|undefined} name the chunk name - * @returns {GetName | undefined} a function to get the name of the chunk - */ -const normalizeName = name => { - if (typeof name === "string") { - return () => name; - } - if (typeof name === "function") { - return /** @type {GetName} */ (name); - } -}; - -/** - * @param {OptimizationSplitChunksCacheGroup["chunks"]} chunks the chunk filter option - * @returns {ChunkFilterFunction} the chunk filter function - */ -const normalizeChunksFilter = chunks => { - if (chunks === "initial") { - return INITIAL_CHUNK_FILTER; - } - if (chunks === "async") { - return ASYNC_CHUNK_FILTER; - } - if (chunks === "all") { - return ALL_CHUNK_FILTER; - } - if (chunks instanceof RegExp) { - return chunk => (chunk.name ? chunks.test(chunk.name) : false); - } - if (typeof chunks === "function") { - return chunks; - } -}; - -/** - * @param {GetCacheGroups | Record} cacheGroups the cache group options - * @param {string[]} defaultSizeTypes the default size types - * @returns {GetCacheGroups} a function to get the cache groups - */ -const normalizeCacheGroups = (cacheGroups, defaultSizeTypes) => { - if (typeof cacheGroups === "function") { - return cacheGroups; - } - if (typeof cacheGroups === "object" && cacheGroups !== null) { - /** @type {(function(Module, CacheGroupsContext, CacheGroupSource[]): void)[]} */ - const handlers = []; - for (const key of Object.keys(cacheGroups)) { - const option = cacheGroups[key]; - if (option === false) { - continue; - } - if (typeof option === "string" || option instanceof RegExp) { - const source = createCacheGroupSource({}, key, defaultSizeTypes); - handlers.push((module, context, results) => { - if (checkTest(option, module, context)) { - results.push(source); - } - }); - } else if (typeof option === "function") { - const cache = new WeakMap(); - handlers.push((module, context, results) => { - const result = option(module); - if (result) { - const groups = Array.isArray(result) ? result : [result]; - for (const group of groups) { - const cachedSource = cache.get(group); - if (cachedSource !== undefined) { - results.push(cachedSource); - } else { - const source = createCacheGroupSource( - group, - key, - defaultSizeTypes - ); - cache.set(group, source); - results.push(source); - } - } - } - }); - } else { - const source = createCacheGroupSource(option, key, defaultSizeTypes); - handlers.push((module, context, results) => { - if ( - checkTest(option.test, module, context) && - checkModuleType(option.type, module) && - checkModuleLayer(option.layer, module) - ) { - results.push(source); - } - }); - } - } - /** - * @param {Module} module the current module - * @param {CacheGroupsContext} context the current context - * @returns {CacheGroupSource[]} the matching cache groups - */ - const fn = (module, context) => { - /** @type {CacheGroupSource[]} */ - const results = []; - for (const fn of handlers) { - fn(module, context, results); - } - return results; - }; - return fn; - } - return () => null; -}; - -/** - * @param {undefined|boolean|string|RegExp|Function} test test option - * @param {Module} module the module - * @param {CacheGroupsContext} context context object - * @returns {boolean} true, if the module should be selected - */ -const checkTest = (test, module, context) => { - if (test === undefined) return true; - if (typeof test === "function") { - return test(module, context); - } - if (typeof test === "boolean") return test; - if (typeof test === "string") { - const name = module.nameForCondition(); - return name && name.startsWith(test); - } - if (test instanceof RegExp) { - const name = module.nameForCondition(); - return name && test.test(name); - } - return false; -}; - -/** - * @param {undefined|string|RegExp|Function} test type option - * @param {Module} module the module - * @returns {boolean} true, if the module should be selected - */ -const checkModuleType = (test, module) => { - if (test === undefined) return true; - if (typeof test === "function") { - return test(module.type); - } - if (typeof test === "string") { - const type = module.type; - return test === type; - } - if (test instanceof RegExp) { - const type = module.type; - return test.test(type); - } - return false; -}; - -/** - * @param {undefined|string|RegExp|Function} test type option - * @param {Module} module the module - * @returns {boolean} true, if the module should be selected - */ -const checkModuleLayer = (test, module) => { - if (test === undefined) return true; - if (typeof test === "function") { - return test(module.layer); - } - if (typeof test === "string") { - const layer = module.layer; - return test === "" ? !layer : layer && layer.startsWith(test); - } - if (test instanceof RegExp) { - const layer = module.layer; - return test.test(layer); - } - return false; -}; - -/** - * @param {OptimizationSplitChunksCacheGroup} options the group options - * @param {string} key key of cache group - * @param {string[]} defaultSizeTypes the default size types - * @returns {CacheGroupSource} the normalized cached group - */ -const createCacheGroupSource = (options, key, defaultSizeTypes) => { - const minSize = normalizeSizes(options.minSize, defaultSizeTypes); - const minSizeReduction = normalizeSizes( - options.minSizeReduction, - defaultSizeTypes - ); - const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes); - return { - key, - priority: options.priority, - getName: normalizeName(options.name), - chunksFilter: normalizeChunksFilter(options.chunks), - enforce: options.enforce, - minSize, - minSizeReduction, - minRemainingSize: mergeSizes( - normalizeSizes(options.minRemainingSize, defaultSizeTypes), - minSize - ), - enforceSizeThreshold: normalizeSizes( - options.enforceSizeThreshold, - defaultSizeTypes - ), - maxAsyncSize: mergeSizes( - normalizeSizes(options.maxAsyncSize, defaultSizeTypes), - maxSize - ), - maxInitialSize: mergeSizes( - normalizeSizes(options.maxInitialSize, defaultSizeTypes), - maxSize - ), - minChunks: options.minChunks, - maxAsyncRequests: options.maxAsyncRequests, - maxInitialRequests: options.maxInitialRequests, - filename: options.filename, - idHint: options.idHint, - automaticNameDelimiter: options.automaticNameDelimiter, - reuseExistingChunk: options.reuseExistingChunk, - usedExports: options.usedExports - }; -}; - -module.exports = class SplitChunksPlugin { - /** - * @param {OptimizationSplitChunksOptions=} options plugin options - */ - constructor(options = {}) { - const defaultSizeTypes = options.defaultSizeTypes || [ - "javascript", - "unknown" - ]; - const fallbackCacheGroup = options.fallbackCacheGroup || {}; - const minSize = normalizeSizes(options.minSize, defaultSizeTypes); - const minSizeReduction = normalizeSizes( - options.minSizeReduction, - defaultSizeTypes - ); - const maxSize = normalizeSizes(options.maxSize, defaultSizeTypes); - - /** @type {SplitChunksOptions} */ - this.options = { - chunksFilter: normalizeChunksFilter(options.chunks || "all"), - defaultSizeTypes, - minSize, - minSizeReduction, - minRemainingSize: mergeSizes( - normalizeSizes(options.minRemainingSize, defaultSizeTypes), - minSize - ), - enforceSizeThreshold: normalizeSizes( - options.enforceSizeThreshold, - defaultSizeTypes - ), - maxAsyncSize: mergeSizes( - normalizeSizes(options.maxAsyncSize, defaultSizeTypes), - maxSize - ), - maxInitialSize: mergeSizes( - normalizeSizes(options.maxInitialSize, defaultSizeTypes), - maxSize - ), - minChunks: options.minChunks || 1, - maxAsyncRequests: options.maxAsyncRequests || 1, - maxInitialRequests: options.maxInitialRequests || 1, - hidePathInfo: options.hidePathInfo || false, - filename: options.filename || undefined, - getCacheGroups: normalizeCacheGroups( - options.cacheGroups, - defaultSizeTypes - ), - getName: options.name ? normalizeName(options.name) : defaultGetName, - automaticNameDelimiter: options.automaticNameDelimiter, - usedExports: options.usedExports, - fallbackCacheGroup: { - chunksFilter: normalizeChunksFilter( - fallbackCacheGroup.chunks || options.chunks || "all" - ), - minSize: mergeSizes( - normalizeSizes(fallbackCacheGroup.minSize, defaultSizeTypes), - minSize - ), - maxAsyncSize: mergeSizes( - normalizeSizes(fallbackCacheGroup.maxAsyncSize, defaultSizeTypes), - normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes), - normalizeSizes(options.maxAsyncSize, defaultSizeTypes), - normalizeSizes(options.maxSize, defaultSizeTypes) - ), - maxInitialSize: mergeSizes( - normalizeSizes(fallbackCacheGroup.maxInitialSize, defaultSizeTypes), - normalizeSizes(fallbackCacheGroup.maxSize, defaultSizeTypes), - normalizeSizes(options.maxInitialSize, defaultSizeTypes), - normalizeSizes(options.maxSize, defaultSizeTypes) - ), - automaticNameDelimiter: - fallbackCacheGroup.automaticNameDelimiter || - options.automaticNameDelimiter || - "~" - } - }; - - /** @type {WeakMap} */ - this._cacheGroupCache = new WeakMap(); - } - - /** - * @param {CacheGroupSource} cacheGroupSource source - * @returns {CacheGroup} the cache group (cached) - */ - _getCacheGroup(cacheGroupSource) { - const cacheEntry = this._cacheGroupCache.get(cacheGroupSource); - if (cacheEntry !== undefined) return cacheEntry; - const minSize = mergeSizes( - cacheGroupSource.minSize, - cacheGroupSource.enforce ? undefined : this.options.minSize - ); - const minSizeReduction = mergeSizes( - cacheGroupSource.minSizeReduction, - cacheGroupSource.enforce ? undefined : this.options.minSizeReduction - ); - const minRemainingSize = mergeSizes( - cacheGroupSource.minRemainingSize, - cacheGroupSource.enforce ? undefined : this.options.minRemainingSize - ); - const enforceSizeThreshold = mergeSizes( - cacheGroupSource.enforceSizeThreshold, - cacheGroupSource.enforce ? undefined : this.options.enforceSizeThreshold - ); - const cacheGroup = { - key: cacheGroupSource.key, - priority: cacheGroupSource.priority || 0, - chunksFilter: cacheGroupSource.chunksFilter || this.options.chunksFilter, - minSize, - minSizeReduction, - minRemainingSize, - enforceSizeThreshold, - maxAsyncSize: mergeSizes( - cacheGroupSource.maxAsyncSize, - cacheGroupSource.enforce ? undefined : this.options.maxAsyncSize - ), - maxInitialSize: mergeSizes( - cacheGroupSource.maxInitialSize, - cacheGroupSource.enforce ? undefined : this.options.maxInitialSize - ), - minChunks: - cacheGroupSource.minChunks !== undefined - ? cacheGroupSource.minChunks - : cacheGroupSource.enforce - ? 1 - : this.options.minChunks, - maxAsyncRequests: - cacheGroupSource.maxAsyncRequests !== undefined - ? cacheGroupSource.maxAsyncRequests - : cacheGroupSource.enforce - ? Infinity - : this.options.maxAsyncRequests, - maxInitialRequests: - cacheGroupSource.maxInitialRequests !== undefined - ? cacheGroupSource.maxInitialRequests - : cacheGroupSource.enforce - ? Infinity - : this.options.maxInitialRequests, - getName: - cacheGroupSource.getName !== undefined - ? cacheGroupSource.getName - : this.options.getName, - usedExports: - cacheGroupSource.usedExports !== undefined - ? cacheGroupSource.usedExports - : this.options.usedExports, - filename: - cacheGroupSource.filename !== undefined - ? cacheGroupSource.filename - : this.options.filename, - automaticNameDelimiter: - cacheGroupSource.automaticNameDelimiter !== undefined - ? cacheGroupSource.automaticNameDelimiter - : this.options.automaticNameDelimiter, - idHint: - cacheGroupSource.idHint !== undefined - ? cacheGroupSource.idHint - : cacheGroupSource.key, - reuseExistingChunk: cacheGroupSource.reuseExistingChunk || false, - _validateSize: hasNonZeroSizes(minSize), - _validateRemainingSize: hasNonZeroSizes(minRemainingSize), - _minSizeForMaxSize: mergeSizes( - cacheGroupSource.minSize, - this.options.minSize - ), - _conditionalEnforce: hasNonZeroSizes(enforceSizeThreshold) - }; - this._cacheGroupCache.set(cacheGroupSource, cacheGroup); - return cacheGroup; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const cachedMakePathsRelative = makePathsRelative.bindContextCache( - compiler.context, - compiler.root - ); - compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => { - const logger = compilation.getLogger("webpack.SplitChunksPlugin"); - let alreadyOptimized = false; - compilation.hooks.unseal.tap("SplitChunksPlugin", () => { - alreadyOptimized = false; - }); - compilation.hooks.optimizeChunks.tap( - { - name: "SplitChunksPlugin", - stage: STAGE_ADVANCED - }, - chunks => { - if (alreadyOptimized) return; - alreadyOptimized = true; - logger.time("prepare"); - const chunkGraph = compilation.chunkGraph; - const moduleGraph = compilation.moduleGraph; - // Give each selected chunk an index (to create strings from chunks) - /** @type {Map} */ - const chunkIndexMap = new Map(); - const ZERO = BigInt("0"); - const ONE = BigInt("1"); - const START = ONE << BigInt("31"); - let index = START; - for (const chunk of chunks) { - chunkIndexMap.set( - chunk, - index | BigInt((Math.random() * 0x7fffffff) | 0) - ); - index = index << ONE; - } - /** - * @param {Iterable} chunks list of chunks - * @returns {bigint | Chunk} key of the chunks - */ - const getKey = chunks => { - const iterator = chunks[Symbol.iterator](); - let result = iterator.next(); - if (result.done) return ZERO; - const first = result.value; - result = iterator.next(); - if (result.done) return first; - let key = - chunkIndexMap.get(first) | chunkIndexMap.get(result.value); - while (!(result = iterator.next()).done) { - const raw = chunkIndexMap.get(result.value); - key = key ^ raw; - } - return key; - }; - /** - * @param {bigint | Chunk} key key of the chunks - * @returns {string} stringified key - */ - const keyToString = key => { - if (typeof key === "bigint") return key.toString(16); - return chunkIndexMap.get(key).toString(16); - }; - - const getChunkSetsInGraph = memoize(() => { - /** @type {Map>} */ - const chunkSetsInGraph = new Map(); - /** @type {Set} */ - const singleChunkSets = new Set(); - for (const module of compilation.modules) { - const chunks = chunkGraph.getModuleChunksIterable(module); - const chunksKey = getKey(chunks); - if (typeof chunksKey === "bigint") { - if (!chunkSetsInGraph.has(chunksKey)) { - chunkSetsInGraph.set(chunksKey, new Set(chunks)); - } - } else { - singleChunkSets.add(chunksKey); - } - } - return { chunkSetsInGraph, singleChunkSets }; - }); - - /** - * @param {Module} module the module - * @returns {Iterable} groups of chunks with equal exports - */ - const groupChunksByExports = module => { - const exportsInfo = moduleGraph.getExportsInfo(module); - const groupedByUsedExports = new Map(); - for (const chunk of chunkGraph.getModuleChunksIterable(module)) { - const key = exportsInfo.getUsageKey(chunk.runtime); - const list = groupedByUsedExports.get(key); - if (list !== undefined) { - list.push(chunk); - } else { - groupedByUsedExports.set(key, [chunk]); - } - } - return groupedByUsedExports.values(); - }; - - /** @type {Map>} */ - const groupedByExportsMap = new Map(); - - const getExportsChunkSetsInGraph = memoize(() => { - /** @type {Map>} */ - const chunkSetsInGraph = new Map(); - /** @type {Set} */ - const singleChunkSets = new Set(); - for (const module of compilation.modules) { - const groupedChunks = Array.from(groupChunksByExports(module)); - groupedByExportsMap.set(module, groupedChunks); - for (const chunks of groupedChunks) { - if (chunks.length === 1) { - singleChunkSets.add(chunks[0]); - } else { - const chunksKey = /** @type {bigint} */ (getKey(chunks)); - if (!chunkSetsInGraph.has(chunksKey)) { - chunkSetsInGraph.set(chunksKey, new Set(chunks)); - } - } - } - } - return { chunkSetsInGraph, singleChunkSets }; - }); - - // group these set of chunks by count - // to allow to check less sets via isSubset - // (only smaller sets can be subset) - /** - * @param {IterableIterator>} chunkSets set of sets of chunks - * @returns {Map>>} map of sets of chunks by count - */ - const groupChunkSetsByCount = chunkSets => { - /** @type {Map>>} */ - const chunkSetsByCount = new Map(); - for (const chunksSet of chunkSets) { - const count = chunksSet.size; - let array = chunkSetsByCount.get(count); - if (array === undefined) { - array = []; - chunkSetsByCount.set(count, array); - } - array.push(chunksSet); - } - return chunkSetsByCount; - }; - const getChunkSetsByCount = memoize(() => - groupChunkSetsByCount( - getChunkSetsInGraph().chunkSetsInGraph.values() - ) - ); - const getExportsChunkSetsByCount = memoize(() => - groupChunkSetsByCount( - getExportsChunkSetsInGraph().chunkSetsInGraph.values() - ) - ); - - // Create a list of possible combinations - const createGetCombinations = ( - chunkSets, - singleChunkSets, - chunkSetsByCount - ) => { - /** @type {Map | Chunk)[]>} */ - const combinationsCache = new Map(); - - return key => { - const cacheEntry = combinationsCache.get(key); - if (cacheEntry !== undefined) return cacheEntry; - if (key instanceof Chunk) { - const result = [key]; - combinationsCache.set(key, result); - return result; - } - const chunksSet = chunkSets.get(key); - /** @type {(Set | Chunk)[]} */ - const array = [chunksSet]; - for (const [count, setArray] of chunkSetsByCount) { - // "equal" is not needed because they would have been merge in the first step - if (count < chunksSet.size) { - for (const set of setArray) { - if (isSubset(chunksSet, set)) { - array.push(set); - } - } - } - } - for (const chunk of singleChunkSets) { - if (chunksSet.has(chunk)) { - array.push(chunk); - } - } - combinationsCache.set(key, array); - return array; - }; - }; - - const getCombinationsFactory = memoize(() => { - const { chunkSetsInGraph, singleChunkSets } = getChunkSetsInGraph(); - return createGetCombinations( - chunkSetsInGraph, - singleChunkSets, - getChunkSetsByCount() - ); - }); - const getCombinations = key => getCombinationsFactory()(key); - - const getExportsCombinationsFactory = memoize(() => { - const { chunkSetsInGraph, singleChunkSets } = - getExportsChunkSetsInGraph(); - return createGetCombinations( - chunkSetsInGraph, - singleChunkSets, - getExportsChunkSetsByCount() - ); - }); - const getExportsCombinations = key => - getExportsCombinationsFactory()(key); - - /** - * @typedef {object} SelectedChunksResult - * @property {Chunk[]} chunks the list of chunks - * @property {bigint | Chunk} key a key of the list - */ - - /** @type {WeakMap | Chunk, WeakMap>} */ - const selectedChunksCacheByChunksSet = new WeakMap(); - - /** - * get list and key by applying the filter function to the list - * It is cached for performance reasons - * @param {Set | Chunk} chunks list of chunks - * @param {ChunkFilterFunction} chunkFilter filter function for chunks - * @returns {SelectedChunksResult} list and key - */ - const getSelectedChunks = (chunks, chunkFilter) => { - let entry = selectedChunksCacheByChunksSet.get(chunks); - if (entry === undefined) { - entry = new WeakMap(); - selectedChunksCacheByChunksSet.set(chunks, entry); - } - let entry2 = - /** @type {SelectedChunksResult} */ - (entry.get(chunkFilter)); - if (entry2 === undefined) { - /** @type {Chunk[]} */ - const selectedChunks = []; - if (chunks instanceof Chunk) { - if (chunkFilter(chunks)) selectedChunks.push(chunks); - } else { - for (const chunk of chunks) { - if (chunkFilter(chunk)) selectedChunks.push(chunk); - } - } - entry2 = { - chunks: selectedChunks, - key: getKey(selectedChunks) - }; - entry.set(chunkFilter, entry2); - } - return entry2; - }; - - /** @type {Map} */ - const alreadyValidatedParents = new Map(); - /** @type {Set} */ - const alreadyReportedErrors = new Set(); - - // Map a list of chunks to a list of modules - // For the key the chunk "index" is used, the value is a SortableSet of modules - /** @type {Map} */ - const chunksInfoMap = new Map(); - - /** - * @param {CacheGroup} cacheGroup the current cache group - * @param {number} cacheGroupIndex the index of the cache group of ordering - * @param {Chunk[]} selectedChunks chunks selected for this module - * @param {bigint | Chunk} selectedChunksKey a key of selectedChunks - * @param {Module} module the current module - * @returns {void} - */ - const addModuleToChunksInfoMap = ( - cacheGroup, - cacheGroupIndex, - selectedChunks, - selectedChunksKey, - module - ) => { - // Break if minimum number of chunks is not reached - if (selectedChunks.length < cacheGroup.minChunks) return; - // Determine name for split chunk - const name = - /** @type {string} */ - (cacheGroup.getName(module, selectedChunks, cacheGroup.key)); - // Check if the name is ok - const existingChunk = compilation.namedChunks.get(name); - if (existingChunk) { - const parentValidationKey = `${name}|${ - typeof selectedChunksKey === "bigint" - ? selectedChunksKey - : selectedChunksKey.debugId - }`; - const valid = alreadyValidatedParents.get(parentValidationKey); - if (valid === false) return; - if (valid === undefined) { - // Module can only be moved into the existing chunk if the existing chunk - // is a parent of all selected chunks - let isInAllParents = true; - /** @type {Set} */ - const queue = new Set(); - for (const chunk of selectedChunks) { - for (const group of chunk.groupsIterable) { - queue.add(group); - } - } - for (const group of queue) { - if (existingChunk.isInGroup(group)) continue; - let hasParent = false; - for (const parent of group.parentsIterable) { - hasParent = true; - queue.add(parent); - } - if (!hasParent) { - isInAllParents = false; - } - } - const valid = isInAllParents; - alreadyValidatedParents.set(parentValidationKey, valid); - if (!valid) { - if (!alreadyReportedErrors.has(name)) { - alreadyReportedErrors.add(name); - compilation.errors.push( - new WebpackError( - "SplitChunksPlugin\n" + - `Cache group "${cacheGroup.key}" conflicts with existing chunk.\n` + - `Both have the same name "${name}" and existing chunk is not a parent of the selected modules.\n` + - "Use a different name for the cache group or make sure that the existing chunk is a parent (e. g. via dependOn).\n" + - 'HINT: You can omit "name" to automatically create a name.\n' + - "BREAKING CHANGE: webpack < 5 used to allow to use an entrypoint as splitChunk. " + - "This is no longer allowed when the entrypoint is not a parent of the selected modules.\n" + - "Remove this entrypoint and add modules to cache group's 'test' instead. " + - "If you need modules to be evaluated on startup, add them to the existing entrypoints (make them arrays). " + - "See migration guide of more info." - ) - ); - } - return; - } - } - } - // Create key for maps - // When it has a name we use the name as key - // Otherwise we create the key from chunks and cache group key - // This automatically merges equal names - const key = - cacheGroup.key + - (name - ? ` name:${name}` - : ` chunks:${keyToString(selectedChunksKey)}`); - // Add module to maps - let info = /** @type {ChunksInfoItem} */ (chunksInfoMap.get(key)); - if (info === undefined) { - chunksInfoMap.set( - key, - (info = { - modules: new SortableSet( - undefined, - compareModulesByIdentifier - ), - cacheGroup, - cacheGroupIndex, - name, - sizes: {}, - chunks: new Set(), - reusableChunks: new Set(), - chunksKeys: new Set() - }) - ); - } - const oldSize = info.modules.size; - info.modules.add(module); - if (info.modules.size !== oldSize) { - for (const type of module.getSourceTypes()) { - info.sizes[type] = (info.sizes[type] || 0) + module.size(type); - } - } - const oldChunksKeysSize = info.chunksKeys.size; - info.chunksKeys.add(selectedChunksKey); - if (oldChunksKeysSize !== info.chunksKeys.size) { - for (const chunk of selectedChunks) { - info.chunks.add(chunk); - } - } - }; - - const context = { - moduleGraph, - chunkGraph - }; - - logger.timeEnd("prepare"); - - logger.time("modules"); - - // Walk through all modules - for (const module of compilation.modules) { - // Get cache group - const cacheGroups = this.options.getCacheGroups(module, context); - if (!Array.isArray(cacheGroups) || cacheGroups.length === 0) { - continue; - } - - // Prepare some values (usedExports = false) - const getCombs = memoize(() => { - const chunks = chunkGraph.getModuleChunksIterable(module); - const chunksKey = getKey(chunks); - return getCombinations(chunksKey); - }); - - // Prepare some values (usedExports = true) - const getCombsByUsedExports = memoize(() => { - // fill the groupedByExportsMap - getExportsChunkSetsInGraph(); - /** @type {Set | Chunk>} */ - const set = new Set(); - const groupedByUsedExports = - /** @type {Iterable} */ - (groupedByExportsMap.get(module)); - for (const chunks of groupedByUsedExports) { - const chunksKey = getKey(chunks); - for (const comb of getExportsCombinations(chunksKey)) - set.add(comb); - } - return set; - }); - - let cacheGroupIndex = 0; - for (const cacheGroupSource of cacheGroups) { - const cacheGroup = this._getCacheGroup(cacheGroupSource); - - const combs = cacheGroup.usedExports - ? getCombsByUsedExports() - : getCombs(); - // For all combination of chunk selection - for (const chunkCombination of combs) { - // Break if minimum number of chunks is not reached - const count = - chunkCombination instanceof Chunk ? 1 : chunkCombination.size; - if (count < cacheGroup.minChunks) continue; - // Select chunks by configuration - const { chunks: selectedChunks, key: selectedChunksKey } = - getSelectedChunks( - chunkCombination, - /** @type {ChunkFilterFunction} */ (cacheGroup.chunksFilter) - ); - - addModuleToChunksInfoMap( - cacheGroup, - cacheGroupIndex, - selectedChunks, - selectedChunksKey, - module - ); - } - cacheGroupIndex++; - } - } - - logger.timeEnd("modules"); - - logger.time("queue"); - - /** - * @param {ChunksInfoItem} info entry - * @param {string[]} sourceTypes source types to be removed - */ - const removeModulesWithSourceType = (info, sourceTypes) => { - for (const module of info.modules) { - const types = module.getSourceTypes(); - if (sourceTypes.some(type => types.has(type))) { - info.modules.delete(module); - for (const type of types) { - info.sizes[type] -= module.size(type); - } - } - } - }; - - /** - * @param {ChunksInfoItem} info entry - * @returns {boolean} true, if entry become empty - */ - const removeMinSizeViolatingModules = info => { - if (!info.cacheGroup._validateSize) return false; - const violatingSizes = getViolatingMinSizes( - info.sizes, - info.cacheGroup.minSize - ); - if (violatingSizes === undefined) return false; - removeModulesWithSourceType(info, violatingSizes); - return info.modules.size === 0; - }; - - // Filter items were size < minSize - for (const [key, info] of chunksInfoMap) { - if (removeMinSizeViolatingModules(info)) { - chunksInfoMap.delete(key); - } else if ( - !checkMinSizeReduction( - info.sizes, - info.cacheGroup.minSizeReduction, - info.chunks.size - ) - ) { - chunksInfoMap.delete(key); - } - } - - /** - * @typedef {object} MaxSizeQueueItem - * @property {SplitChunksSizes} minSize - * @property {SplitChunksSizes} maxAsyncSize - * @property {SplitChunksSizes} maxInitialSize - * @property {string} automaticNameDelimiter - * @property {string[]} keys - */ - - /** @type {Map} */ - const maxSizeQueueMap = new Map(); - - while (chunksInfoMap.size > 0) { - // Find best matching entry - let bestEntryKey; - let bestEntry; - for (const pair of chunksInfoMap) { - const key = pair[0]; - const info = pair[1]; - if ( - bestEntry === undefined || - compareEntries(bestEntry, info) < 0 - ) { - bestEntry = info; - bestEntryKey = key; - } - } - - const item = /** @type {ChunksInfoItem} */ (bestEntry); - chunksInfoMap.delete(/** @type {string} */ (bestEntryKey)); - - /** @type {Chunk["name"] | undefined} */ - let chunkName = item.name; - // Variable for the new chunk (lazy created) - /** @type {Chunk | undefined} */ - let newChunk; - // When no chunk name, check if we can reuse a chunk instead of creating a new one - let isExistingChunk = false; - let isReusedWithAllModules = false; - if (chunkName) { - const chunkByName = compilation.namedChunks.get(chunkName); - if (chunkByName !== undefined) { - newChunk = chunkByName; - const oldSize = item.chunks.size; - item.chunks.delete(newChunk); - isExistingChunk = item.chunks.size !== oldSize; - } - } else if (item.cacheGroup.reuseExistingChunk) { - outer: for (const chunk of item.chunks) { - if ( - chunkGraph.getNumberOfChunkModules(chunk) !== - item.modules.size - ) { - continue; - } - if ( - item.chunks.size > 1 && - chunkGraph.getNumberOfEntryModules(chunk) > 0 - ) { - continue; - } - for (const module of item.modules) { - if (!chunkGraph.isModuleInChunk(module, chunk)) { - continue outer; - } - } - if (!newChunk || !newChunk.name) { - newChunk = chunk; - } else if ( - chunk.name && - chunk.name.length < newChunk.name.length - ) { - newChunk = chunk; - } else if ( - chunk.name && - chunk.name.length === newChunk.name.length && - chunk.name < newChunk.name - ) { - newChunk = chunk; - } - } - if (newChunk) { - item.chunks.delete(newChunk); - chunkName = undefined; - isExistingChunk = true; - isReusedWithAllModules = true; - } - } - - const enforced = - item.cacheGroup._conditionalEnforce && - checkMinSize(item.sizes, item.cacheGroup.enforceSizeThreshold); - - const usedChunks = new Set(item.chunks); - - // Check if maxRequests condition can be fulfilled - if ( - !enforced && - (Number.isFinite(item.cacheGroup.maxInitialRequests) || - Number.isFinite(item.cacheGroup.maxAsyncRequests)) - ) { - for (const chunk of usedChunks) { - // respect max requests - const maxRequests = /** @type {number} */ ( - chunk.isOnlyInitial() - ? item.cacheGroup.maxInitialRequests - : chunk.canBeInitial() - ? Math.min( - /** @type {number} */ - (item.cacheGroup.maxInitialRequests), - /** @type {number} */ - (item.cacheGroup.maxAsyncRequests) - ) - : item.cacheGroup.maxAsyncRequests - ); - if ( - Number.isFinite(maxRequests) && - getRequests(chunk) >= maxRequests - ) { - usedChunks.delete(chunk); - } - } - } - - outer: for (const chunk of usedChunks) { - for (const module of item.modules) { - if (chunkGraph.isModuleInChunk(module, chunk)) continue outer; - } - usedChunks.delete(chunk); - } - - // Were some (invalid) chunks removed from usedChunks? - // => readd all modules to the queue, as things could have been changed - if (usedChunks.size < item.chunks.size) { - if (isExistingChunk) - usedChunks.add(/** @type {Chunk} */ (newChunk)); - if ( - /** @type {number} */ (usedChunks.size) >= - /** @type {number} */ (item.cacheGroup.minChunks) - ) { - const chunksArr = Array.from(usedChunks); - for (const module of item.modules) { - addModuleToChunksInfoMap( - item.cacheGroup, - item.cacheGroupIndex, - chunksArr, - getKey(usedChunks), - module - ); - } - } - continue; - } - - // Validate minRemainingSize constraint when a single chunk is left over - if ( - !enforced && - item.cacheGroup._validateRemainingSize && - usedChunks.size === 1 - ) { - const [chunk] = usedChunks; - const chunkSizes = Object.create(null); - for (const module of chunkGraph.getChunkModulesIterable(chunk)) { - if (!item.modules.has(module)) { - for (const type of module.getSourceTypes()) { - chunkSizes[type] = - (chunkSizes[type] || 0) + module.size(type); - } - } - } - const violatingSizes = getViolatingMinSizes( - chunkSizes, - item.cacheGroup.minRemainingSize - ); - if (violatingSizes !== undefined) { - const oldModulesSize = item.modules.size; - removeModulesWithSourceType(item, violatingSizes); - if ( - item.modules.size > 0 && - item.modules.size !== oldModulesSize - ) { - // queue this item again to be processed again - // without violating modules - chunksInfoMap.set(/** @type {string} */ (bestEntryKey), item); - } - continue; - } - } - - // Create the new chunk if not reusing one - if (newChunk === undefined) { - newChunk = compilation.addChunk(chunkName); - } - // Walk through all chunks - for (const chunk of usedChunks) { - // Add graph connections for splitted chunk - chunk.split(newChunk); - } - - // Add a note to the chunk - newChunk.chunkReason = - (newChunk.chunkReason ? `${newChunk.chunkReason}, ` : "") + - (isReusedWithAllModules - ? "reused as split chunk" - : "split chunk"); - if (item.cacheGroup.key) { - newChunk.chunkReason += ` (cache group: ${item.cacheGroup.key})`; - } - if (chunkName) { - newChunk.chunkReason += ` (name: ${chunkName})`; - } - if (item.cacheGroup.filename) { - newChunk.filenameTemplate = item.cacheGroup.filename; - } - if (item.cacheGroup.idHint) { - newChunk.idNameHints.add(item.cacheGroup.idHint); - } - if (!isReusedWithAllModules) { - // Add all modules to the new chunk - for (const module of item.modules) { - if (!module.chunkCondition(newChunk, compilation)) continue; - // Add module to new chunk - chunkGraph.connectChunkAndModule(newChunk, module); - // Remove module from used chunks - for (const chunk of usedChunks) { - chunkGraph.disconnectChunkAndModule(chunk, module); - } - } - } else { - // Remove all modules from used chunks - for (const module of item.modules) { - for (const chunk of usedChunks) { - chunkGraph.disconnectChunkAndModule(chunk, module); - } - } - } - - if ( - Object.keys(item.cacheGroup.maxAsyncSize).length > 0 || - Object.keys(item.cacheGroup.maxInitialSize).length > 0 - ) { - const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk); - maxSizeQueueMap.set(newChunk, { - minSize: oldMaxSizeSettings - ? combineSizes( - oldMaxSizeSettings.minSize, - item.cacheGroup._minSizeForMaxSize, - Math.max - ) - : item.cacheGroup.minSize, - maxAsyncSize: oldMaxSizeSettings - ? combineSizes( - oldMaxSizeSettings.maxAsyncSize, - item.cacheGroup.maxAsyncSize, - Math.min - ) - : item.cacheGroup.maxAsyncSize, - maxInitialSize: oldMaxSizeSettings - ? combineSizes( - oldMaxSizeSettings.maxInitialSize, - item.cacheGroup.maxInitialSize, - Math.min - ) - : item.cacheGroup.maxInitialSize, - automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter, - keys: oldMaxSizeSettings - ? oldMaxSizeSettings.keys.concat(item.cacheGroup.key) - : [item.cacheGroup.key] - }); - } - - // remove all modules from other entries and update size - for (const [key, info] of chunksInfoMap) { - if (isOverlap(info.chunks, usedChunks)) { - // update modules and total size - // may remove it from the map when < minSize - let updated = false; - for (const module of item.modules) { - if (info.modules.has(module)) { - // remove module - info.modules.delete(module); - // update size - for (const key of module.getSourceTypes()) { - info.sizes[key] -= module.size(key); - } - updated = true; - } - } - if (updated) { - if (info.modules.size === 0) { - chunksInfoMap.delete(key); - continue; - } - if ( - removeMinSizeViolatingModules(info) || - !checkMinSizeReduction( - info.sizes, - info.cacheGroup.minSizeReduction, - info.chunks.size - ) - ) { - chunksInfoMap.delete(key); - continue; - } - } - } - } - } - - logger.timeEnd("queue"); - - logger.time("maxSize"); - - /** @type {Set} */ - const incorrectMinMaxSizeSet = new Set(); - - const { outputOptions } = compilation; - - // Make sure that maxSize is fulfilled - const { fallbackCacheGroup } = this.options; - for (const chunk of Array.from(compilation.chunks)) { - const chunkConfig = maxSizeQueueMap.get(chunk); - const { - minSize, - maxAsyncSize, - maxInitialSize, - automaticNameDelimiter - } = chunkConfig || fallbackCacheGroup; - if (!chunkConfig && !fallbackCacheGroup.chunksFilter(chunk)) - continue; - /** @type {SplitChunksSizes} */ - let maxSize; - if (chunk.isOnlyInitial()) { - maxSize = maxInitialSize; - } else if (chunk.canBeInitial()) { - maxSize = combineSizes(maxAsyncSize, maxInitialSize, Math.min); - } else { - maxSize = maxAsyncSize; - } - if (Object.keys(maxSize).length === 0) { - continue; - } - for (const key of Object.keys(maxSize)) { - const maxSizeValue = maxSize[key]; - const minSizeValue = minSize[key]; - if ( - typeof minSizeValue === "number" && - minSizeValue > maxSizeValue - ) { - const keys = chunkConfig && chunkConfig.keys; - const warningKey = `${ - keys && keys.join() - } ${minSizeValue} ${maxSizeValue}`; - if (!incorrectMinMaxSizeSet.has(warningKey)) { - incorrectMinMaxSizeSet.add(warningKey); - compilation.warnings.push( - new MinMaxSizeWarning(keys, minSizeValue, maxSizeValue) - ); - } - } - } - const results = deterministicGroupingForModules({ - minSize, - maxSize: mapObject(maxSize, (value, key) => { - const minSizeValue = minSize[key]; - return typeof minSizeValue === "number" - ? Math.max(value, minSizeValue) - : value; - }), - items: chunkGraph.getChunkModulesIterable(chunk), - getKey(module) { - const cache = getKeyCache.get(module); - if (cache !== undefined) return cache; - const ident = cachedMakePathsRelative(module.identifier()); - const nameForCondition = - module.nameForCondition && module.nameForCondition(); - const name = nameForCondition - ? cachedMakePathsRelative(nameForCondition) - : ident.replace(/^.*!|\?[^?!]*$/g, ""); - const fullKey = - name + - automaticNameDelimiter + - hashFilename(ident, outputOptions); - const key = requestToId(fullKey); - getKeyCache.set(module, key); - return key; - }, - getSize(module) { - const size = Object.create(null); - for (const key of module.getSourceTypes()) { - size[key] = module.size(key); - } - return size; - } - }); - if (results.length <= 1) { - continue; - } - for (let i = 0; i < results.length; i++) { - const group = results[i]; - const key = this.options.hidePathInfo - ? hashFilename(group.key, outputOptions) - : group.key; - let name = chunk.name - ? chunk.name + automaticNameDelimiter + key - : null; - if (name && name.length > 100) { - name = - name.slice(0, 100) + - automaticNameDelimiter + - hashFilename(name, outputOptions); - } - if (i !== results.length - 1) { - const newPart = compilation.addChunk( - /** @type {Chunk["name"]} */ (name) - ); - chunk.split(newPart); - newPart.chunkReason = chunk.chunkReason; - // Add all modules to the new chunk - for (const module of group.items) { - if (!module.chunkCondition(newPart, compilation)) { - continue; - } - // Add module to new chunk - chunkGraph.connectChunkAndModule(newPart, module); - // Remove module from used chunks - chunkGraph.disconnectChunkAndModule(chunk, module); - } - } else { - // change the chunk to be a part - chunk.name = /** @type {Chunk["name"]} */ (name); - } - } - } - logger.timeEnd("maxSize"); - } - ); - }); - } -}; diff --git a/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js b/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js deleted file mode 100644 index 5b414fc0dfd..00000000000 --- a/webpack-lib/lib/performance/AssetsOverSizeLimitWarning.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -const { formatSize } = require("../SizeFormatHelpers"); -const WebpackError = require("../WebpackError"); - -/** @typedef {import("./SizeLimitsPlugin").AssetDetails} AssetDetails */ - -module.exports = class AssetsOverSizeLimitWarning extends WebpackError { - /** - * @param {AssetDetails[]} assetsOverSizeLimit the assets - * @param {number} assetLimit the size limit - */ - constructor(assetsOverSizeLimit, assetLimit) { - const assetLists = assetsOverSizeLimit - .map(asset => `\n ${asset.name} (${formatSize(asset.size)})`) - .join(""); - - super(`asset size limit: The following asset(s) exceed the recommended size limit (${formatSize( - assetLimit - )}). -This can impact web performance. -Assets: ${assetLists}`); - - this.name = "AssetsOverSizeLimitWarning"; - this.assets = assetsOverSizeLimit; - } -}; diff --git a/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js b/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js deleted file mode 100644 index 270e8aaa708..00000000000 --- a/webpack-lib/lib/performance/EntrypointsOverSizeLimitWarning.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -const { formatSize } = require("../SizeFormatHelpers"); -const WebpackError = require("../WebpackError"); - -/** @typedef {import("./SizeLimitsPlugin").EntrypointDetails} EntrypointDetails */ - -module.exports = class EntrypointsOverSizeLimitWarning extends WebpackError { - /** - * @param {EntrypointDetails[]} entrypoints the entrypoints - * @param {number} entrypointLimit the size limit - */ - constructor(entrypoints, entrypointLimit) { - const entrypointList = entrypoints - .map( - entrypoint => - `\n ${entrypoint.name} (${formatSize( - entrypoint.size - )})\n${entrypoint.files.map(asset => ` ${asset}`).join("\n")}` - ) - .join(""); - super(`entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (${formatSize( - entrypointLimit - )}). This can impact web performance. -Entrypoints:${entrypointList}\n`); - - this.name = "EntrypointsOverSizeLimitWarning"; - this.entrypoints = entrypoints; - } -}; diff --git a/webpack-lib/lib/performance/NoAsyncChunksWarning.js b/webpack-lib/lib/performance/NoAsyncChunksWarning.js deleted file mode 100644 index a7319d5950b..00000000000 --- a/webpack-lib/lib/performance/NoAsyncChunksWarning.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); - -module.exports = class NoAsyncChunksWarning extends WebpackError { - constructor() { - super( - "webpack performance recommendations: \n" + - "You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.\n" + - "For more info visit https://webpack.js.org/guides/code-splitting/" - ); - - this.name = "NoAsyncChunksWarning"; - } -}; diff --git a/webpack-lib/lib/performance/SizeLimitsPlugin.js b/webpack-lib/lib/performance/SizeLimitsPlugin.js deleted file mode 100644 index b1371a231fc..00000000000 --- a/webpack-lib/lib/performance/SizeLimitsPlugin.js +++ /dev/null @@ -1,179 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sean Larkin @thelarkinn -*/ - -"use strict"; - -const { find } = require("../util/SetHelpers"); -const AssetsOverSizeLimitWarning = require("./AssetsOverSizeLimitWarning"); -const EntrypointsOverSizeLimitWarning = require("./EntrypointsOverSizeLimitWarning"); -const NoAsyncChunksWarning = require("./NoAsyncChunksWarning"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation").Asset} Asset */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Entrypoint")} Entrypoint */ -/** @typedef {import("../WebpackError")} WebpackError */ - -/** - * @typedef {object} AssetDetails - * @property {string} name - * @property {number} size - */ - -/** - * @typedef {object} EntrypointDetails - * @property {string} name - * @property {number} size - * @property {string[]} files - */ - -const isOverSizeLimitSet = new WeakSet(); - -/** - * @param {Asset["name"]} name the name - * @param {Asset["source"]} source the source - * @param {Asset["info"]} info the info - * @returns {boolean} result - */ -const excludeSourceMap = (name, source, info) => !info.development; - -module.exports = class SizeLimitsPlugin { - /** - * @param {PerformanceOptions} options the plugin options - */ - constructor(options) { - this.hints = options.hints; - this.maxAssetSize = options.maxAssetSize; - this.maxEntrypointSize = options.maxEntrypointSize; - this.assetFilter = options.assetFilter; - } - - /** - * @param {ChunkGroup | Source} thing the resource to test - * @returns {boolean} true if over the limit - */ - static isOverSizeLimit(thing) { - return isOverSizeLimitSet.has(thing); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const entrypointSizeLimit = this.maxEntrypointSize; - const assetSizeLimit = this.maxAssetSize; - const hints = this.hints; - const assetFilter = this.assetFilter || excludeSourceMap; - - compiler.hooks.afterEmit.tap("SizeLimitsPlugin", compilation => { - /** @type {WebpackError[]} */ - const warnings = []; - - /** - * @param {Entrypoint} entrypoint an entrypoint - * @returns {number} the size of the entrypoint - */ - const getEntrypointSize = entrypoint => { - let size = 0; - for (const file of entrypoint.getFiles()) { - const asset = compilation.getAsset(file); - if ( - asset && - assetFilter(asset.name, asset.source, asset.info) && - asset.source - ) { - size += asset.info.size || asset.source.size(); - } - } - return size; - }; - - /** @type {AssetDetails[]} */ - const assetsOverSizeLimit = []; - for (const { name, source, info } of compilation.getAssets()) { - if (!assetFilter(name, source, info) || !source) { - continue; - } - - const size = info.size || source.size(); - if (size > /** @type {number} */ (assetSizeLimit)) { - assetsOverSizeLimit.push({ - name, - size - }); - isOverSizeLimitSet.add(source); - } - } - - /** - * @param {Asset["name"]} name the name - * @returns {boolean | undefined} result - */ - const fileFilter = name => { - const asset = compilation.getAsset(name); - return asset && assetFilter(asset.name, asset.source, asset.info); - }; - - /** @type {EntrypointDetails[]} */ - const entrypointsOverLimit = []; - for (const [name, entry] of compilation.entrypoints) { - const size = getEntrypointSize(entry); - - if (size > /** @type {number} */ (entrypointSizeLimit)) { - entrypointsOverLimit.push({ - name, - size, - files: entry.getFiles().filter(fileFilter) - }); - isOverSizeLimitSet.add(entry); - } - } - - if (hints) { - // 1. Individual Chunk: Size < 250kb - // 2. Collective Initial Chunks [entrypoint] (Each Set?): Size < 250kb - // 3. No Async Chunks - // if !1, then 2, if !2 return - if (assetsOverSizeLimit.length > 0) { - warnings.push( - new AssetsOverSizeLimitWarning( - assetsOverSizeLimit, - /** @type {number} */ (assetSizeLimit) - ) - ); - } - if (entrypointsOverLimit.length > 0) { - warnings.push( - new EntrypointsOverSizeLimitWarning( - entrypointsOverLimit, - /** @type {number} */ (entrypointSizeLimit) - ) - ); - } - - if (warnings.length > 0) { - const someAsyncChunk = find( - compilation.chunks, - chunk => !chunk.canBeInitial() - ); - - if (!someAsyncChunk) { - warnings.push(new NoAsyncChunksWarning()); - } - - if (hints === "error") { - compilation.errors.push(...warnings); - } else { - compilation.warnings.push(...warnings); - } - } - } - }); - } -}; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js deleted file mode 100644 index a330b4a4d73..00000000000 --- a/webpack-lib/lib/prefetch/ChunkPrefetchFunctionRuntimeModule.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -class ChunkPrefetchFunctionRuntimeModule extends RuntimeModule { - /** - * @param {string} childType TODO - * @param {string} runtimeFunction TODO - * @param {string} runtimeHandlers TODO - */ - constructor(childType, runtimeFunction, runtimeHandlers) { - super(`chunk ${childType} function`); - this.childType = childType; - this.runtimeFunction = runtimeFunction; - this.runtimeHandlers = runtimeHandlers; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { runtimeFunction, runtimeHandlers } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return Template.asString([ - `${runtimeHandlers} = {};`, - `${runtimeFunction} = ${runtimeTemplate.basicFunction("chunkId", [ - // map is shorter than forEach - `Object.keys(${runtimeHandlers}).map(${runtimeTemplate.basicFunction( - "key", - `${runtimeHandlers}[key](chunkId);` - )});` - ])}` - ]); - } -} - -module.exports = ChunkPrefetchFunctionRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js b/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js deleted file mode 100644 index 08e78ef6b9f..00000000000 --- a/webpack-lib/lib/prefetch/ChunkPrefetchPreloadPlugin.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const ChunkPrefetchFunctionRuntimeModule = require("./ChunkPrefetchFunctionRuntimeModule"); -const ChunkPrefetchStartupRuntimeModule = require("./ChunkPrefetchStartupRuntimeModule"); -const ChunkPrefetchTriggerRuntimeModule = require("./ChunkPrefetchTriggerRuntimeModule"); -const ChunkPreloadTriggerRuntimeModule = require("./ChunkPreloadTriggerRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGroup").RawChunkGroupOptions} RawChunkGroupOptions */ -/** @typedef {import("../Compiler")} Compiler */ - -class ChunkPrefetchPreloadPlugin { - /** - * @param {Compiler} compiler the compiler - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "ChunkPrefetchPreloadPlugin", - compilation => { - compilation.hooks.additionalChunkRuntimeRequirements.tap( - "ChunkPrefetchPreloadPlugin", - (chunk, set, { chunkGraph }) => { - if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; - const startupChildChunks = chunk.getChildrenOfTypeInOrder( - chunkGraph, - "prefetchOrder" - ); - if (startupChildChunks) { - set.add(RuntimeGlobals.prefetchChunk); - set.add(RuntimeGlobals.onChunksLoaded); - set.add(RuntimeGlobals.exports); - compilation.addRuntimeModule( - chunk, - new ChunkPrefetchStartupRuntimeModule(startupChildChunks) - ); - } - } - ); - compilation.hooks.additionalTreeRuntimeRequirements.tap( - "ChunkPrefetchPreloadPlugin", - (chunk, set, { chunkGraph }) => { - const chunkMap = chunk.getChildIdsByOrdersMap(chunkGraph); - - if (chunkMap.prefetch) { - set.add(RuntimeGlobals.prefetchChunk); - compilation.addRuntimeModule( - chunk, - new ChunkPrefetchTriggerRuntimeModule(chunkMap.prefetch) - ); - } - if (chunkMap.preload) { - set.add(RuntimeGlobals.preloadChunk); - compilation.addRuntimeModule( - chunk, - new ChunkPreloadTriggerRuntimeModule(chunkMap.preload) - ); - } - } - ); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.prefetchChunk) - .tap("ChunkPrefetchPreloadPlugin", (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new ChunkPrefetchFunctionRuntimeModule( - "prefetch", - RuntimeGlobals.prefetchChunk, - RuntimeGlobals.prefetchChunkHandlers - ) - ); - set.add(RuntimeGlobals.prefetchChunkHandlers); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.preloadChunk) - .tap("ChunkPrefetchPreloadPlugin", (chunk, set) => { - compilation.addRuntimeModule( - chunk, - new ChunkPrefetchFunctionRuntimeModule( - "preload", - RuntimeGlobals.preloadChunk, - RuntimeGlobals.preloadChunkHandlers - ) - ); - set.add(RuntimeGlobals.preloadChunkHandlers); - }); - } - ); - } -} - -module.exports = ChunkPrefetchPreloadPlugin; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js deleted file mode 100644 index 740bbe8c3c1..00000000000 --- a/webpack-lib/lib/prefetch/ChunkPrefetchStartupRuntimeModule.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -class ChunkPrefetchStartupRuntimeModule extends RuntimeModule { - /** - * @param {{ onChunks: Chunk[], chunks: Set }[]} startupChunks chunk ids to trigger when chunks are loaded - */ - constructor(startupChunks) { - super("startup prefetch", RuntimeModule.STAGE_TRIGGER); - this.startupChunks = startupChunks; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { startupChunks } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const chunk = /** @type {Chunk} */ (this.chunk); - const { runtimeTemplate } = compilation; - return Template.asString( - startupChunks.map( - ({ onChunks, chunks }) => - `${RuntimeGlobals.onChunksLoaded}(0, ${JSON.stringify( - // This need to include itself to delay execution after this chunk has been fully loaded - onChunks.filter(c => c === chunk).map(c => c.id) - )}, ${runtimeTemplate.basicFunction( - "", - chunks.size < 3 - ? Array.from( - chunks, - c => - `${RuntimeGlobals.prefetchChunk}(${JSON.stringify(c.id)});` - ) - : `${JSON.stringify(Array.from(chunks, c => c.id))}.map(${ - RuntimeGlobals.prefetchChunk - });` - )}, 5);` - ) - ); - } -} - -module.exports = ChunkPrefetchStartupRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js deleted file mode 100644 index 74eb2bc613f..00000000000 --- a/webpack-lib/lib/prefetch/ChunkPrefetchTriggerRuntimeModule.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -class ChunkPrefetchTriggerRuntimeModule extends RuntimeModule { - /** - * @param {Record} chunkMap map from chunk to - */ - constructor(chunkMap) { - super("chunk prefetch trigger", RuntimeModule.STAGE_TRIGGER); - this.chunkMap = chunkMap; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { chunkMap } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const body = [ - "var chunks = chunkToChildrenMap[chunkId];", - `Array.isArray(chunks) && chunks.map(${RuntimeGlobals.prefetchChunk});` - ]; - return Template.asString([ - Template.asString([ - `var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`, - `${ - RuntimeGlobals.ensureChunkHandlers - }.prefetch = ${runtimeTemplate.expressionFunction( - `Promise.all(promises).then(${runtimeTemplate.basicFunction( - "", - body - )})`, - "chunkId, promises" - )};` - ]) - ]); - } -} - -module.exports = ChunkPrefetchTriggerRuntimeModule; diff --git a/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js b/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js deleted file mode 100644 index 8509def176d..00000000000 --- a/webpack-lib/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -class ChunkPreloadTriggerRuntimeModule extends RuntimeModule { - /** - * @param {Record} chunkMap map from chunk to chunks - */ - constructor(chunkMap) { - super("chunk preload trigger", RuntimeModule.STAGE_TRIGGER); - this.chunkMap = chunkMap; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { chunkMap } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const body = [ - "var chunks = chunkToChildrenMap[chunkId];", - `Array.isArray(chunks) && chunks.map(${RuntimeGlobals.preloadChunk});` - ]; - return Template.asString([ - Template.asString([ - `var chunkToChildrenMap = ${JSON.stringify(chunkMap, null, "\t")};`, - `${ - RuntimeGlobals.ensureChunkHandlers - }.preload = ${runtimeTemplate.basicFunction("chunkId", body)};` - ]) - ]); - } -} - -module.exports = ChunkPreloadTriggerRuntimeModule; diff --git a/webpack-lib/lib/rules/BasicEffectRulePlugin.js b/webpack-lib/lib/rules/BasicEffectRulePlugin.js deleted file mode 100644 index 935716baad5..00000000000 --- a/webpack-lib/lib/rules/BasicEffectRulePlugin.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ - -class BasicEffectRulePlugin { - /** - * @param {string} ruleProperty the rule property - * @param {string=} effectType the effect type - */ - constructor(ruleProperty, effectType) { - this.ruleProperty = ruleProperty; - this.effectType = effectType || ruleProperty; - } - - /** - * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler - * @returns {void} - */ - apply(ruleSetCompiler) { - ruleSetCompiler.hooks.rule.tap( - "BasicEffectRulePlugin", - (path, rule, unhandledProperties, result, references) => { - if (unhandledProperties.has(this.ruleProperty)) { - unhandledProperties.delete(this.ruleProperty); - - const value = - rule[/** @type {keyof RuleSetRule} */ (this.ruleProperty)]; - - result.effects.push({ - type: this.effectType, - value - }); - } - } - ); - } -} - -module.exports = BasicEffectRulePlugin; diff --git a/webpack-lib/lib/rules/BasicMatcherRulePlugin.js b/webpack-lib/lib/rules/BasicMatcherRulePlugin.js deleted file mode 100644 index 47ac214f624..00000000000 --- a/webpack-lib/lib/rules/BasicMatcherRulePlugin.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ -/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */ - -class BasicMatcherRulePlugin { - /** - * @param {string} ruleProperty the rule property - * @param {string=} dataProperty the data property - * @param {boolean=} invert if true, inverts the condition - */ - constructor(ruleProperty, dataProperty, invert) { - this.ruleProperty = ruleProperty; - this.dataProperty = dataProperty || ruleProperty; - this.invert = invert || false; - } - - /** - * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler - * @returns {void} - */ - apply(ruleSetCompiler) { - ruleSetCompiler.hooks.rule.tap( - "BasicMatcherRulePlugin", - (path, rule, unhandledProperties, result) => { - if (unhandledProperties.has(this.ruleProperty)) { - unhandledProperties.delete(this.ruleProperty); - const value = - rule[/** @type {keyof RuleSetRule} */ (this.ruleProperty)]; - const condition = ruleSetCompiler.compileCondition( - `${path}.${this.ruleProperty}`, - value - ); - const fn = condition.fn; - result.conditions.push({ - property: this.dataProperty, - matchWhenEmpty: this.invert - ? !condition.matchWhenEmpty - : condition.matchWhenEmpty, - fn: this.invert ? v => !fn(v) : fn - }); - } - } - ); - } -} - -module.exports = BasicMatcherRulePlugin; diff --git a/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js b/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js deleted file mode 100644 index 984e86f83fa..00000000000 --- a/webpack-lib/lib/rules/ObjectMatcherRulePlugin.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ -/** @typedef {import("./RuleSetCompiler").RuleCondition} RuleCondition */ -/** @typedef {import("./RuleSetCompiler").RuleConditionFunction} RuleConditionFunction */ - -class ObjectMatcherRulePlugin { - /** - * @param {string} ruleProperty the rule property - * @param {string=} dataProperty the data property - * @param {RuleConditionFunction=} additionalConditionFunction need to check - */ - constructor(ruleProperty, dataProperty, additionalConditionFunction) { - this.ruleProperty = ruleProperty; - this.dataProperty = dataProperty || ruleProperty; - this.additionalConditionFunction = additionalConditionFunction; - } - - /** - * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler - * @returns {void} - */ - apply(ruleSetCompiler) { - const { ruleProperty, dataProperty } = this; - ruleSetCompiler.hooks.rule.tap( - "ObjectMatcherRulePlugin", - (path, rule, unhandledProperties, result) => { - if (unhandledProperties.has(ruleProperty)) { - unhandledProperties.delete(ruleProperty); - const value = - /** @type {Record} */ - (rule[/** @type {keyof RuleSetRule} */ (ruleProperty)]); - for (const property of Object.keys(value)) { - const nestedDataProperties = property.split("."); - const condition = ruleSetCompiler.compileCondition( - `${path}.${ruleProperty}.${property}`, - value[property] - ); - if (this.additionalConditionFunction) { - result.conditions.push({ - property: [dataProperty], - matchWhenEmpty: condition.matchWhenEmpty, - fn: this.additionalConditionFunction - }); - } - result.conditions.push({ - property: [dataProperty, ...nestedDataProperties], - matchWhenEmpty: condition.matchWhenEmpty, - fn: condition.fn - }); - } - } - } - ); - } -} - -module.exports = ObjectMatcherRulePlugin; diff --git a/webpack-lib/lib/rules/RuleSetCompiler.js b/webpack-lib/lib/rules/RuleSetCompiler.js deleted file mode 100644 index 5e32e133987..00000000000 --- a/webpack-lib/lib/rules/RuleSetCompiler.js +++ /dev/null @@ -1,400 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncHook } = require("tapable"); - -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRules} RuleSetRules */ - -/** @typedef {function(string | EffectData): boolean} RuleConditionFunction */ - -/** - * @typedef {object} RuleCondition - * @property {string | string[]} property - * @property {boolean} matchWhenEmpty - * @property {RuleConditionFunction} fn - */ - -/** - * @typedef {object} Condition - * @property {boolean} matchWhenEmpty - * @property {RuleConditionFunction} fn - */ - -/** - * @typedef {Record} EffectData - */ - -/** - * @typedef {object} CompiledRule - * @property {RuleCondition[]} conditions - * @property {(Effect|function(EffectData): Effect[])[]} effects - * @property {CompiledRule[]=} rules - * @property {CompiledRule[]=} oneOf - */ - -/** - * @typedef {object} Effect - * @property {string} type - * @property {any} value - */ - -/** - * @typedef {object} RuleSet - * @property {Map} references map of references in the rule set (may grow over time) - * @property {function(EffectData): Effect[]} exec execute the rule set - */ - -/** @typedef {{ apply: (function(RuleSetCompiler): void) }} RuleSetPlugin */ - -class RuleSetCompiler { - /** - * @param {RuleSetPlugin[]} plugins plugins - */ - constructor(plugins) { - this.hooks = Object.freeze({ - /** @type {SyncHook<[string, RuleSetRule, Set, CompiledRule, Map]>} */ - rule: new SyncHook([ - "path", - "rule", - "unhandledProperties", - "compiledRule", - "references" - ]) - }); - if (plugins) { - for (const plugin of plugins) { - plugin.apply(this); - } - } - } - - /** - * @param {TODO[]} ruleSet raw user provided rules - * @returns {RuleSet} compiled RuleSet - */ - compile(ruleSet) { - const refs = new Map(); - const rules = this.compileRules("ruleSet", ruleSet, refs); - - /** - * @param {EffectData} data data passed in - * @param {CompiledRule} rule the compiled rule - * @param {Effect[]} effects an array where effects are pushed to - * @returns {boolean} true, if the rule has matched - */ - const execRule = (data, rule, effects) => { - for (const condition of rule.conditions) { - const p = condition.property; - if (Array.isArray(p)) { - /** @type {EffectData | string | undefined} */ - let current = data; - for (const subProperty of p) { - if ( - current && - typeof current === "object" && - Object.prototype.hasOwnProperty.call(current, subProperty) - ) { - current = current[subProperty]; - } else { - current = undefined; - break; - } - } - if (current !== undefined) { - if (!condition.fn(current)) return false; - continue; - } - } else if (p in data) { - const value = data[p]; - if (value !== undefined) { - if (!condition.fn(value)) return false; - continue; - } - } - if (!condition.matchWhenEmpty) { - return false; - } - } - for (const effect of rule.effects) { - if (typeof effect === "function") { - const returnedEffects = effect(data); - for (const effect of returnedEffects) { - effects.push(effect); - } - } else { - effects.push(effect); - } - } - if (rule.rules) { - for (const childRule of rule.rules) { - execRule(data, childRule, effects); - } - } - if (rule.oneOf) { - for (const childRule of rule.oneOf) { - if (execRule(data, childRule, effects)) { - break; - } - } - } - return true; - }; - - return { - references: refs, - exec: data => { - /** @type {Effect[]} */ - const effects = []; - for (const rule of rules) { - execRule(data, rule, effects); - } - return effects; - } - }; - } - - /** - * @param {string} path current path - * @param {RuleSetRules} rules the raw rules provided by user - * @param {Map} refs references - * @returns {CompiledRule[]} rules - */ - compileRules(path, rules, refs) { - return rules - .filter(Boolean) - .map((rule, i) => - this.compileRule( - `${path}[${i}]`, - /** @type {RuleSetRule} */ (rule), - refs - ) - ); - } - - /** - * @param {string} path current path - * @param {RuleSetRule} rule the raw rule provided by user - * @param {Map} refs references - * @returns {CompiledRule} normalized and compiled rule for processing - */ - compileRule(path, rule, refs) { - const unhandledProperties = new Set( - Object.keys(rule).filter( - key => rule[/** @type {keyof RuleSetRule} */ (key)] !== undefined - ) - ); - - /** @type {CompiledRule} */ - const compiledRule = { - conditions: [], - effects: [], - rules: undefined, - oneOf: undefined - }; - - this.hooks.rule.call(path, rule, unhandledProperties, compiledRule, refs); - - if (unhandledProperties.has("rules")) { - unhandledProperties.delete("rules"); - const rules = rule.rules; - if (!Array.isArray(rules)) - throw this.error(path, rules, "Rule.rules must be an array of rules"); - compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs); - } - - if (unhandledProperties.has("oneOf")) { - unhandledProperties.delete("oneOf"); - const oneOf = rule.oneOf; - if (!Array.isArray(oneOf)) - throw this.error(path, oneOf, "Rule.oneOf must be an array of rules"); - compiledRule.oneOf = this.compileRules(`${path}.oneOf`, oneOf, refs); - } - - if (unhandledProperties.size > 0) { - throw this.error( - path, - rule, - `Properties ${Array.from(unhandledProperties).join(", ")} are unknown` - ); - } - - return compiledRule; - } - - /** - * @param {string} path current path - * @param {any} condition user provided condition value - * @returns {Condition} compiled condition - */ - compileCondition(path, condition) { - if (condition === "") { - return { - matchWhenEmpty: true, - fn: str => str === "" - }; - } - if (!condition) { - throw this.error( - path, - condition, - "Expected condition but got falsy value" - ); - } - if (typeof condition === "string") { - return { - matchWhenEmpty: condition.length === 0, - fn: str => typeof str === "string" && str.startsWith(condition) - }; - } - if (typeof condition === "function") { - try { - return { - matchWhenEmpty: condition(""), - fn: condition - }; - } catch (_err) { - throw this.error( - path, - condition, - "Evaluation of condition function threw error" - ); - } - } - if (condition instanceof RegExp) { - return { - matchWhenEmpty: condition.test(""), - fn: v => typeof v === "string" && condition.test(v) - }; - } - if (Array.isArray(condition)) { - const items = condition.map((c, i) => - this.compileCondition(`${path}[${i}]`, c) - ); - return this.combineConditionsOr(items); - } - - if (typeof condition !== "object") { - throw this.error( - path, - condition, - `Unexpected ${typeof condition} when condition was expected` - ); - } - - const conditions = []; - for (const key of Object.keys(condition)) { - const value = condition[key]; - switch (key) { - case "or": - if (value) { - if (!Array.isArray(value)) { - throw this.error( - `${path}.or`, - condition.or, - "Expected array of conditions" - ); - } - conditions.push(this.compileCondition(`${path}.or`, value)); - } - break; - case "and": - if (value) { - if (!Array.isArray(value)) { - throw this.error( - `${path}.and`, - condition.and, - "Expected array of conditions" - ); - } - let i = 0; - for (const item of value) { - conditions.push(this.compileCondition(`${path}.and[${i}]`, item)); - i++; - } - } - break; - case "not": - if (value) { - const matcher = this.compileCondition(`${path}.not`, value); - const fn = matcher.fn; - conditions.push({ - matchWhenEmpty: !matcher.matchWhenEmpty, - fn: /** @type {RuleConditionFunction} */ (v => !fn(v)) - }); - } - break; - default: - throw this.error( - `${path}.${key}`, - condition[key], - `Unexpected property ${key} in condition` - ); - } - } - if (conditions.length === 0) { - throw this.error( - path, - condition, - "Expected condition, but got empty thing" - ); - } - return this.combineConditionsAnd(conditions); - } - - /** - * @param {Condition[]} conditions some conditions - * @returns {Condition} merged condition - */ - combineConditionsOr(conditions) { - if (conditions.length === 0) { - return { - matchWhenEmpty: false, - fn: () => false - }; - } else if (conditions.length === 1) { - return conditions[0]; - } - return { - matchWhenEmpty: conditions.some(c => c.matchWhenEmpty), - fn: v => conditions.some(c => c.fn(v)) - }; - } - - /** - * @param {Condition[]} conditions some conditions - * @returns {Condition} merged condition - */ - combineConditionsAnd(conditions) { - if (conditions.length === 0) { - return { - matchWhenEmpty: false, - fn: () => false - }; - } else if (conditions.length === 1) { - return conditions[0]; - } - return { - matchWhenEmpty: conditions.every(c => c.matchWhenEmpty), - fn: v => conditions.every(c => c.fn(v)) - }; - } - - /** - * @param {string} path current path - * @param {any} value value at the error location - * @param {string} message message explaining the problem - * @returns {Error} an error object - */ - error(path, value, message) { - return new Error( - `Compiling RuleSet failed: ${message} (at ${path}: ${value})` - ); - } -} - -module.exports = RuleSetCompiler; diff --git a/webpack-lib/lib/rules/UseEffectRulePlugin.js b/webpack-lib/lib/rules/UseEffectRulePlugin.js deleted file mode 100644 index 56f2423de62..00000000000 --- a/webpack-lib/lib/rules/UseEffectRulePlugin.js +++ /dev/null @@ -1,200 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); - -/** @typedef {import("../../declarations/WebpackOptions").RuleSetLoader} RuleSetLoader */ -/** @typedef {import("../../declarations/WebpackOptions").RuleSetLoaderOptions} RuleSetLoaderOptions */ -/** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ -/** @typedef {import("./RuleSetCompiler")} RuleSetCompiler */ -/** @typedef {import("./RuleSetCompiler").Effect} Effect */ - -class UseEffectRulePlugin { - /** - * @param {RuleSetCompiler} ruleSetCompiler the rule set compiler - * @returns {void} - */ - apply(ruleSetCompiler) { - ruleSetCompiler.hooks.rule.tap( - "UseEffectRulePlugin", - (path, rule, unhandledProperties, result, references) => { - /** - * @param {keyof RuleSetRule} property property - * @param {string} correctProperty correct property - */ - const conflictWith = (property, correctProperty) => { - if (unhandledProperties.has(property)) { - throw ruleSetCompiler.error( - `${path}.${property}`, - rule[property], - `A Rule must not have a '${property}' property when it has a '${correctProperty}' property` - ); - } - }; - - if (unhandledProperties.has("use")) { - unhandledProperties.delete("use"); - unhandledProperties.delete("enforce"); - - conflictWith("loader", "use"); - conflictWith("options", "use"); - - const use = rule.use; - const enforce = rule.enforce; - - const type = enforce ? `use-${enforce}` : "use"; - - /** - * @param {string} path options path - * @param {string} defaultIdent default ident when none is provided - * @param {object} item user provided use value - * @returns {Effect|function(any): Effect[]} effect - */ - const useToEffect = (path, defaultIdent, item) => { - if (typeof item === "function") { - return data => useToEffectsWithoutIdent(path, item(data)); - } - return useToEffectRaw(path, defaultIdent, item); - }; - - /** - * @param {string} path options path - * @param {string} defaultIdent default ident when none is provided - * @param {{ ident?: string, loader?: RuleSetLoader, options?: RuleSetLoaderOptions }} item user provided use value - * @returns {Effect} effect - */ - const useToEffectRaw = (path, defaultIdent, item) => { - if (typeof item === "string") { - return { - type, - value: { - loader: item, - options: undefined, - ident: undefined - } - }; - } - const loader = item.loader; - const options = item.options; - let ident = item.ident; - if (options && typeof options === "object") { - if (!ident) ident = defaultIdent; - references.set(ident, options); - } - if (typeof options === "string") { - util.deprecate( - () => {}, - `Using a string as loader options is deprecated (${path}.options)`, - "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" - )(); - } - return { - type: enforce ? `use-${enforce}` : "use", - value: { - loader, - options, - ident - } - }; - }; - - /** - * @param {string} path options path - * @param {any} items user provided use value - * @returns {Effect[]} effects - */ - const useToEffectsWithoutIdent = (path, items) => { - if (Array.isArray(items)) { - return items - .filter(Boolean) - .map((item, idx) => - useToEffectRaw(`${path}[${idx}]`, "[[missing ident]]", item) - ); - } - return [useToEffectRaw(path, "[[missing ident]]", items)]; - }; - - /** - * @param {string} path current path - * @param {any} items user provided use value - * @returns {(Effect|function(any): Effect[])[]} effects - */ - const useToEffects = (path, items) => { - if (Array.isArray(items)) { - return items.filter(Boolean).map((item, idx) => { - const subPath = `${path}[${idx}]`; - return useToEffect(subPath, subPath, item); - }); - } - return [useToEffect(path, path, items)]; - }; - - if (typeof use === "function") { - result.effects.push(data => - useToEffectsWithoutIdent( - `${path}.use`, - use(/** @type {TODO} */ (data)) - ) - ); - } else { - for (const effect of useToEffects(`${path}.use`, use)) { - result.effects.push(effect); - } - } - } - - if (unhandledProperties.has("loader")) { - unhandledProperties.delete("loader"); - unhandledProperties.delete("options"); - unhandledProperties.delete("enforce"); - - const loader = /** @type {RuleSetLoader} */ (rule.loader); - const options = rule.options; - const enforce = rule.enforce; - - if (loader.includes("!")) { - throw ruleSetCompiler.error( - `${path}.loader`, - loader, - "Exclamation mark separated loader lists has been removed in favor of the 'use' property with arrays" - ); - } - - if (loader.includes("?")) { - throw ruleSetCompiler.error( - `${path}.loader`, - loader, - "Query arguments on 'loader' has been removed in favor of the 'options' property" - ); - } - - if (typeof options === "string") { - util.deprecate( - () => {}, - `Using a string as loader options is deprecated (${path}.options)`, - "DEP_WEBPACK_RULE_LOADER_OPTIONS_STRING" - )(); - } - - const ident = - options && typeof options === "object" ? path : undefined; - references.set(ident, options); - result.effects.push({ - type: enforce ? `use-${enforce}` : "use", - value: { - loader, - options, - ident - } - }); - } - } - ); - } -} - -module.exports = UseEffectRulePlugin; diff --git a/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js b/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js deleted file mode 100644 index 79141c76f2e..00000000000 --- a/webpack-lib/lib/runtime/AsyncModuleRuntimeModule.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class AsyncModuleRuntimeModule extends HelperRuntimeModule { - constructor() { - super("async module"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.asyncModule; - return Template.asString([ - 'var webpackQueues = typeof Symbol === "function" ? Symbol("webpack queues") : "__webpack_queues__";', - `var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "${RuntimeGlobals.exports}";`, - 'var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";', - `var resolveQueue = ${runtimeTemplate.basicFunction("queue", [ - "if(queue && queue.d < 1) {", - Template.indent([ - "queue.d = 1;", - `queue.forEach(${runtimeTemplate.expressionFunction( - "fn.r--", - "fn" - )});`, - `queue.forEach(${runtimeTemplate.expressionFunction( - "fn.r-- ? fn.r++ : fn()", - "fn" - )});` - ]), - "}" - ])}`, - `var wrapDeps = ${runtimeTemplate.returningFunction( - `deps.map(${runtimeTemplate.basicFunction("dep", [ - 'if(dep !== null && typeof dep === "object") {', - Template.indent([ - "if(dep[webpackQueues]) return dep;", - "if(dep.then) {", - Template.indent([ - "var queue = [];", - "queue.d = 0;", - `dep.then(${runtimeTemplate.basicFunction("r", [ - "obj[webpackExports] = r;", - "resolveQueue(queue);" - ])}, ${runtimeTemplate.basicFunction("e", [ - "obj[webpackError] = e;", - "resolveQueue(queue);" - ])});`, - "var obj = {};", - `obj[webpackQueues] = ${runtimeTemplate.expressionFunction( - "fn(queue)", - "fn" - )};`, - "return obj;" - ]), - "}" - ]), - "}", - "var ret = {};", - `ret[webpackQueues] = ${runtimeTemplate.emptyFunction()};`, - "ret[webpackExports] = dep;", - "return ret;" - ])})`, - "deps" - )};`, - `${fn} = ${runtimeTemplate.basicFunction("module, body, hasAwait", [ - "var queue;", - "hasAwait && ((queue = []).d = -1);", - "var depQueues = new Set();", - "var exports = module.exports;", - "var currentDeps;", - "var outerResolve;", - "var reject;", - `var promise = new Promise(${runtimeTemplate.basicFunction( - "resolve, rej", - ["reject = rej;", "outerResolve = resolve;"] - )});`, - "promise[webpackExports] = exports;", - `promise[webpackQueues] = ${runtimeTemplate.expressionFunction( - `queue && fn(queue), depQueues.forEach(fn), promise["catch"](${runtimeTemplate.emptyFunction()})`, - "fn" - )};`, - "module.exports = promise;", - `body(${runtimeTemplate.basicFunction("deps", [ - "currentDeps = wrapDeps(deps);", - "var fn;", - `var getResult = ${runtimeTemplate.returningFunction( - `currentDeps.map(${runtimeTemplate.basicFunction("d", [ - "if(d[webpackError]) throw d[webpackError];", - "return d[webpackExports];" - ])})` - )}`, - `var promise = new Promise(${runtimeTemplate.basicFunction( - "resolve", - [ - `fn = ${runtimeTemplate.expressionFunction( - "resolve(getResult)", - "" - )};`, - "fn.r = 0;", - `var fnQueue = ${runtimeTemplate.expressionFunction( - "q !== queue && !depQueues.has(q) && (depQueues.add(q), q && !q.d && (fn.r++, q.push(fn)))", - "q" - )};`, - `currentDeps.map(${runtimeTemplate.expressionFunction( - "dep[webpackQueues](fnQueue)", - "dep" - )});` - ] - )});`, - "return fn.r ? promise : getResult();" - ])}, ${runtimeTemplate.expressionFunction( - "(err ? reject(promise[webpackError] = err) : outerResolve(exports)), resolveQueue(queue)", - "err" - )});`, - "queue && queue.d < 0 && (queue.d = 0);" - ])};` - ]); - } -} - -module.exports = AsyncModuleRuntimeModule; diff --git a/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js b/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js deleted file mode 100644 index 74b40a1e883..00000000000 --- a/webpack-lib/lib/runtime/AutoPublicPathRuntimeModule.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ - -class AutoPublicPathRuntimeModule extends RuntimeModule { - constructor() { - super("publicPath", RuntimeModule.STAGE_BASIC); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { scriptType, importMetaName, path } = compilation.outputOptions; - const chunkName = compilation.getPath( - JavascriptModulesPlugin.getChunkFilenameTemplate( - /** @type {Chunk} */ - (this.chunk), - compilation.outputOptions - ), - { - chunk: this.chunk, - contentHashType: "javascript" - } - ); - const undoPath = getUndoPath( - chunkName, - /** @type {string} */ (path), - false - ); - - return Template.asString([ - "var scriptUrl;", - scriptType === "module" - ? `if (typeof ${importMetaName}.url === "string") scriptUrl = ${importMetaName}.url` - : Template.asString([ - `if (${RuntimeGlobals.global}.importScripts) scriptUrl = ${RuntimeGlobals.global}.location + "";`, - `var document = ${RuntimeGlobals.global}.document;`, - "if (!scriptUrl && document) {", - Template.indent([ - // Technically we could use `document.currentScript instanceof window.HTMLScriptElement`, - // but an attacker could try to inject `` - // and use `` - "if (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT')", - Template.indent("scriptUrl = document.currentScript.src;"), - "if (!scriptUrl) {", - Template.indent([ - 'var scripts = document.getElementsByTagName("script");', - "if(scripts.length) {", - Template.indent([ - "var i = scripts.length - 1;", - "while (i > -1 && (!scriptUrl || !/^http(s?):/.test(scriptUrl))) scriptUrl = scripts[i--].src;" - ]), - "}" - ]), - "}" - ]), - "}" - ]), - "// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration", - '// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.', - 'if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");', - 'scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\\?.*$/, "").replace(/\\/[^\\/]+$/, "/");', - !undoPath - ? `${RuntimeGlobals.publicPath} = scriptUrl;` - : `${RuntimeGlobals.publicPath} = scriptUrl + ${JSON.stringify( - undoPath - )};` - ]); - } -} - -module.exports = AutoPublicPathRuntimeModule; diff --git a/webpack-lib/lib/runtime/BaseUriRuntimeModule.js b/webpack-lib/lib/runtime/BaseUriRuntimeModule.js deleted file mode 100644 index 99609b762bd..00000000000 --- a/webpack-lib/lib/runtime/BaseUriRuntimeModule.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../../declarations/WebpackOptions").EntryDescriptionNormalized} EntryDescriptionNormalized */ -/** @typedef {import("../Chunk")} Chunk */ - -class BaseUriRuntimeModule extends RuntimeModule { - constructor() { - super("base uri", RuntimeModule.STAGE_ATTACH); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const chunk = /** @type {Chunk} */ (this.chunk); - const options = - /** @type {EntryDescriptionNormalized} */ - (chunk.getEntryOptions()); - return `${RuntimeGlobals.baseURI} = ${ - options.baseUri === undefined - ? "undefined" - : JSON.stringify(options.baseUri) - };`; - } -} - -module.exports = BaseUriRuntimeModule; diff --git a/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js b/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js deleted file mode 100644 index 22149767907..00000000000 --- a/webpack-lib/lib/runtime/ChunkNameRuntimeModule.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -class ChunkNameRuntimeModule extends RuntimeModule { - /** - * @param {string} chunkName the chunk's name - */ - constructor(chunkName) { - super("chunkName"); - this.chunkName = chunkName; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return `${RuntimeGlobals.chunkName} = ${JSON.stringify(this.chunkName)};`; - } -} - -module.exports = ChunkNameRuntimeModule; diff --git a/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js b/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js deleted file mode 100644 index 1406e051fd9..00000000000 --- a/webpack-lib/lib/runtime/CompatGetDefaultExportRuntimeModule.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class CompatGetDefaultExportRuntimeModule extends HelperRuntimeModule { - constructor() { - super("compat get default export"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.compatGetDefaultExport; - return Template.asString([ - "// getDefaultExport function for compatibility with non-harmony modules", - `${fn} = ${runtimeTemplate.basicFunction("module", [ - "var getter = module && module.__esModule ?", - Template.indent([ - `${runtimeTemplate.returningFunction("module['default']")} :`, - `${runtimeTemplate.returningFunction("module")};` - ]), - `${RuntimeGlobals.definePropertyGetters}(getter, { a: getter });`, - "return getter;" - ])};` - ]); - } -} - -module.exports = CompatGetDefaultExportRuntimeModule; diff --git a/webpack-lib/lib/runtime/CompatRuntimeModule.js b/webpack-lib/lib/runtime/CompatRuntimeModule.js deleted file mode 100644 index cf386c0886b..00000000000 --- a/webpack-lib/lib/runtime/CompatRuntimeModule.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../MainTemplate")} MainTemplate */ - -class CompatRuntimeModule extends RuntimeModule { - constructor() { - super("compat", RuntimeModule.STAGE_ATTACH); - this.fullHash = true; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const { - runtimeTemplate, - mainTemplate, - moduleTemplates, - dependencyTemplates - } = compilation; - const bootstrap = mainTemplate.hooks.bootstrap.call( - "", - chunk, - compilation.hash || "XXXX", - moduleTemplates.javascript, - dependencyTemplates - ); - const localVars = mainTemplate.hooks.localVars.call( - "", - chunk, - compilation.hash || "XXXX" - ); - const requireExtensions = mainTemplate.hooks.requireExtensions.call( - "", - chunk, - compilation.hash || "XXXX" - ); - const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - let requireEnsure = ""; - if (runtimeRequirements.has(RuntimeGlobals.ensureChunk)) { - const requireEnsureHandler = mainTemplate.hooks.requireEnsure.call( - "", - chunk, - compilation.hash || "XXXX", - "chunkId" - ); - if (requireEnsureHandler) { - requireEnsure = `${ - RuntimeGlobals.ensureChunkHandlers - }.compat = ${runtimeTemplate.basicFunction( - "chunkId, promises", - requireEnsureHandler - )};`; - } - } - return [bootstrap, localVars, requireEnsure, requireExtensions] - .filter(Boolean) - .join("\n"); - } - - /** - * @returns {boolean} true, if the runtime module should get it's own scope - */ - shouldIsolate() { - // We avoid isolating this to have better backward-compat - return false; - } -} - -module.exports = CompatRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js b/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js deleted file mode 100644 index 05b811b19b0..00000000000 --- a/webpack-lib/lib/runtime/CreateFakeNamespaceObjectRuntimeModule.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class CreateFakeNamespaceObjectRuntimeModule extends HelperRuntimeModule { - constructor() { - super("create fake namespace object"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.createFakeNamespaceObject; - return Template.asString([ - `var getProto = Object.getPrototypeOf ? ${runtimeTemplate.returningFunction( - "Object.getPrototypeOf(obj)", - "obj" - )} : ${runtimeTemplate.returningFunction("obj.__proto__", "obj")};`, - "var 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", - // Note: must be a function (not arrow), because this is used in body! - `${fn} = function(value, mode) {`, - Template.indent([ - "if(mode & 1) value = this(value);", - "if(mode & 8) return value;", - "if(typeof value === 'object' && value) {", - Template.indent([ - "if((mode & 4) && value.__esModule) return value;", - "if((mode & 16) && typeof value.then === 'function') return value;" - ]), - "}", - "var ns = Object.create(null);", - `${RuntimeGlobals.makeNamespaceObject}(ns);`, - "var def = {};", - "leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];", - "for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {", - Template.indent([ - `Object.getOwnPropertyNames(current).forEach(${runtimeTemplate.expressionFunction( - `def[key] = ${runtimeTemplate.returningFunction("value[key]", "")}`, - "key" - )});` - ]), - "}", - `def['default'] = ${runtimeTemplate.returningFunction("value", "")};`, - `${RuntimeGlobals.definePropertyGetters}(ns, def);`, - "return ns;" - ]), - "};" - ]); - } -} - -module.exports = CreateFakeNamespaceObjectRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js b/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js deleted file mode 100644 index 7859e87d411..00000000000 --- a/webpack-lib/lib/runtime/CreateScriptRuntimeModule.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class CreateScriptRuntimeModule extends HelperRuntimeModule { - constructor() { - super("trusted types script"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate, outputOptions } = compilation; - const { trustedTypes } = outputOptions; - const fn = RuntimeGlobals.createScript; - - return Template.asString( - `${fn} = ${runtimeTemplate.returningFunction( - trustedTypes - ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScript(script)` - : "script", - "script" - )};` - ); - } -} - -module.exports = CreateScriptRuntimeModule; diff --git a/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js b/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js deleted file mode 100644 index 4c8960024d9..00000000000 --- a/webpack-lib/lib/runtime/CreateScriptUrlRuntimeModule.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class CreateScriptUrlRuntimeModule extends HelperRuntimeModule { - constructor() { - super("trusted types script url"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate, outputOptions } = compilation; - const { trustedTypes } = outputOptions; - const fn = RuntimeGlobals.createScriptUrl; - - return Template.asString( - `${fn} = ${runtimeTemplate.returningFunction( - trustedTypes - ? `${RuntimeGlobals.getTrustedTypesPolicy}().createScriptURL(url)` - : "url", - "url" - )};` - ); - } -} - -module.exports = CreateScriptUrlRuntimeModule; diff --git a/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js b/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js deleted file mode 100644 index 4dad207a935..00000000000 --- a/webpack-lib/lib/runtime/DefinePropertyGettersRuntimeModule.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class DefinePropertyGettersRuntimeModule extends HelperRuntimeModule { - constructor() { - super("define property getters"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.definePropertyGetters; - return Template.asString([ - "// define getter functions for harmony exports", - `${fn} = ${runtimeTemplate.basicFunction("exports, definition", [ - "for(var key in definition) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(definition, key) && !${RuntimeGlobals.hasOwnProperty}(exports, key)) {`, - Template.indent([ - "Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });" - ]), - "}" - ]), - "}" - ])};` - ]); - } -} - -module.exports = DefinePropertyGettersRuntimeModule; diff --git a/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js b/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js deleted file mode 100644 index bc6c0ecbdf1..00000000000 --- a/webpack-lib/lib/runtime/EnsureChunkRuntimeModule.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -class EnsureChunkRuntimeModule extends RuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("ensure chunk"); - this.runtimeRequirements = runtimeRequirements; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - // Check if there are non initial chunks which need to be imported using require-ensure - if (this.runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers)) { - const withFetchPriority = this.runtimeRequirements.has( - RuntimeGlobals.hasFetchPriority - ); - const handlers = RuntimeGlobals.ensureChunkHandlers; - return Template.asString([ - `${handlers} = {};`, - "// This file contains only the entry chunk.", - "// The chunk loading function for additional chunks", - `${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.basicFunction( - `chunkId${withFetchPriority ? ", fetchPriority" : ""}`, - [ - `return Promise.all(Object.keys(${handlers}).reduce(${runtimeTemplate.basicFunction( - "promises, key", - [ - `${handlers}[key](chunkId, promises${ - withFetchPriority ? ", fetchPriority" : "" - });`, - "return promises;" - ] - )}, []));` - ] - )};` - ]); - } - // There ensureChunk is used somewhere in the tree, so we need an empty requireEnsure - // function. This can happen with multiple entrypoints. - return Template.asString([ - "// The chunk loading function for additional chunks", - "// Since all referenced chunks are already included", - "// in this file, this function is empty here.", - `${RuntimeGlobals.ensureChunk} = ${runtimeTemplate.returningFunction( - "Promise.resolve()" - )};` - ]); - } -} - -module.exports = EnsureChunkRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js b/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js deleted file mode 100644 index 3d9b2659950..00000000000 --- a/webpack-lib/lib/runtime/GetChunkFilenameRuntimeModule.js +++ /dev/null @@ -1,295 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { first } = require("../util/SetHelpers"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ - -class GetChunkFilenameRuntimeModule extends RuntimeModule { - /** - * @param {string} contentType the contentType to use the content hash for - * @param {string} name kind of filename - * @param {string} global function name to be assigned - * @param {function(Chunk): TemplatePath | false} getFilenameForChunk functor to get the filename or function - * @param {boolean} allChunks when false, only async chunks are included - */ - constructor(contentType, name, global, getFilenameForChunk, allChunks) { - super(`get ${name} chunk filename`); - this.contentType = contentType; - this.global = global; - this.getFilenameForChunk = getFilenameForChunk; - this.allChunks = allChunks; - this.dependentHash = true; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { global, contentType, getFilenameForChunk, allChunks } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const { runtimeTemplate } = compilation; - - /** @type {Map>} */ - const chunkFilenames = new Map(); - let maxChunks = 0; - /** @type {string | undefined} */ - let dynamicFilename; - - /** - * @param {Chunk} c the chunk - * @returns {void} - */ - const addChunk = c => { - const chunkFilename = getFilenameForChunk(c); - if (chunkFilename) { - let set = chunkFilenames.get(chunkFilename); - if (set === undefined) { - chunkFilenames.set(chunkFilename, (set = new Set())); - } - set.add(c); - if (typeof chunkFilename === "string") { - if (set.size < maxChunks) return; - if (set.size === maxChunks) { - if ( - chunkFilename.length < - /** @type {string} */ (dynamicFilename).length - ) { - return; - } - - if ( - chunkFilename.length === - /** @type {string} */ (dynamicFilename).length && - chunkFilename < /** @type {string} */ (dynamicFilename) - ) { - return; - } - } - maxChunks = set.size; - dynamicFilename = chunkFilename; - } - } - }; - - /** @type {string[]} */ - const includedChunksMessages = []; - if (allChunks) { - includedChunksMessages.push("all chunks"); - for (const c of chunk.getAllReferencedChunks()) { - addChunk(c); - } - } else { - includedChunksMessages.push("async chunks"); - for (const c of chunk.getAllAsyncChunks()) { - addChunk(c); - } - const includeEntries = chunkGraph - .getTreeRuntimeRequirements(chunk) - .has(RuntimeGlobals.ensureChunkIncludeEntries); - if (includeEntries) { - includedChunksMessages.push("sibling chunks for the entrypoint"); - for (const c of chunkGraph.getChunkEntryDependentChunksIterable( - chunk - )) { - addChunk(c); - } - } - } - for (const entrypoint of chunk.getAllReferencedAsyncEntrypoints()) { - addChunk(entrypoint.chunks[entrypoint.chunks.length - 1]); - } - - /** @type {Map>} */ - const staticUrls = new Map(); - /** @type {Set} */ - const dynamicUrlChunks = new Set(); - - /** - * @param {Chunk} c the chunk - * @param {string | TemplatePath} chunkFilename the filename template for the chunk - * @returns {void} - */ - const addStaticUrl = (c, chunkFilename) => { - /** - * @param {string | number} value a value - * @returns {string} string to put in quotes - */ - const unquotedStringify = value => { - const str = `${value}`; - if (str.length >= 5 && str === `${c.id}`) { - // This is shorter and generates the same result - return '" + chunkId + "'; - } - const s = JSON.stringify(str); - return s.slice(1, -1); - }; - /** - * @param {string} value string - * @returns {function(number): string} string to put in quotes with length - */ - const unquotedStringifyWithLength = value => length => - unquotedStringify(`${value}`.slice(0, length)); - const chunkFilenameValue = - typeof chunkFilename === "function" - ? JSON.stringify( - chunkFilename({ - chunk: c, - contentHashType: contentType - }) - ) - : JSON.stringify(chunkFilename); - const staticChunkFilename = compilation.getPath(chunkFilenameValue, { - hash: `" + ${RuntimeGlobals.getFullHash}() + "`, - hashWithLength: length => - `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, - chunk: { - id: unquotedStringify(/** @type {number | string} */ (c.id)), - hash: unquotedStringify(/** @type {string} */ (c.renderedHash)), - hashWithLength: unquotedStringifyWithLength( - /** @type {string} */ (c.renderedHash) - ), - name: unquotedStringify( - c.name || /** @type {number | string} */ (c.id) - ), - contentHash: { - [contentType]: unquotedStringify(c.contentHash[contentType]) - }, - contentHashWithLength: { - [contentType]: unquotedStringifyWithLength( - c.contentHash[contentType] - ) - } - }, - contentHashType: contentType - }); - let set = staticUrls.get(staticChunkFilename); - if (set === undefined) { - staticUrls.set(staticChunkFilename, (set = new Set())); - } - set.add(c.id); - }; - - for (const [filename, chunks] of chunkFilenames) { - if (filename !== dynamicFilename) { - for (const c of chunks) addStaticUrl(c, filename); - } else { - for (const c of chunks) dynamicUrlChunks.add(c); - } - } - - /** - * @param {function(Chunk): string | number} fn function from chunk to value - * @returns {string} code with static mapping of results of fn - */ - const createMap = fn => { - /** @type {Record} */ - const obj = {}; - let useId = false; - /** @type {number | string | undefined} */ - let lastKey; - let entries = 0; - for (const c of dynamicUrlChunks) { - const value = fn(c); - if (value === c.id) { - useId = true; - } else { - obj[/** @type {number | string} */ (c.id)] = value; - lastKey = /** @type {number | string} */ (c.id); - entries++; - } - } - if (entries === 0) return "chunkId"; - if (entries === 1) { - return useId - ? `(chunkId === ${JSON.stringify(lastKey)} ? ${JSON.stringify( - obj[/** @type {number | string} */ (lastKey)] - )} : chunkId)` - : JSON.stringify(obj[/** @type {number | string} */ (lastKey)]); - } - return useId - ? `(${JSON.stringify(obj)}[chunkId] || chunkId)` - : `${JSON.stringify(obj)}[chunkId]`; - }; - - /** - * @param {function(Chunk): string | number} fn function from chunk to value - * @returns {string} code with static mapping of results of fn for including in quoted string - */ - const mapExpr = fn => `" + ${createMap(fn)} + "`; - - /** - * @param {function(Chunk): string | number} fn function from chunk to value - * @returns {function(number): string} function which generates code with static mapping of results of fn for including in quoted string for specific length - */ - const mapExprWithLength = fn => length => - `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`; - - const url = - dynamicFilename && - compilation.getPath(JSON.stringify(dynamicFilename), { - hash: `" + ${RuntimeGlobals.getFullHash}() + "`, - hashWithLength: length => - `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, - chunk: { - id: '" + chunkId + "', - hash: mapExpr(c => /** @type {string} */ (c.renderedHash)), - hashWithLength: mapExprWithLength( - c => /** @type {string} */ (c.renderedHash) - ), - name: mapExpr(c => c.name || /** @type {number | string} */ (c.id)), - contentHash: { - [contentType]: mapExpr(c => c.contentHash[contentType]) - }, - contentHashWithLength: { - [contentType]: mapExprWithLength(c => c.contentHash[contentType]) - } - }, - contentHashType: contentType - }); - - return Template.asString([ - `// This function allow to reference ${includedChunksMessages.join( - " and " - )}`, - `${global} = ${runtimeTemplate.basicFunction( - "chunkId", - - staticUrls.size > 0 - ? [ - "// return url for filenames not based on template", - // it minimizes to `x===1?"...":x===2?"...":"..."` - Template.asString( - Array.from(staticUrls, ([url, ids]) => { - const condition = - ids.size === 1 - ? `chunkId === ${JSON.stringify(first(ids))}` - : `{${Array.from( - ids, - id => `${JSON.stringify(id)}:1` - ).join(",")}}[chunkId]`; - return `if (${condition}) return ${url};`; - }) - ), - "// return url for filenames based on template", - `return ${url};` - ] - : ["// return url for filenames based on template", `return ${url};`] - )};` - ]); - } -} - -module.exports = GetChunkFilenameRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js b/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js deleted file mode 100644 index cf9949394fb..00000000000 --- a/webpack-lib/lib/runtime/GetFullHashRuntimeModule.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class GetFullHashRuntimeModule extends RuntimeModule { - constructor() { - super("getFullHash"); - this.fullHash = true; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return `${RuntimeGlobals.getFullHash} = ${runtimeTemplate.returningFunction( - JSON.stringify(compilation.hash || "XXXX") - )}`; - } -} - -module.exports = GetFullHashRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js b/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js deleted file mode 100644 index 0a9fdf50bb8..00000000000 --- a/webpack-lib/lib/runtime/GetMainFilenameRuntimeModule.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ - -class GetMainFilenameRuntimeModule extends RuntimeModule { - /** - * @param {string} name readable name - * @param {string} global global object binding - * @param {string} filename main file name - */ - constructor(name, global, filename) { - super(`get ${name} filename`); - this.global = global; - this.filename = filename; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { global, filename } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - const chunk = /** @type {Chunk} */ (this.chunk); - const { runtimeTemplate } = compilation; - const url = compilation.getPath(JSON.stringify(filename), { - hash: `" + ${RuntimeGlobals.getFullHash}() + "`, - hashWithLength: length => - `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, - chunk, - runtime: chunk.runtime - }); - return Template.asString([ - `${global} = ${runtimeTemplate.returningFunction(url)};` - ]); - } -} - -module.exports = GetMainFilenameRuntimeModule; diff --git a/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js b/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js deleted file mode 100644 index e8342b3431c..00000000000 --- a/webpack-lib/lib/runtime/GetTrustedTypesPolicyRuntimeModule.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -class GetTrustedTypesPolicyRuntimeModule extends HelperRuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("trusted types policy"); - this.runtimeRequirements = runtimeRequirements; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate, outputOptions } = compilation; - const { trustedTypes } = outputOptions; - const fn = RuntimeGlobals.getTrustedTypesPolicy; - const wrapPolicyCreationInTryCatch = trustedTypes - ? trustedTypes.onPolicyCreationFailure === "continue" - : false; - - return Template.asString([ - "var policy;", - `${fn} = ${runtimeTemplate.basicFunction("", [ - "// Create Trusted Type policy if Trusted Types are available and the policy doesn't exist yet.", - "if (policy === undefined) {", - Template.indent([ - "policy = {", - Template.indent( - [ - ...(this.runtimeRequirements.has(RuntimeGlobals.createScript) - ? [ - `createScript: ${runtimeTemplate.returningFunction( - "script", - "script" - )}` - ] - : []), - ...(this.runtimeRequirements.has(RuntimeGlobals.createScriptUrl) - ? [ - `createScriptURL: ${runtimeTemplate.returningFunction( - "url", - "url" - )}` - ] - : []) - ].join(",\n") - ), - "};", - ...(trustedTypes - ? [ - 'if (typeof trustedTypes !== "undefined" && trustedTypes.createPolicy) {', - Template.indent([ - ...(wrapPolicyCreationInTryCatch ? ["try {"] : []), - ...[ - `policy = trustedTypes.createPolicy(${JSON.stringify( - trustedTypes.policyName - )}, policy);` - ].map(line => - wrapPolicyCreationInTryCatch ? Template.indent(line) : line - ), - ...(wrapPolicyCreationInTryCatch - ? [ - "} catch (e) {", - Template.indent([ - `console.warn('Could not create trusted-types policy ${JSON.stringify( - trustedTypes.policyName - )}');` - ]), - "}" - ] - : []) - ]), - "}" - ] - : []) - ]), - "}", - "return policy;" - ])};` - ]); - } -} - -module.exports = GetTrustedTypesPolicyRuntimeModule; diff --git a/webpack-lib/lib/runtime/GlobalRuntimeModule.js b/webpack-lib/lib/runtime/GlobalRuntimeModule.js deleted file mode 100644 index 89e556c0858..00000000000 --- a/webpack-lib/lib/runtime/GlobalRuntimeModule.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -class GlobalRuntimeModule extends RuntimeModule { - constructor() { - super("global"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return Template.asString([ - `${RuntimeGlobals.global} = (function() {`, - Template.indent([ - "if (typeof globalThis === 'object') return globalThis;", - "try {", - Template.indent( - // This works in non-strict mode - // or - // This works if eval is allowed (see CSP) - "return this || new Function('return this')();" - ), - "} catch (e) {", - Template.indent( - // This works if the window reference is available - "if (typeof window === 'object') return window;" - ), - "}" - // It can still be `undefined`, but nothing to do about it... - // We return `undefined`, instead of nothing here, so it's - // easier to handle this case: - // if (!global) { … } - ]), - "})();" - ]); - } -} - -module.exports = GlobalRuntimeModule; diff --git a/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js b/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js deleted file mode 100644 index 78bf3afeb95..00000000000 --- a/webpack-lib/lib/runtime/HasOwnPropertyRuntimeModule.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sergey Melyukov @smelukov -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ - -class HasOwnPropertyRuntimeModule extends RuntimeModule { - constructor() { - super("hasOwnProperty shorthand"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - - return Template.asString([ - `${RuntimeGlobals.hasOwnProperty} = ${runtimeTemplate.returningFunction( - "Object.prototype.hasOwnProperty.call(obj, prop)", - "obj, prop" - )}` - ]); - } -} - -module.exports = HasOwnPropertyRuntimeModule; diff --git a/webpack-lib/lib/runtime/HelperRuntimeModule.js b/webpack-lib/lib/runtime/HelperRuntimeModule.js deleted file mode 100644 index 012916c9228..00000000000 --- a/webpack-lib/lib/runtime/HelperRuntimeModule.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeModule = require("../RuntimeModule"); - -class HelperRuntimeModule extends RuntimeModule { - /** - * @param {string} name a readable name - */ - constructor(name) { - super(name); - } -} - -module.exports = HelperRuntimeModule; diff --git a/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js b/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js deleted file mode 100644 index b6b2f3e381c..00000000000 --- a/webpack-lib/lib/runtime/LoadScriptRuntimeModule.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const Compilation = require("../Compilation"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} LoadScriptCompilationHooks - * @property {SyncWaterfallHook<[string, Chunk]>} createScript - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class LoadScriptRuntimeModule extends HelperRuntimeModule { - /** - * @param {Compilation} compilation the compilation - * @returns {LoadScriptCompilationHooks} 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 = { - createScript: new SyncWaterfallHook(["source", "chunk"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {boolean=} withCreateScriptUrl use create script url for trusted types - * @param {boolean=} withFetchPriority use `fetchPriority` attribute - */ - constructor(withCreateScriptUrl, withFetchPriority) { - super("load script"); - this._withCreateScriptUrl = withCreateScriptUrl; - this._withFetchPriority = withFetchPriority; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate, outputOptions } = compilation; - const { - scriptType, - chunkLoadTimeout: loadTimeout, - crossOriginLoading, - uniqueName, - charset - } = outputOptions; - const fn = RuntimeGlobals.loadScript; - - const { createScript } = - LoadScriptRuntimeModule.getCompilationHooks(compilation); - - const code = Template.asString([ - "script = document.createElement('script');", - scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "", - charset ? "script.charset = 'utf-8';" : "", - `script.timeout = ${/** @type {number} */ (loadTimeout) / 1000};`, - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - uniqueName - ? 'script.setAttribute("data-webpack", dataWebpackPrefix + key);' - : "", - this._withFetchPriority - ? Template.asString([ - "if(fetchPriority) {", - Template.indent( - 'script.setAttribute("fetchpriority", fetchPriority);' - ), - "}" - ]) - : "", - `script.src = ${ - this._withCreateScriptUrl - ? `${RuntimeGlobals.createScriptUrl}(url)` - : "url" - };`, - crossOriginLoading - ? crossOriginLoading === "use-credentials" - ? 'script.crossOrigin = "use-credentials";' - : Template.asString([ - "if (script.src.indexOf(window.location.origin + '/') !== 0) {", - Template.indent( - `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};` - ), - "}" - ]) - : "" - ]); - - return Template.asString([ - "var inProgress = {};", - uniqueName - ? `var dataWebpackPrefix = ${JSON.stringify(`${uniqueName}:`)};` - : "// data-webpack is not used as build has no uniqueName", - "// loadScript function to load a script via script tag", - `${fn} = ${runtimeTemplate.basicFunction( - `url, done, key, chunkId${ - this._withFetchPriority ? ", fetchPriority" : "" - }`, - [ - "if(inProgress[url]) { inProgress[url].push(done); return; }", - "var script, needAttach;", - "if(key !== undefined) {", - Template.indent([ - 'var scripts = document.getElementsByTagName("script");', - "for(var i = 0; i < scripts.length; i++) {", - Template.indent([ - "var s = scripts[i];", - `if(s.getAttribute("src") == url${ - uniqueName - ? ' || s.getAttribute("data-webpack") == dataWebpackPrefix + key' - : "" - }) { script = s; break; }` - ]), - "}" - ]), - "}", - "if(!script) {", - Template.indent([ - "needAttach = true;", - createScript.call(code, /** @type {Chunk} */ (this.chunk)) - ]), - "}", - "inProgress[url] = [done];", - `var onScriptComplete = ${runtimeTemplate.basicFunction( - "prev, event", - Template.asString([ - "// avoid mem leaks in IE.", - "script.onerror = script.onload = null;", - "clearTimeout(timeout);", - "var doneFns = inProgress[url];", - "delete inProgress[url];", - "script.parentNode && script.parentNode.removeChild(script);", - `doneFns && doneFns.forEach(${runtimeTemplate.returningFunction( - "fn(event)", - "fn" - )});`, - "if(prev) return prev(event);" - ]) - )}`, - `var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), ${loadTimeout});`, - "script.onerror = onScriptComplete.bind(null, script.onerror);", - "script.onload = onScriptComplete.bind(null, script.onload);", - "needAttach && document.head.appendChild(script);" - ] - )};` - ]); - } -} - -module.exports = LoadScriptRuntimeModule; diff --git a/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js b/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js deleted file mode 100644 index 7b43080d020..00000000000 --- a/webpack-lib/lib/runtime/MakeNamespaceObjectRuntimeModule.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class MakeNamespaceObjectRuntimeModule extends HelperRuntimeModule { - constructor() { - super("make namespace object"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - const fn = RuntimeGlobals.makeNamespaceObject; - return Template.asString([ - "// define __esModule on exports", - `${fn} = ${runtimeTemplate.basicFunction("exports", [ - "if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {", - Template.indent([ - "Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });" - ]), - "}", - "Object.defineProperty(exports, '__esModule', { value: true });" - ])};` - ]); - } -} - -module.exports = MakeNamespaceObjectRuntimeModule; diff --git a/webpack-lib/lib/runtime/NonceRuntimeModule.js b/webpack-lib/lib/runtime/NonceRuntimeModule.js deleted file mode 100644 index 238407c1ba6..00000000000 --- a/webpack-lib/lib/runtime/NonceRuntimeModule.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -class NonceRuntimeModule extends RuntimeModule { - constructor() { - super("nonce", RuntimeModule.STAGE_ATTACH); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return `${RuntimeGlobals.scriptNonce} = undefined;`; - } -} - -module.exports = NonceRuntimeModule; diff --git a/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js b/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js deleted file mode 100644 index 2224d02bb4a..00000000000 --- a/webpack-lib/lib/runtime/OnChunksLoadedRuntimeModule.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Compilation")} Compilation */ - -class OnChunksLoadedRuntimeModule extends RuntimeModule { - constructor() { - super("chunk loaded"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return Template.asString([ - "var deferred = [];", - `${RuntimeGlobals.onChunksLoaded} = ${runtimeTemplate.basicFunction( - "result, chunkIds, fn, priority", - [ - "if(chunkIds) {", - Template.indent([ - "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;" - ]), - "}", - "var notFulfilled = Infinity;", - "for (var i = 0; i < deferred.length; i++) {", - Template.indent([ - runtimeTemplate.destructureArray( - ["chunkIds", "fn", "priority"], - "deferred[i]" - ), - "var fulfilled = true;", - "for (var j = 0; j < chunkIds.length; j++) {", - Template.indent([ - `if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(${ - RuntimeGlobals.onChunksLoaded - }).every(${runtimeTemplate.returningFunction( - `${RuntimeGlobals.onChunksLoaded}[key](chunkIds[j])`, - "key" - )})) {`, - Template.indent(["chunkIds.splice(j--, 1);"]), - "} else {", - Template.indent([ - "fulfilled = false;", - "if(priority < notFulfilled) notFulfilled = priority;" - ]), - "}" - ]), - "}", - "if(fulfilled) {", - Template.indent([ - "deferred.splice(i--, 1)", - "var r = fn();", - "if (r !== undefined) result = r;" - ]), - "}" - ]), - "}", - "return result;" - ] - )};` - ]); - } -} - -module.exports = OnChunksLoadedRuntimeModule; diff --git a/webpack-lib/lib/runtime/PublicPathRuntimeModule.js b/webpack-lib/lib/runtime/PublicPathRuntimeModule.js deleted file mode 100644 index 7ea226161c9..00000000000 --- a/webpack-lib/lib/runtime/PublicPathRuntimeModule.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../Compilation")} Compilation */ - -class PublicPathRuntimeModule extends RuntimeModule { - /** - * @param {OutputOptions["publicPath"]} publicPath public path - */ - constructor(publicPath) { - super("publicPath", RuntimeModule.STAGE_BASIC); - this.publicPath = publicPath; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const { publicPath } = this; - const compilation = /** @type {Compilation} */ (this.compilation); - - return `${RuntimeGlobals.publicPath} = ${JSON.stringify( - compilation.getPath(publicPath || "", { - hash: compilation.hash || "XXXX" - }) - )};`; - } -} - -module.exports = PublicPathRuntimeModule; diff --git a/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js b/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js deleted file mode 100644 index 92e32daed98..00000000000 --- a/webpack-lib/lib/runtime/RelativeUrlRuntimeModule.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const HelperRuntimeModule = require("./HelperRuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class RelativeUrlRuntimeModule extends HelperRuntimeModule { - constructor() { - super("relative url"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return Template.asString([ - `${RuntimeGlobals.relativeUrl} = function RelativeURL(url) {`, - Template.indent([ - 'var realUrl = new URL(url, "x:/");', - "var values = {};", - "for (var key in realUrl) values[key] = realUrl[key];", - "values.href = url;", - 'values.pathname = url.replace(/[?#].*/, "");', - 'values.origin = values.protocol = "";', - `values.toString = values.toJSON = ${runtimeTemplate.returningFunction( - "url" - )};`, - "for (var key in values) Object.defineProperty(this, key, { enumerable: true, configurable: true, value: values[key] });" - ]), - "};", - `${RuntimeGlobals.relativeUrl}.prototype = URL.prototype;` - ]); - } -} - -module.exports = RelativeUrlRuntimeModule; diff --git a/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js b/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js deleted file mode 100644 index 1923bafca7e..00000000000 --- a/webpack-lib/lib/runtime/RuntimeIdRuntimeModule.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ - -class RuntimeIdRuntimeModule extends RuntimeModule { - constructor() { - super("runtimeId"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const runtime = chunk.runtime; - if (typeof runtime !== "string") - throw new Error("RuntimeIdRuntimeModule must be in a single runtime"); - const id = chunkGraph.getRuntimeId(runtime); - return `${RuntimeGlobals.runtimeId} = ${JSON.stringify(id)};`; - } -} - -module.exports = RuntimeIdRuntimeModule; diff --git a/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js b/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js deleted file mode 100644 index 6fc74cde8ce..00000000000 --- a/webpack-lib/lib/runtime/StartupChunkDependenciesPlugin.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const StartupChunkDependenciesRuntimeModule = require("./StartupChunkDependenciesRuntimeModule"); -const StartupEntrypointRuntimeModule = require("./StartupEntrypointRuntimeModule"); - -/** @typedef {import("../../declarations/WebpackOptions").ChunkLoadingType} ChunkLoadingType */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} Options - * @property {ChunkLoadingType} chunkLoading - * @property {boolean=} asyncChunkLoading - */ - -class StartupChunkDependenciesPlugin { - /** - * @param {Options} options options - */ - constructor(options) { - this.chunkLoading = options.chunkLoading; - this.asyncChunkLoading = - typeof options.asyncChunkLoading === "boolean" - ? options.asyncChunkLoading - : true; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "StartupChunkDependenciesPlugin", - compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk chunk to check - * @returns {boolean} true, when the plugin is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === this.chunkLoading; - }; - compilation.hooks.additionalTreeRuntimeRequirements.tap( - "StartupChunkDependenciesPlugin", - (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if (chunkGraph.hasChunkEntryDependentChunks(chunk)) { - set.add(RuntimeGlobals.startup); - set.add(RuntimeGlobals.ensureChunk); - set.add(RuntimeGlobals.ensureChunkIncludeEntries); - compilation.addRuntimeModule( - chunk, - new StartupChunkDependenciesRuntimeModule( - this.asyncChunkLoading - ) - ); - } - } - ); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.startupEntrypoint) - .tap("StartupChunkDependenciesPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.require); - set.add(RuntimeGlobals.ensureChunk); - set.add(RuntimeGlobals.ensureChunkIncludeEntries); - compilation.addRuntimeModule( - chunk, - new StartupEntrypointRuntimeModule(this.asyncChunkLoading) - ); - }); - } - ); - } -} - -module.exports = StartupChunkDependenciesPlugin; diff --git a/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js b/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js deleted file mode 100644 index da2ec7548eb..00000000000 --- a/webpack-lib/lib/runtime/StartupChunkDependenciesRuntimeModule.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ - -class StartupChunkDependenciesRuntimeModule extends RuntimeModule { - /** - * @param {boolean} asyncChunkLoading use async chunk loading - */ - constructor(asyncChunkLoading) { - super("startup chunk dependencies", RuntimeModule.STAGE_TRIGGER); - this.asyncChunkLoading = asyncChunkLoading; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const chunkIds = Array.from( - chunkGraph.getChunkEntryDependentChunksIterable(chunk) - ).map(chunk => chunk.id); - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return Template.asString([ - `var next = ${RuntimeGlobals.startup};`, - `${RuntimeGlobals.startup} = ${runtimeTemplate.basicFunction( - "", - !this.asyncChunkLoading - ? chunkIds - .map( - id => `${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)});` - ) - .concat("return next();") - : chunkIds.length === 1 - ? `return ${RuntimeGlobals.ensureChunk}(${JSON.stringify( - chunkIds[0] - )}).then(next);` - : chunkIds.length > 2 - ? [ - // using map is shorter for 3 or more chunks - `return Promise.all(${JSON.stringify(chunkIds)}.map(${ - RuntimeGlobals.ensureChunk - }, ${RuntimeGlobals.require})).then(next);` - ] - : [ - // calling ensureChunk directly is shorter for 0 - 2 chunks - "return Promise.all([", - Template.indent( - chunkIds - .map( - id => - `${RuntimeGlobals.ensureChunk}(${JSON.stringify(id)})` - ) - .join(",\n") - ), - "]).then(next);" - ] - )};` - ]); - } -} - -module.exports = StartupChunkDependenciesRuntimeModule; diff --git a/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js b/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js deleted file mode 100644 index 5133767dab3..00000000000 --- a/webpack-lib/lib/runtime/StartupEntrypointRuntimeModule.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../MainTemplate")} MainTemplate */ - -class StartupEntrypointRuntimeModule extends RuntimeModule { - /** - * @param {boolean} asyncChunkLoading use async chunk loading - */ - constructor(asyncChunkLoading) { - super("startup entrypoint"); - this.asyncChunkLoading = asyncChunkLoading; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { runtimeTemplate } = compilation; - return `${ - RuntimeGlobals.startupEntrypoint - } = ${runtimeTemplate.basicFunction("result, chunkIds, fn", [ - "// arguments: chunkIds, moduleId are deprecated", - "var moduleId = chunkIds;", - `if(!fn) chunkIds = result, fn = ${runtimeTemplate.returningFunction( - `${RuntimeGlobals.require}(${RuntimeGlobals.entryModuleId} = moduleId)` - )};`, - ...(this.asyncChunkLoading - ? [ - `return Promise.all(chunkIds.map(${RuntimeGlobals.ensureChunk}, ${ - RuntimeGlobals.require - })).then(${runtimeTemplate.basicFunction("", [ - "var r = fn();", - "return r === undefined ? result : r;" - ])})` - ] - : [ - `chunkIds.map(${RuntimeGlobals.ensureChunk}, ${RuntimeGlobals.require})`, - "var r = fn();", - "return r === undefined ? result : r;" - ]) - ])}`; - } -} - -module.exports = StartupEntrypointRuntimeModule; diff --git a/webpack-lib/lib/runtime/SystemContextRuntimeModule.js b/webpack-lib/lib/runtime/SystemContextRuntimeModule.js deleted file mode 100644 index b7663ffde1c..00000000000 --- a/webpack-lib/lib/runtime/SystemContextRuntimeModule.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); - -/** @typedef {import("../Compilation")} Compilation */ - -class SystemContextRuntimeModule extends RuntimeModule { - constructor() { - super("__system_context__"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - return `${RuntimeGlobals.systemContext} = __system_context__;`; - } -} - -module.exports = SystemContextRuntimeModule; diff --git a/webpack-lib/lib/schemes/DataUriPlugin.js b/webpack-lib/lib/schemes/DataUriPlugin.js deleted file mode 100644 index 06f0d0feca6..00000000000 --- a/webpack-lib/lib/schemes/DataUriPlugin.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NormalModule = require("../NormalModule"); - -/** @typedef {import("../Compiler")} Compiler */ - -// data URL scheme: "data:text/javascript;charset=utf-8;base64,some-string" -// http://www.ietf.org/rfc/rfc2397.txt -const URIRegEx = /^data:([^;,]+)?((?:;[^;,]+)*?)(?:;(base64)?)?,(.*)$/i; - -/** - * @param {string} uri data URI - * @returns {Buffer | null} decoded data - */ -const decodeDataURI = uri => { - const match = URIRegEx.exec(uri); - if (!match) return null; - - const isBase64 = match[3]; - const body = match[4]; - - if (isBase64) { - return Buffer.from(body, "base64"); - } - - // CSS allows to use `data:image/svg+xml;utf8,` - // so we return original body if we can't `decodeURIComponent` - try { - return Buffer.from(decodeURIComponent(body), "ascii"); - } catch (_) { - return Buffer.from(body, "ascii"); - } -}; - -class DataUriPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "DataUriPlugin", - (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.resolveForScheme - .for("data") - .tap("DataUriPlugin", resourceData => { - const match = URIRegEx.exec(resourceData.resource); - if (match) { - resourceData.data.mimetype = match[1] || ""; - resourceData.data.parameters = match[2] || ""; - resourceData.data.encoding = match[3] || false; - resourceData.data.encodedContent = match[4] || ""; - } - }); - NormalModule.getCompilationHooks(compilation) - .readResourceForScheme.for("data") - .tap("DataUriPlugin", resource => decodeDataURI(resource)); - } - ); - } -} - -module.exports = DataUriPlugin; diff --git a/webpack-lib/lib/schemes/FileUriPlugin.js b/webpack-lib/lib/schemes/FileUriPlugin.js deleted file mode 100644 index 453abbd3b13..00000000000 --- a/webpack-lib/lib/schemes/FileUriPlugin.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { URL, fileURLToPath } = require("url"); -const { NormalModule } = require(".."); - -/** @typedef {import("../Compiler")} Compiler */ - -class FileUriPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - "FileUriPlugin", - (compilation, { normalModuleFactory }) => { - normalModuleFactory.hooks.resolveForScheme - .for("file") - .tap("FileUriPlugin", resourceData => { - const url = new URL(resourceData.resource); - const path = fileURLToPath(url); - const query = url.search; - const fragment = url.hash; - resourceData.path = path; - resourceData.query = query; - resourceData.fragment = fragment; - resourceData.resource = path + query + fragment; - return true; - }); - const hooks = NormalModule.getCompilationHooks(compilation); - hooks.readResource - .for(undefined) - .tapAsync("FileUriPlugin", (loaderContext, callback) => { - const { resourcePath } = loaderContext; - loaderContext.addDependency(resourcePath); - loaderContext.fs.readFile(resourcePath, callback); - }); - } - ); - } -} - -module.exports = FileUriPlugin; diff --git a/webpack-lib/lib/schemes/HttpUriPlugin.js b/webpack-lib/lib/schemes/HttpUriPlugin.js deleted file mode 100644 index 510b189f242..00000000000 --- a/webpack-lib/lib/schemes/HttpUriPlugin.js +++ /dev/null @@ -1,1271 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const EventEmitter = require("events"); -const { extname, basename } = require("path"); -const { URL } = require("url"); -const { createGunzip, createBrotliDecompress, createInflate } = require("zlib"); -const NormalModule = require("../NormalModule"); -const createSchemaValidation = require("../util/create-schema-validation"); -const createHash = require("../util/createHash"); -const { mkdirp, dirname, join } = require("../util/fs"); -const memoize = require("../util/memoize"); - -/** @typedef {import("http").IncomingMessage} IncomingMessage */ -/** @typedef {import("http").RequestOptions} RequestOptions */ -/** @typedef {import("net").Socket} Socket */ -/** @typedef {import("stream").Readable} Readable */ -/** @typedef {import("../../declarations/plugins/schemes/HttpUriPlugin").HttpUriPluginOptions} HttpUriPluginOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../FileSystemInfo").Snapshot} Snapshot */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../NormalModuleFactory").ResourceDataWithData} ResourceDataWithData */ -/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ - -const getHttp = memoize(() => require("http")); -const getHttps = memoize(() => require("https")); - -/** - * @param {typeof import("http") | typeof import("https")} request request - * @param {string | { toString: () => string } | undefined} proxy proxy - * @returns {function(URL, RequestOptions, function(IncomingMessage): void): EventEmitter} fn - */ -const proxyFetch = (request, proxy) => (url, options, callback) => { - const eventEmitter = new EventEmitter(); - - /** - * @param {Socket=} socket socket - * @returns {void} - */ - const doRequest = socket => { - request - .get(url, { ...options, ...(socket && { socket }) }, callback) - .on("error", eventEmitter.emit.bind(eventEmitter, "error")); - }; - - if (proxy) { - const { hostname: host, port } = new URL(proxy); - - getHttp() - .request({ - host, // IP address of proxy server - port, // port of proxy server - method: "CONNECT", - path: url.host - }) - .on("connect", (res, socket) => { - if (res.statusCode === 200) { - // connected to proxy server - doRequest(socket); - } - }) - .on("error", err => { - eventEmitter.emit( - "error", - new Error( - `Failed to connect to proxy server "${proxy}": ${err.message}` - ) - ); - }) - .end(); - } else { - doRequest(); - } - - return eventEmitter; -}; - -/** @typedef {() => void} InProgressWriteItem */ -/** @type {InProgressWriteItem[] | undefined} */ -let inProgressWrite; - -const validate = createSchemaValidation( - require("../../schemas/plugins/schemes/HttpUriPlugin.check.js"), - () => require("../../schemas/plugins/schemes/HttpUriPlugin.json"), - { - name: "Http Uri Plugin", - baseDataPath: "options" - } -); - -/** - * @param {string} str path - * @returns {string} safe path - */ -const toSafePath = str => - str - .replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, "") - .replace(/[^a-zA-Z0-9._-]+/g, "_"); - -/** - * @param {Buffer} content content - * @returns {string} integrity - */ -const computeIntegrity = content => { - const hash = createHash("sha512"); - hash.update(content); - const integrity = `sha512-${hash.digest("base64")}`; - return integrity; -}; - -/** - * @param {Buffer} content content - * @param {string} integrity integrity - * @returns {boolean} true, if integrity matches - */ -const verifyIntegrity = (content, integrity) => { - if (integrity === "ignore") return true; - return computeIntegrity(content) === integrity; -}; - -/** - * @param {string} str input - * @returns {Record} parsed - */ -const parseKeyValuePairs = str => { - /** @type {Record} */ - const result = {}; - for (const item of str.split(",")) { - const i = item.indexOf("="); - if (i >= 0) { - const key = item.slice(0, i).trim(); - const value = item.slice(i + 1).trim(); - result[key] = value; - } else { - const key = item.trim(); - if (!key) continue; - result[key] = key; - } - } - return result; -}; - -/** - * @param {string | undefined} cacheControl Cache-Control header - * @param {number} requestTime timestamp of request - * @returns {{storeCache: boolean, storeLock: boolean, validUntil: number}} Logic for storing in cache and lockfile cache - */ -const parseCacheControl = (cacheControl, requestTime) => { - // When false resource is not stored in cache - let storeCache = true; - // When false resource is not stored in lockfile cache - let storeLock = true; - // Resource is only revalidated, after that timestamp and when upgrade is chosen - let validUntil = 0; - if (cacheControl) { - const parsed = parseKeyValuePairs(cacheControl); - if (parsed["no-cache"]) storeCache = storeLock = false; - if (parsed["max-age"] && !Number.isNaN(Number(parsed["max-age"]))) { - validUntil = requestTime + Number(parsed["max-age"]) * 1000; - } - if (parsed["must-revalidate"]) validUntil = 0; - } - return { - storeLock, - storeCache, - validUntil - }; -}; - -/** - * @typedef {object} LockfileEntry - * @property {string} resolved - * @property {string} integrity - * @property {string} contentType - */ - -/** - * @param {LockfileEntry} a first lockfile entry - * @param {LockfileEntry} b second lockfile entry - * @returns {boolean} true when equal, otherwise false - */ -const areLockfileEntriesEqual = (a, b) => - a.resolved === b.resolved && - a.integrity === b.integrity && - a.contentType === b.contentType; - -/** - * @param {LockfileEntry} entry lockfile entry - * @returns {`resolved: ${string}, integrity: ${string}, contentType: ${*}`} stringified entry - */ -const entryToString = entry => - `resolved: ${entry.resolved}, integrity: ${entry.integrity}, contentType: ${entry.contentType}`; - -class Lockfile { - constructor() { - this.version = 1; - /** @type {Map} */ - this.entries = new Map(); - } - - /** - * @param {string} content content of the lockfile - * @returns {Lockfile} lockfile - */ - static parse(content) { - // TODO handle merge conflicts - const data = JSON.parse(content); - if (data.version !== 1) - throw new Error(`Unsupported lockfile version ${data.version}`); - const lockfile = new Lockfile(); - for (const key of Object.keys(data)) { - if (key === "version") continue; - const entry = data[key]; - lockfile.entries.set( - key, - typeof entry === "string" - ? entry - : { - resolved: key, - ...entry - } - ); - } - return lockfile; - } - - /** - * @returns {string} stringified lockfile - */ - toString() { - let str = "{\n"; - const entries = Array.from(this.entries).sort(([a], [b]) => - a < b ? -1 : 1 - ); - for (const [key, entry] of entries) { - if (typeof entry === "string") { - str += ` ${JSON.stringify(key)}: ${JSON.stringify(entry)},\n`; - } else { - str += ` ${JSON.stringify(key)}: { `; - if (entry.resolved !== key) - str += `"resolved": ${JSON.stringify(entry.resolved)}, `; - str += `"integrity": ${JSON.stringify( - entry.integrity - )}, "contentType": ${JSON.stringify(entry.contentType)} },\n`; - } - } - str += ` "version": ${this.version}\n}\n`; - return str; - } -} - -/** - * @template R - * @param {function(function(Error | null, R=): void): void} fn function - * @returns {function(function(Error | null, R=): void): void} cached function - */ -const cachedWithoutKey = fn => { - let inFlight = false; - /** @type {Error | undefined} */ - let cachedError; - /** @type {R | undefined} */ - let cachedResult; - /** @type {(function(Error| null, R=): void)[] | undefined} */ - let cachedCallbacks; - return callback => { - if (inFlight) { - if (cachedResult !== undefined) return callback(null, cachedResult); - if (cachedError !== undefined) return callback(cachedError); - if (cachedCallbacks === undefined) cachedCallbacks = [callback]; - else cachedCallbacks.push(callback); - return; - } - inFlight = true; - fn((err, result) => { - if (err) cachedError = err; - else cachedResult = result; - const callbacks = cachedCallbacks; - cachedCallbacks = undefined; - callback(err, result); - if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); - }); - }; -}; - -/** - * @template T - * @template R - * @param {function(T, function(Error | null, R=): void): void} fn function - * @param {function(T, function(Error | null, R=): void): void=} forceFn function for the second try - * @returns {(function(T, function(Error | null, R=): void): void) & { force: function(T, function(Error | null, R=): void): void }} cached function - */ -const cachedWithKey = (fn, forceFn = fn) => { - /** - * @template R - * @typedef {{ result?: R, error?: Error, callbacks?: (function(Error | null, R=): void)[], force?: true }} CacheEntry - */ - /** @type {Map>} */ - const cache = new Map(); - /** - * @param {T} arg arg - * @param {function(Error | null, R=): void} callback callback - * @returns {void} - */ - const resultFn = (arg, callback) => { - const cacheEntry = cache.get(arg); - if (cacheEntry !== undefined) { - if (cacheEntry.result !== undefined) - return callback(null, cacheEntry.result); - if (cacheEntry.error !== undefined) return callback(cacheEntry.error); - if (cacheEntry.callbacks === undefined) cacheEntry.callbacks = [callback]; - else cacheEntry.callbacks.push(callback); - return; - } - /** @type {CacheEntry} */ - const newCacheEntry = { - result: undefined, - error: undefined, - callbacks: undefined - }; - cache.set(arg, newCacheEntry); - fn(arg, (err, result) => { - if (err) newCacheEntry.error = err; - else newCacheEntry.result = result; - const callbacks = newCacheEntry.callbacks; - newCacheEntry.callbacks = undefined; - callback(err, result); - if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); - }); - }; - /** - * @param {T} arg arg - * @param {function(Error | null, R=): void} callback callback - * @returns {void} - */ - resultFn.force = (arg, callback) => { - const cacheEntry = cache.get(arg); - if (cacheEntry !== undefined && cacheEntry.force) { - if (cacheEntry.result !== undefined) - return callback(null, cacheEntry.result); - if (cacheEntry.error !== undefined) return callback(cacheEntry.error); - if (cacheEntry.callbacks === undefined) cacheEntry.callbacks = [callback]; - else cacheEntry.callbacks.push(callback); - return; - } - /** @type {CacheEntry} */ - const newCacheEntry = { - result: undefined, - error: undefined, - callbacks: undefined, - force: true - }; - cache.set(arg, newCacheEntry); - forceFn(arg, (err, result) => { - if (err) newCacheEntry.error = err; - else newCacheEntry.result = result; - const callbacks = newCacheEntry.callbacks; - newCacheEntry.callbacks = undefined; - callback(err, result); - if (callbacks !== undefined) for (const cb of callbacks) cb(err, result); - }); - }; - return resultFn; -}; - -/** - * @typedef {object} LockfileCache - * @property {Lockfile} lockfile lockfile - * @property {Snapshot} snapshot snapshot - */ - -/** - * @typedef {object} ResolveContentResult - * @property {LockfileEntry} entry lockfile entry - * @property {Buffer} content content - * @property {boolean} storeLock need store lockfile - */ - -/** @typedef {{ storeCache: boolean, storeLock: boolean, validUntil: number, etag: string | undefined, fresh: boolean }} FetchResultMeta */ -/** @typedef {FetchResultMeta & { location: string }} RedirectFetchResult */ -/** @typedef {FetchResultMeta & { entry: LockfileEntry, content: Buffer }} ContentFetchResult */ -/** @typedef {RedirectFetchResult | ContentFetchResult} FetchResult */ - -class HttpUriPlugin { - /** - * @param {HttpUriPluginOptions} options options - */ - constructor(options) { - validate(options); - this._lockfileLocation = options.lockfileLocation; - this._cacheLocation = options.cacheLocation; - this._upgrade = options.upgrade; - this._frozen = options.frozen; - this._allowedUris = options.allowedUris; - this._proxy = options.proxy; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const proxy = - this._proxy || process.env.http_proxy || process.env.HTTP_PROXY; - const schemes = [ - { - scheme: "http", - fetch: proxyFetch(getHttp(), proxy) - }, - { - scheme: "https", - fetch: proxyFetch(getHttps(), proxy) - } - ]; - /** @type {LockfileCache} */ - let lockfileCache; - compiler.hooks.compilation.tap( - "HttpUriPlugin", - (compilation, { normalModuleFactory }) => { - const intermediateFs = - /** @type {IntermediateFileSystem} */ - (compiler.intermediateFileSystem); - const fs = compilation.inputFileSystem; - const cache = compilation.getCache("webpack.HttpUriPlugin"); - const logger = compilation.getLogger("webpack.HttpUriPlugin"); - /** @type {string} */ - const lockfileLocation = - this._lockfileLocation || - join( - intermediateFs, - compiler.context, - compiler.name - ? `${toSafePath(compiler.name)}.webpack.lock` - : "webpack.lock" - ); - /** @type {string | false} */ - const cacheLocation = - this._cacheLocation !== undefined - ? this._cacheLocation - : `${lockfileLocation}.data`; - const upgrade = this._upgrade || false; - const frozen = this._frozen || false; - const hashFunction = "sha512"; - const hashDigest = "hex"; - const hashDigestLength = 20; - const allowedUris = this._allowedUris; - - let warnedAboutEol = false; - - /** @type {Map} */ - const cacheKeyCache = new Map(); - /** - * @param {string} url the url - * @returns {string} the key - */ - const getCacheKey = url => { - const cachedResult = cacheKeyCache.get(url); - if (cachedResult !== undefined) return cachedResult; - const result = _getCacheKey(url); - cacheKeyCache.set(url, result); - return result; - }; - - /** - * @param {string} url the url - * @returns {string} the key - */ - const _getCacheKey = url => { - const parsedUrl = new URL(url); - const folder = toSafePath(parsedUrl.origin); - const name = toSafePath(parsedUrl.pathname); - const query = toSafePath(parsedUrl.search); - let ext = extname(name); - if (ext.length > 20) ext = ""; - const basename = ext ? name.slice(0, -ext.length) : name; - const hash = createHash(hashFunction); - hash.update(url); - const digest = hash.digest(hashDigest).slice(0, hashDigestLength); - return `${folder.slice(-50)}/${`${basename}${ - query ? `_${query}` : "" - }`.slice(0, 150)}_${digest}${ext}`; - }; - - const getLockfile = cachedWithoutKey( - /** - * @param {function(Error | null, Lockfile=): void} callback callback - * @returns {void} - */ - callback => { - const readLockfile = () => { - intermediateFs.readFile(lockfileLocation, (err, buffer) => { - if (err && err.code !== "ENOENT") { - compilation.missingDependencies.add(lockfileLocation); - return callback(err); - } - compilation.fileDependencies.add(lockfileLocation); - compilation.fileSystemInfo.createSnapshot( - compiler.fsStartTime, - buffer ? [lockfileLocation] : [], - [], - buffer ? [] : [lockfileLocation], - { timestamp: true }, - (err, s) => { - if (err) return callback(err); - const lockfile = buffer - ? Lockfile.parse(buffer.toString("utf-8")) - : new Lockfile(); - lockfileCache = { - lockfile, - snapshot: /** @type {Snapshot} */ (s) - }; - callback(null, lockfile); - } - ); - }); - }; - if (lockfileCache) { - compilation.fileSystemInfo.checkSnapshotValid( - lockfileCache.snapshot, - (err, valid) => { - if (err) return callback(err); - if (!valid) return readLockfile(); - callback(null, lockfileCache.lockfile); - } - ); - } else { - readLockfile(); - } - } - ); - - /** @typedef {Map} LockfileUpdates */ - - /** @type {LockfileUpdates | undefined} */ - let lockfileUpdates; - - /** - * @param {Lockfile} lockfile lockfile instance - * @param {string} url url to store - * @param {LockfileEntry | "ignore" | "no-cache"} entry lockfile entry - */ - const storeLockEntry = (lockfile, url, entry) => { - const oldEntry = lockfile.entries.get(url); - if (lockfileUpdates === undefined) lockfileUpdates = new Map(); - lockfileUpdates.set(url, entry); - lockfile.entries.set(url, entry); - if (!oldEntry) { - logger.log(`${url} added to lockfile`); - } else if (typeof oldEntry === "string") { - if (typeof entry === "string") { - logger.log(`${url} updated in lockfile: ${oldEntry} -> ${entry}`); - } else { - logger.log( - `${url} updated in lockfile: ${oldEntry} -> ${entry.resolved}` - ); - } - } else if (typeof entry === "string") { - logger.log( - `${url} updated in lockfile: ${oldEntry.resolved} -> ${entry}` - ); - } else if (oldEntry.resolved !== entry.resolved) { - logger.log( - `${url} updated in lockfile: ${oldEntry.resolved} -> ${entry.resolved}` - ); - } else if (oldEntry.integrity !== entry.integrity) { - logger.log(`${url} updated in lockfile: content changed`); - } else if (oldEntry.contentType !== entry.contentType) { - logger.log( - `${url} updated in lockfile: ${oldEntry.contentType} -> ${entry.contentType}` - ); - } else { - logger.log(`${url} updated in lockfile`); - } - }; - - /** - * @param {Lockfile} lockfile lockfile - * @param {string} url url - * @param {ResolveContentResult} result result - * @param {function(Error | null, ResolveContentResult=): void} callback callback - * @returns {void} - */ - const storeResult = (lockfile, url, result, callback) => { - if (result.storeLock) { - storeLockEntry(lockfile, url, result.entry); - if (!cacheLocation || !result.content) - return callback(null, result); - const key = getCacheKey(result.entry.resolved); - const filePath = join(intermediateFs, cacheLocation, key); - mkdirp(intermediateFs, dirname(intermediateFs, filePath), err => { - if (err) return callback(err); - intermediateFs.writeFile(filePath, result.content, err => { - if (err) return callback(err); - callback(null, result); - }); - }); - } else { - storeLockEntry(lockfile, url, "no-cache"); - callback(null, result); - } - }; - - for (const { scheme, fetch } of schemes) { - /** - * @param {string} url URL - * @param {string | null} integrity integrity - * @param {function(Error | null, ResolveContentResult=): void} callback callback - */ - const resolveContent = (url, integrity, callback) => { - /** - * @param {Error | null} err error - * @param {TODO} result result result - * @returns {void} - */ - const handleResult = (err, result) => { - if (err) return callback(err); - if ("location" in result) { - return resolveContent( - result.location, - integrity, - (err, innerResult) => { - if (err) return callback(err); - const { entry, content, storeLock } = - /** @type {ResolveContentResult} */ (innerResult); - callback(null, { - entry, - content, - storeLock: storeLock && result.storeLock - }); - } - ); - } - if ( - !result.fresh && - integrity && - result.entry.integrity !== integrity && - !verifyIntegrity(result.content, integrity) - ) { - return fetchContent.force(url, handleResult); - } - return callback(null, { - entry: result.entry, - content: result.content, - storeLock: result.storeLock - }); - }; - fetchContent(url, handleResult); - }; - - /** - * @param {string} url URL - * @param {FetchResult | RedirectFetchResult | undefined} cachedResult result from cache - * @param {function(Error | null, FetchResult=): void} callback callback - * @returns {void} - */ - const fetchContentRaw = (url, cachedResult, callback) => { - const requestTime = Date.now(); - fetch( - new URL(url), - { - headers: { - "accept-encoding": "gzip, deflate, br", - "user-agent": "webpack", - "if-none-match": /** @type {TODO} */ ( - cachedResult ? cachedResult.etag || null : null - ) - } - }, - res => { - const etag = res.headers.etag; - const location = res.headers.location; - const cacheControl = res.headers["cache-control"]; - const { storeLock, storeCache, validUntil } = parseCacheControl( - cacheControl, - requestTime - ); - /** - * @param {Partial> & (Pick | Pick)} partialResult result - * @returns {void} - */ - const finishWith = partialResult => { - if ("location" in partialResult) { - logger.debug( - `GET ${url} [${res.statusCode}] -> ${partialResult.location}` - ); - } else { - logger.debug( - `GET ${url} [${res.statusCode}] ${Math.ceil( - partialResult.content.length / 1024 - )} kB${!storeLock ? " no-cache" : ""}` - ); - } - const result = { - ...partialResult, - fresh: true, - storeLock, - storeCache, - validUntil, - etag - }; - if (!storeCache) { - logger.log( - `${url} can't be stored in cache, due to Cache-Control header: ${cacheControl}` - ); - return callback(null, result); - } - cache.store( - url, - null, - { - ...result, - fresh: false - }, - err => { - if (err) { - logger.warn( - `${url} can't be stored in cache: ${err.message}` - ); - logger.debug(err.stack); - } - callback(null, result); - } - ); - }; - if (res.statusCode === 304) { - const result = /** @type {FetchResult} */ (cachedResult); - if ( - result.validUntil < validUntil || - result.storeLock !== storeLock || - result.storeCache !== storeCache || - result.etag !== etag - ) { - return finishWith(result); - } - logger.debug(`GET ${url} [${res.statusCode}] (unchanged)`); - return callback(null, { ...result, fresh: true }); - } - if ( - location && - res.statusCode && - res.statusCode >= 301 && - res.statusCode <= 308 - ) { - const result = { - location: new URL(location, url).href - }; - if ( - !cachedResult || - !("location" in cachedResult) || - cachedResult.location !== result.location || - cachedResult.validUntil < validUntil || - cachedResult.storeLock !== storeLock || - cachedResult.storeCache !== storeCache || - cachedResult.etag !== etag - ) { - return finishWith(result); - } - logger.debug(`GET ${url} [${res.statusCode}] (unchanged)`); - return callback(null, { - ...result, - fresh: true, - storeLock, - storeCache, - validUntil, - etag - }); - } - const contentType = res.headers["content-type"] || ""; - /** @type {Buffer[]} */ - const bufferArr = []; - - const contentEncoding = res.headers["content-encoding"]; - /** @type {Readable} */ - let stream = res; - if (contentEncoding === "gzip") { - stream = stream.pipe(createGunzip()); - } else if (contentEncoding === "br") { - stream = stream.pipe(createBrotliDecompress()); - } else if (contentEncoding === "deflate") { - stream = stream.pipe(createInflate()); - } - - stream.on("data", chunk => { - bufferArr.push(chunk); - }); - - stream.on("end", () => { - if (!res.complete) { - logger.log(`GET ${url} [${res.statusCode}] (terminated)`); - return callback(new Error(`${url} request was terminated`)); - } - - const content = Buffer.concat(bufferArr); - - if (res.statusCode !== 200) { - logger.log(`GET ${url} [${res.statusCode}]`); - return callback( - new Error( - `${url} request status code = ${ - res.statusCode - }\n${content.toString("utf-8")}` - ) - ); - } - - const integrity = computeIntegrity(content); - const entry = { resolved: url, integrity, contentType }; - - finishWith({ - entry, - content - }); - }); - } - ).on("error", err => { - logger.log(`GET ${url} (error)`); - err.message += `\nwhile fetching ${url}`; - callback(err); - }); - }; - - const fetchContent = cachedWithKey( - /** - * @param {string} url URL - * @param {function(Error | null, { validUntil: number, etag?: string, entry: LockfileEntry, content: Buffer, fresh: boolean } | { validUntil: number, etag?: string, location: string, fresh: boolean }=): void} callback callback - * @returns {void} - */ - (url, callback) => { - cache.get(url, null, (err, cachedResult) => { - if (err) return callback(err); - if (cachedResult) { - const isValid = cachedResult.validUntil >= Date.now(); - if (isValid) return callback(null, cachedResult); - } - fetchContentRaw(url, cachedResult, callback); - }); - }, - (url, callback) => fetchContentRaw(url, undefined, callback) - ); - - /** - * @param {string} uri uri - * @returns {boolean} true when allowed, otherwise false - */ - const isAllowed = uri => { - for (const allowed of allowedUris) { - if (typeof allowed === "string") { - if (uri.startsWith(allowed)) return true; - } else if (typeof allowed === "function") { - if (allowed(uri)) return true; - } else if (allowed.test(uri)) { - return true; - } - } - return false; - }; - - /** @typedef {{ entry: LockfileEntry, content: Buffer }} Info */ - - const getInfo = cachedWithKey( - /** - * @param {string} url the url - * @param {function(Error | null, Info=): void} callback callback - * @returns {void} - */ - // eslint-disable-next-line no-loop-func - (url, callback) => { - if (!isAllowed(url)) { - return callback( - new Error( - `${url} doesn't match the allowedUris policy. These URIs are allowed:\n${allowedUris - .map(uri => ` - ${uri}`) - .join("\n")}` - ) - ); - } - getLockfile((err, _lockfile) => { - if (err) return callback(err); - const lockfile = /** @type {Lockfile} */ (_lockfile); - const entryOrString = lockfile.entries.get(url); - if (!entryOrString) { - if (frozen) { - return callback( - new Error( - `${url} has no lockfile entry and lockfile is frozen` - ) - ); - } - resolveContent(url, null, (err, result) => { - if (err) return callback(err); - storeResult( - /** @type {Lockfile} */ - (lockfile), - url, - /** @type {ResolveContentResult} */ - (result), - callback - ); - }); - return; - } - if (typeof entryOrString === "string") { - const entryTag = entryOrString; - resolveContent(url, null, (err, _result) => { - if (err) return callback(err); - const result = - /** @type {ResolveContentResult} */ - (_result); - if (!result.storeLock || entryTag === "ignore") - return callback(null, result); - if (frozen) { - return callback( - new Error( - `${url} used to have ${entryTag} lockfile entry and has content now, but lockfile is frozen` - ) - ); - } - if (!upgrade) { - return callback( - new Error( - `${url} used to have ${entryTag} lockfile entry and has content now. -This should be reflected in the lockfile, so this lockfile entry must be upgraded, but upgrading is not enabled. -Remove this line from the lockfile to force upgrading.` - ) - ); - } - storeResult(lockfile, url, result, callback); - }); - return; - } - let entry = entryOrString; - /** - * @param {Buffer=} lockedContent locked content - */ - const doFetch = lockedContent => { - resolveContent(url, entry.integrity, (err, _result) => { - if (err) { - if (lockedContent) { - logger.warn( - `Upgrade request to ${url} failed: ${err.message}` - ); - logger.debug(err.stack); - return callback(null, { - entry, - content: lockedContent - }); - } - return callback(err); - } - const result = - /** @type {ResolveContentResult} */ - (_result); - if (!result.storeLock) { - // When the lockfile entry should be no-cache - // we need to update the lockfile - if (frozen) { - return callback( - new Error( - `${url} has a lockfile entry and is no-cache now, but lockfile is frozen\nLockfile: ${entryToString( - entry - )}` - ) - ); - } - storeResult(lockfile, url, result, callback); - return; - } - if (!areLockfileEntriesEqual(result.entry, entry)) { - // When the lockfile entry is outdated - // we need to update the lockfile - if (frozen) { - return callback( - new Error( - `${url} has an outdated lockfile entry, but lockfile is frozen\nLockfile: ${entryToString( - entry - )}\nExpected: ${entryToString(result.entry)}` - ) - ); - } - storeResult(lockfile, url, result, callback); - return; - } - if (!lockedContent && cacheLocation) { - // When the lockfile cache content is missing - // we need to update the lockfile - if (frozen) { - return callback( - new Error( - `${url} is missing content in the lockfile cache, but lockfile is frozen\nLockfile: ${entryToString( - entry - )}` - ) - ); - } - storeResult(lockfile, url, result, callback); - return; - } - return callback(null, result); - }); - }; - if (cacheLocation) { - // When there is a lockfile cache - // we read the content from there - const key = getCacheKey(entry.resolved); - const filePath = join(intermediateFs, cacheLocation, key); - fs.readFile(filePath, (err, result) => { - if (err) { - if (err.code === "ENOENT") return doFetch(); - return callback(err); - } - const content = /** @type {Buffer} */ (result); - /** - * @param {Buffer | undefined} _result result - * @returns {void} - */ - const continueWithCachedContent = _result => { - if (!upgrade) { - // When not in upgrade mode, we accept the result from the lockfile cache - return callback(null, { entry, content }); - } - return doFetch(content); - }; - if (!verifyIntegrity(content, entry.integrity)) { - /** @type {Buffer | undefined} */ - let contentWithChangedEol; - let isEolChanged = false; - try { - contentWithChangedEol = Buffer.from( - content.toString("utf-8").replace(/\r\n/g, "\n") - ); - isEolChanged = verifyIntegrity( - contentWithChangedEol, - entry.integrity - ); - } catch (_err) { - // ignore - } - if (isEolChanged) { - if (!warnedAboutEol) { - const explainer = `Incorrect end of line sequence was detected in the lockfile cache. -The lockfile cache is protected by integrity checks, so any external modification will lead to a corrupted lockfile cache. -When using git make sure to configure .gitattributes correctly for the lockfile cache: - **/*webpack.lock.data/** -text -This will avoid that the end of line sequence is changed by git on Windows.`; - if (frozen) { - logger.error(explainer); - } else { - logger.warn(explainer); - logger.info( - "Lockfile cache will be automatically fixed now, but when lockfile is frozen this would result in an error." - ); - } - warnedAboutEol = true; - } - if (!frozen) { - // "fix" the end of line sequence of the lockfile content - logger.log( - `${filePath} fixed end of line sequence (\\r\\n instead of \\n).` - ); - intermediateFs.writeFile( - filePath, - /** @type {Buffer} */ - (contentWithChangedEol), - err => { - if (err) return callback(err); - continueWithCachedContent( - /** @type {Buffer} */ - (contentWithChangedEol) - ); - } - ); - return; - } - } - if (frozen) { - return callback( - new Error( - `${ - entry.resolved - } integrity mismatch, expected content with integrity ${ - entry.integrity - } but got ${computeIntegrity(content)}. -Lockfile corrupted (${ - isEolChanged - ? "end of line sequence was unexpectedly changed" - : "incorrectly merged? changed by other tools?" - }). -Run build with un-frozen lockfile to automatically fix lockfile.` - ) - ); - } - // "fix" the lockfile entry to the correct integrity - // the content has priority over the integrity value - entry = { - ...entry, - integrity: computeIntegrity(content) - }; - storeLockEntry(lockfile, url, entry); - } - continueWithCachedContent(result); - }); - } else { - doFetch(); - } - }); - } - ); - - /** - * @param {URL} url url - * @param {ResourceDataWithData} resourceData resource data - * @param {function(Error | null, true | void): void} callback callback - */ - const respondWithUrlModule = (url, resourceData, callback) => { - getInfo(url.href, (err, _result) => { - if (err) return callback(err); - const result = /** @type {Info} */ (_result); - resourceData.resource = url.href; - resourceData.path = url.origin + url.pathname; - resourceData.query = url.search; - resourceData.fragment = url.hash; - resourceData.context = new URL( - ".", - result.entry.resolved - ).href.slice(0, -1); - resourceData.data.mimetype = result.entry.contentType; - callback(null, true); - }); - }; - normalModuleFactory.hooks.resolveForScheme - .for(scheme) - .tapAsync( - "HttpUriPlugin", - (resourceData, resolveData, callback) => { - respondWithUrlModule( - new URL(resourceData.resource), - resourceData, - callback - ); - } - ); - normalModuleFactory.hooks.resolveInScheme - .for(scheme) - .tapAsync("HttpUriPlugin", (resourceData, data, callback) => { - // Only handle relative urls (./xxx, ../xxx, /xxx, //xxx) - if ( - data.dependencyType !== "url" && - !/^\.{0,2}\//.test(resourceData.resource) - ) { - return callback(); - } - respondWithUrlModule( - new URL(resourceData.resource, `${data.context}/`), - resourceData, - callback - ); - }); - const hooks = NormalModule.getCompilationHooks(compilation); - hooks.readResourceForScheme - .for(scheme) - .tapAsync("HttpUriPlugin", (resource, module, callback) => - getInfo(resource, (err, _result) => { - if (err) return callback(err); - const result = /** @type {Info} */ (_result); - /** @type {BuildInfo} */ - (module.buildInfo).resourceIntegrity = result.entry.integrity; - callback(null, result.content); - }) - ); - hooks.needBuild.tapAsync( - "HttpUriPlugin", - (module, context, callback) => { - if ( - module.resource && - module.resource.startsWith(`${scheme}://`) - ) { - getInfo(module.resource, (err, _result) => { - if (err) return callback(err); - const result = /** @type {Info} */ (_result); - if ( - result.entry.integrity !== - /** @type {BuildInfo} */ - (module.buildInfo).resourceIntegrity - ) { - return callback(null, true); - } - callback(); - }); - } else { - return callback(); - } - } - ); - } - compilation.hooks.finishModules.tapAsync( - "HttpUriPlugin", - (modules, callback) => { - if (!lockfileUpdates) return callback(); - const ext = extname(lockfileLocation); - const tempFile = join( - intermediateFs, - dirname(intermediateFs, lockfileLocation), - `.${basename(lockfileLocation, ext)}.${ - (Math.random() * 10000) | 0 - }${ext}` - ); - - const writeDone = () => { - const nextOperation = - /** @type {InProgressWriteItem[]} */ - (inProgressWrite).shift(); - if (nextOperation) { - nextOperation(); - } else { - inProgressWrite = undefined; - } - }; - const runWrite = () => { - intermediateFs.readFile(lockfileLocation, (err, buffer) => { - if (err && err.code !== "ENOENT") { - writeDone(); - return callback(err); - } - const lockfile = buffer - ? Lockfile.parse(buffer.toString("utf-8")) - : new Lockfile(); - for (const [key, value] of /** @type {LockfileUpdates} */ ( - lockfileUpdates - )) { - lockfile.entries.set(key, value); - } - intermediateFs.writeFile(tempFile, lockfile.toString(), err => { - if (err) { - writeDone(); - return ( - /** @type {NonNullable} */ - (intermediateFs.unlink)(tempFile, () => callback(err)) - ); - } - intermediateFs.rename(tempFile, lockfileLocation, err => { - if (err) { - writeDone(); - return ( - /** @type {NonNullable} */ - (intermediateFs.unlink)(tempFile, () => callback(err)) - ); - } - writeDone(); - callback(); - }); - }); - }); - }; - if (inProgressWrite) { - inProgressWrite.push(runWrite); - } else { - inProgressWrite = []; - runWrite(); - } - } - ); - } - ); - } -} - -module.exports = HttpUriPlugin; diff --git a/webpack-lib/lib/serialization/ArraySerializer.js b/webpack-lib/lib/serialization/ArraySerializer.js deleted file mode 100644 index 021c82ca5d4..00000000000 --- a/webpack-lib/lib/serialization/ArraySerializer.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ArraySerializer { - /** - * @template T - * @param {T[]} array array - * @param {ObjectSerializerContext} context context - */ - serialize(array, context) { - context.write(array.length); - for (const item of array) context.write(item); - } - - /** - * @template T - * @param {ObjectDeserializerContext} context context - * @returns {T[]} array - */ - deserialize(context) { - /** @type {number} */ - const length = context.read(); - /** @type {T[]} */ - const array = []; - for (let i = 0; i < length; i++) { - array.push(context.read()); - } - return array; - } -} - -module.exports = ArraySerializer; diff --git a/webpack-lib/lib/serialization/BinaryMiddleware.js b/webpack-lib/lib/serialization/BinaryMiddleware.js deleted file mode 100644 index 2db7487a253..00000000000 --- a/webpack-lib/lib/serialization/BinaryMiddleware.js +++ /dev/null @@ -1,1142 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const memoize = require("../util/memoize"); -const SerializerMiddleware = require("./SerializerMiddleware"); - -/** @typedef {import("./types").BufferSerializableType} BufferSerializableType */ -/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */ - -/* -Format: - -File -> Section* - -Section -> NullsSection | - BooleansSection | - F64NumbersSection | - I32NumbersSection | - I8NumbersSection | - ShortStringSection | - BigIntSection | - I32BigIntSection | - I8BigIntSection - StringSection | - BufferSection | - NopSection - - - -NullsSection -> - NullHeaderByte | Null2HeaderByte | Null3HeaderByte | - Nulls8HeaderByte 0xnn (n:count - 4) | - Nulls32HeaderByte n:ui32 (n:count - 260) | -BooleansSection -> TrueHeaderByte | FalseHeaderByte | BooleansSectionHeaderByte BooleansCountAndBitsByte -F64NumbersSection -> F64NumbersSectionHeaderByte f64* -I32NumbersSection -> I32NumbersSectionHeaderByte i32* -I8NumbersSection -> I8NumbersSectionHeaderByte i8* -ShortStringSection -> ShortStringSectionHeaderByte ascii-byte* -StringSection -> StringSectionHeaderByte i32:length utf8-byte* -BufferSection -> BufferSectionHeaderByte i32:length byte* -NopSection --> NopSectionHeaderByte -BigIntSection -> BigIntSectionHeaderByte i32:length ascii-byte* -I32BigIntSection -> I32BigIntSectionHeaderByte i32 -I8BigIntSection -> I8BigIntSectionHeaderByte i8 - -ShortStringSectionHeaderByte -> 0b1nnn_nnnn (n:length) - -F64NumbersSectionHeaderByte -> 0b001n_nnnn (n:count - 1) -I32NumbersSectionHeaderByte -> 0b010n_nnnn (n:count - 1) -I8NumbersSectionHeaderByte -> 0b011n_nnnn (n:count - 1) - -NullsSectionHeaderByte -> 0b0001_nnnn (n:count - 1) -BooleansCountAndBitsByte -> - 0b0000_1xxx (count = 3) | - 0b0001_xxxx (count = 4) | - 0b001x_xxxx (count = 5) | - 0b01xx_xxxx (count = 6) | - 0b1nnn_nnnn (n:count - 7, 7 <= count <= 133) - 0xff n:ui32 (n:count, 134 <= count < 2^32) - -StringSectionHeaderByte -> 0b0000_1110 -BufferSectionHeaderByte -> 0b0000_1111 -NopSectionHeaderByte -> 0b0000_1011 -BigIntSectionHeaderByte -> 0b0001_1010 -I32BigIntSectionHeaderByte -> 0b0001_1100 -I8BigIntSectionHeaderByte -> 0b0001_1011 -FalseHeaderByte -> 0b0000_1100 -TrueHeaderByte -> 0b0000_1101 - -RawNumber -> n (n <= 10) - -*/ - -const LAZY_HEADER = 0x0b; -const TRUE_HEADER = 0x0c; -const FALSE_HEADER = 0x0d; -const BOOLEANS_HEADER = 0x0e; -const NULL_HEADER = 0x10; -const NULL2_HEADER = 0x11; -const NULL3_HEADER = 0x12; -const NULLS8_HEADER = 0x13; -const NULLS32_HEADER = 0x14; -const NULL_AND_I8_HEADER = 0x15; -const NULL_AND_I32_HEADER = 0x16; -const NULL_AND_TRUE_HEADER = 0x17; -const NULL_AND_FALSE_HEADER = 0x18; -const BIGINT_HEADER = 0x1a; -const BIGINT_I8_HEADER = 0x1b; -const BIGINT_I32_HEADER = 0x1c; -const STRING_HEADER = 0x1e; -const BUFFER_HEADER = 0x1f; -const I8_HEADER = 0x60; -const I32_HEADER = 0x40; -const F64_HEADER = 0x20; -const SHORT_STRING_HEADER = 0x80; - -/** Uplift high-order bits */ -const NUMBERS_HEADER_MASK = 0xe0; // 0b1010_0000 -const NUMBERS_COUNT_MASK = 0x1f; // 0b0001_1111 -const SHORT_STRING_LENGTH_MASK = 0x7f; // 0b0111_1111 - -const HEADER_SIZE = 1; -const I8_SIZE = 1; -const I32_SIZE = 4; -const F64_SIZE = 8; - -const MEASURE_START_OPERATION = Symbol("MEASURE_START_OPERATION"); -const MEASURE_END_OPERATION = Symbol("MEASURE_END_OPERATION"); - -/** @typedef {typeof MEASURE_START_OPERATION} MEASURE_START_OPERATION_TYPE */ -/** @typedef {typeof MEASURE_END_OPERATION} MEASURE_END_OPERATION_TYPE */ - -/** - * @param {number} n number - * @returns {0 | 1 | 2} type of number for serialization - */ -const identifyNumber = n => { - if (n === (n | 0)) { - if (n <= 127 && n >= -128) return 0; - if (n <= 2147483647 && n >= -2147483648) return 1; - } - return 2; -}; - -/** - * @param {bigint} n bigint - * @returns {0 | 1 | 2} type of bigint for serialization - */ -const identifyBigInt = n => { - if (n <= BigInt(127) && n >= BigInt(-128)) return 0; - if (n <= BigInt(2147483647) && n >= BigInt(-2147483648)) return 1; - return 2; -}; - -/** @typedef {TODO} Context */ - -/** - * @typedef {PrimitiveSerializableType[]} DeserializedType - * @typedef {BufferSerializableType[]} SerializedType - * @extends {SerializerMiddleware} - */ -class BinaryMiddleware extends SerializerMiddleware { - /** - * @param {DeserializedType} data data - * @param {object} context context object - * @returns {SerializedType|Promise} serialized data - */ - serialize(data, context) { - return this._serialize(data, context); - } - - /** - * @param {function(): Promise | any} fn lazy function - * @param {TODO} context serialize function - * @returns {function(): Promise | any} new lazy - */ - _serializeLazy(fn, context) { - return SerializerMiddleware.serializeLazy(fn, data => - this._serialize(data, context) - ); - } - - /** - * @param {DeserializedType} data data - * @param {TODO} context context object - * @param {{ leftOverBuffer: Buffer | null, allocationSize: number, increaseCounter: number }} allocationScope allocation scope - * @returns {SerializedType} serialized data - */ - _serialize( - data, - context, - allocationScope = { - allocationSize: 1024, - increaseCounter: 0, - leftOverBuffer: null - } - ) { - /** @type {Buffer | null} */ - let leftOverBuffer = null; - /** @type {BufferSerializableType[]} */ - let buffers = []; - /** @type {Buffer | null} */ - let currentBuffer = allocationScope ? allocationScope.leftOverBuffer : null; - allocationScope.leftOverBuffer = null; - let currentPosition = 0; - if (currentBuffer === null) { - currentBuffer = Buffer.allocUnsafe(allocationScope.allocationSize); - } - /** - * @param {number} bytesNeeded bytes needed - */ - const allocate = bytesNeeded => { - if (currentBuffer !== null) { - if (currentBuffer.length - currentPosition >= bytesNeeded) return; - flush(); - } - if (leftOverBuffer && leftOverBuffer.length >= bytesNeeded) { - currentBuffer = leftOverBuffer; - leftOverBuffer = null; - } else { - currentBuffer = Buffer.allocUnsafe( - Math.max(bytesNeeded, allocationScope.allocationSize) - ); - if ( - !(allocationScope.increaseCounter = - (allocationScope.increaseCounter + 1) % 4) && - allocationScope.allocationSize < 16777216 - ) { - allocationScope.allocationSize = allocationScope.allocationSize << 1; - } - } - }; - const flush = () => { - if (currentBuffer !== null) { - if (currentPosition > 0) { - buffers.push( - Buffer.from( - currentBuffer.buffer, - currentBuffer.byteOffset, - currentPosition - ) - ); - } - if ( - !leftOverBuffer || - leftOverBuffer.length < currentBuffer.length - currentPosition - ) { - leftOverBuffer = Buffer.from( - currentBuffer.buffer, - currentBuffer.byteOffset + currentPosition, - currentBuffer.byteLength - currentPosition - ); - } - - currentBuffer = null; - currentPosition = 0; - } - }; - /** - * @param {number} byte byte - */ - const writeU8 = byte => { - /** @type {Buffer} */ - (currentBuffer).writeUInt8(byte, currentPosition++); - }; - /** - * @param {number} ui32 ui32 - */ - const writeU32 = ui32 => { - /** @type {Buffer} */ - (currentBuffer).writeUInt32LE(ui32, currentPosition); - currentPosition += 4; - }; - /** @type {number[]} */ - const measureStack = []; - const measureStart = () => { - measureStack.push(buffers.length, currentPosition); - }; - /** - * @returns {number} size - */ - const measureEnd = () => { - const oldPos = /** @type {number} */ (measureStack.pop()); - const buffersIndex = /** @type {number} */ (measureStack.pop()); - let size = currentPosition - oldPos; - for (let i = buffersIndex; i < buffers.length; i++) { - size += buffers[i].length; - } - return size; - }; - for (let i = 0; i < data.length; i++) { - const thing = data[i]; - switch (typeof thing) { - case "function": { - if (!SerializerMiddleware.isLazy(thing)) - throw new Error(`Unexpected function ${thing}`); - /** @type {SerializedType | (() => SerializedType)} */ - let serializedData = - SerializerMiddleware.getLazySerializedValue(thing); - if (serializedData === undefined) { - if (SerializerMiddleware.isLazy(thing, this)) { - flush(); - allocationScope.leftOverBuffer = leftOverBuffer; - const result = - /** @type {(Exclude>)[]} */ ( - thing() - ); - const data = this._serialize(result, context, allocationScope); - leftOverBuffer = allocationScope.leftOverBuffer; - allocationScope.leftOverBuffer = null; - SerializerMiddleware.setLazySerializedValue(thing, data); - serializedData = data; - } else { - serializedData = this._serializeLazy(thing, context); - flush(); - buffers.push(serializedData); - break; - } - } else if (typeof serializedData === "function") { - flush(); - buffers.push(serializedData); - break; - } - /** @type {number[]} */ - const lengths = []; - for (const item of serializedData) { - let last; - if (typeof item === "function") { - lengths.push(0); - } else if (item.length === 0) { - // ignore - } else if ( - lengths.length > 0 && - (last = lengths[lengths.length - 1]) !== 0 - ) { - const remaining = 0xffffffff - last; - if (remaining >= item.length) { - lengths[lengths.length - 1] += item.length; - } else { - lengths.push(item.length - remaining); - lengths[lengths.length - 2] = 0xffffffff; - } - } else { - lengths.push(item.length); - } - } - allocate(5 + lengths.length * 4); - writeU8(LAZY_HEADER); - writeU32(lengths.length); - for (const l of lengths) { - writeU32(l); - } - flush(); - for (const item of serializedData) { - buffers.push(item); - } - break; - } - case "string": { - const len = Buffer.byteLength(thing); - if (len >= 128 || len !== thing.length) { - allocate(len + HEADER_SIZE + I32_SIZE); - writeU8(STRING_HEADER); - writeU32(len); - currentBuffer.write(thing, currentPosition); - currentPosition += len; - } else if (len >= 70) { - allocate(len + HEADER_SIZE); - writeU8(SHORT_STRING_HEADER | len); - - currentBuffer.write(thing, currentPosition, "latin1"); - currentPosition += len; - } else { - allocate(len + HEADER_SIZE); - writeU8(SHORT_STRING_HEADER | len); - - for (let i = 0; i < len; i++) { - currentBuffer[currentPosition++] = thing.charCodeAt(i); - } - } - break; - } - case "bigint": { - const type = identifyBigInt(thing); - if (type === 0 && thing >= 0 && thing <= BigInt(10)) { - // shortcut for very small bigints - allocate(HEADER_SIZE + I8_SIZE); - writeU8(BIGINT_I8_HEADER); - writeU8(Number(thing)); - break; - } - - switch (type) { - case 0: { - let n = 1; - allocate(HEADER_SIZE + I8_SIZE * n); - writeU8(BIGINT_I8_HEADER | (n - 1)); - while (n > 0) { - currentBuffer.writeInt8( - Number(/** @type {bigint} */ (data[i])), - currentPosition - ); - currentPosition += I8_SIZE; - n--; - i++; - } - i--; - break; - } - case 1: { - let n = 1; - allocate(HEADER_SIZE + I32_SIZE * n); - writeU8(BIGINT_I32_HEADER | (n - 1)); - while (n > 0) { - currentBuffer.writeInt32LE( - Number(/** @type {bigint} */ (data[i])), - currentPosition - ); - currentPosition += I32_SIZE; - n--; - i++; - } - i--; - break; - } - default: { - const value = thing.toString(); - const len = Buffer.byteLength(value); - allocate(len + HEADER_SIZE + I32_SIZE); - writeU8(BIGINT_HEADER); - writeU32(len); - currentBuffer.write(value, currentPosition); - currentPosition += len; - break; - } - } - break; - } - case "number": { - const type = identifyNumber(thing); - if (type === 0 && thing >= 0 && thing <= 10) { - // shortcut for very small numbers - allocate(I8_SIZE); - writeU8(thing); - break; - } - /** - * amount of numbers to write - * @type {number} - */ - let n = 1; - for (; n < 32 && i + n < data.length; n++) { - const item = data[i + n]; - if (typeof item !== "number") break; - if (identifyNumber(item) !== type) break; - } - switch (type) { - case 0: - allocate(HEADER_SIZE + I8_SIZE * n); - writeU8(I8_HEADER | (n - 1)); - while (n > 0) { - currentBuffer.writeInt8( - /** @type {number} */ (data[i]), - currentPosition - ); - currentPosition += I8_SIZE; - n--; - i++; - } - break; - case 1: - allocate(HEADER_SIZE + I32_SIZE * n); - writeU8(I32_HEADER | (n - 1)); - while (n > 0) { - currentBuffer.writeInt32LE( - /** @type {number} */ (data[i]), - currentPosition - ); - currentPosition += I32_SIZE; - n--; - i++; - } - break; - case 2: - allocate(HEADER_SIZE + F64_SIZE * n); - writeU8(F64_HEADER | (n - 1)); - while (n > 0) { - currentBuffer.writeDoubleLE( - /** @type {number} */ (data[i]), - currentPosition - ); - currentPosition += F64_SIZE; - n--; - i++; - } - break; - } - - i--; - break; - } - case "boolean": { - let lastByte = thing === true ? 1 : 0; - const bytes = []; - let count = 1; - let n; - for (n = 1; n < 0xffffffff && i + n < data.length; n++) { - const item = data[i + n]; - if (typeof item !== "boolean") break; - const pos = count & 0x7; - if (pos === 0) { - bytes.push(lastByte); - lastByte = item === true ? 1 : 0; - } else if (item === true) { - lastByte |= 1 << pos; - } - count++; - } - i += count - 1; - if (count === 1) { - allocate(HEADER_SIZE); - writeU8(lastByte === 1 ? TRUE_HEADER : FALSE_HEADER); - } else if (count === 2) { - allocate(HEADER_SIZE * 2); - writeU8(lastByte & 1 ? TRUE_HEADER : FALSE_HEADER); - writeU8(lastByte & 2 ? TRUE_HEADER : FALSE_HEADER); - } else if (count <= 6) { - allocate(HEADER_SIZE + I8_SIZE); - writeU8(BOOLEANS_HEADER); - writeU8((1 << count) | lastByte); - } else if (count <= 133) { - allocate(HEADER_SIZE + I8_SIZE + I8_SIZE * bytes.length + I8_SIZE); - writeU8(BOOLEANS_HEADER); - writeU8(0x80 | (count - 7)); - for (const byte of bytes) writeU8(byte); - writeU8(lastByte); - } else { - allocate( - HEADER_SIZE + - I8_SIZE + - I32_SIZE + - I8_SIZE * bytes.length + - I8_SIZE - ); - writeU8(BOOLEANS_HEADER); - writeU8(0xff); - writeU32(count); - for (const byte of bytes) writeU8(byte); - writeU8(lastByte); - } - break; - } - case "object": { - if (thing === null) { - let n; - for (n = 1; n < 0x100000104 && i + n < data.length; n++) { - const item = data[i + n]; - if (item !== null) break; - } - i += n - 1; - if (n === 1) { - if (i + 1 < data.length) { - const next = data[i + 1]; - if (next === true) { - allocate(HEADER_SIZE); - writeU8(NULL_AND_TRUE_HEADER); - i++; - } else if (next === false) { - allocate(HEADER_SIZE); - writeU8(NULL_AND_FALSE_HEADER); - i++; - } else if (typeof next === "number") { - const type = identifyNumber(next); - if (type === 0) { - allocate(HEADER_SIZE + I8_SIZE); - writeU8(NULL_AND_I8_HEADER); - currentBuffer.writeInt8(next, currentPosition); - currentPosition += I8_SIZE; - i++; - } else if (type === 1) { - allocate(HEADER_SIZE + I32_SIZE); - writeU8(NULL_AND_I32_HEADER); - currentBuffer.writeInt32LE(next, currentPosition); - currentPosition += I32_SIZE; - i++; - } else { - allocate(HEADER_SIZE); - writeU8(NULL_HEADER); - } - } else { - allocate(HEADER_SIZE); - writeU8(NULL_HEADER); - } - } else { - allocate(HEADER_SIZE); - writeU8(NULL_HEADER); - } - } else if (n === 2) { - allocate(HEADER_SIZE); - writeU8(NULL2_HEADER); - } else if (n === 3) { - allocate(HEADER_SIZE); - writeU8(NULL3_HEADER); - } else if (n < 260) { - allocate(HEADER_SIZE + I8_SIZE); - writeU8(NULLS8_HEADER); - writeU8(n - 4); - } else { - allocate(HEADER_SIZE + I32_SIZE); - writeU8(NULLS32_HEADER); - writeU32(n - 260); - } - } else if (Buffer.isBuffer(thing)) { - if (thing.length < 8192) { - allocate(HEADER_SIZE + I32_SIZE + thing.length); - writeU8(BUFFER_HEADER); - writeU32(thing.length); - thing.copy(currentBuffer, currentPosition); - currentPosition += thing.length; - } else { - allocate(HEADER_SIZE + I32_SIZE); - writeU8(BUFFER_HEADER); - writeU32(thing.length); - flush(); - buffers.push(thing); - } - } - break; - } - case "symbol": { - if (thing === MEASURE_START_OPERATION) { - measureStart(); - } else if (thing === MEASURE_END_OPERATION) { - const size = measureEnd(); - allocate(HEADER_SIZE + I32_SIZE); - writeU8(I32_HEADER); - currentBuffer.writeInt32LE(size, currentPosition); - currentPosition += I32_SIZE; - } - break; - } - default: { - throw new Error( - `Unknown typeof "${typeof thing}" in binary middleware` - ); - } - } - } - flush(); - - allocationScope.leftOverBuffer = leftOverBuffer; - - // avoid leaking memory - currentBuffer = null; - leftOverBuffer = null; - allocationScope = /** @type {EXPECTED_ANY} */ (undefined); - const _buffers = buffers; - buffers = /** @type {EXPECTED_ANY} */ (undefined); - return _buffers; - } - - /** - * @param {SerializedType} data data - * @param {object} context context object - * @returns {DeserializedType|Promise} deserialized data - */ - deserialize(data, context) { - return this._deserialize(data, context); - } - - _createLazyDeserialized(content, context) { - return SerializerMiddleware.createLazy( - memoize(() => this._deserialize(content, context)), - this, - undefined, - content - ); - } - - _deserializeLazy(fn, context) { - return SerializerMiddleware.deserializeLazy(fn, data => - this._deserialize(data, context) - ); - } - - /** - * @param {SerializedType} data data - * @param {TODO} context context object - * @returns {DeserializedType} deserialized data - */ - _deserialize(data, context) { - let currentDataItem = 0; - /** @type {BufferSerializableType | null} */ - let currentBuffer = data[0]; - let currentIsBuffer = Buffer.isBuffer(currentBuffer); - let currentPosition = 0; - - /** @type {(x: Buffer) => Buffer} */ - const retainedBuffer = context.retainedBuffer || (x => x); - - const checkOverflow = () => { - if (currentPosition >= /** @type {Buffer} */ (currentBuffer).length) { - currentPosition = 0; - currentDataItem++; - currentBuffer = - currentDataItem < data.length ? data[currentDataItem] : null; - currentIsBuffer = Buffer.isBuffer(currentBuffer); - } - }; - /** - * @param {number} n n - * @returns {boolean} true when in current buffer, otherwise false - */ - const isInCurrentBuffer = n => - currentIsBuffer && - n + currentPosition <= /** @type {Buffer} */ (currentBuffer).length; - const ensureBuffer = () => { - if (!currentIsBuffer) { - throw new Error( - currentBuffer === null - ? "Unexpected end of stream" - : "Unexpected lazy element in stream" - ); - } - }; - /** - * Reads n bytes - * @param {number} n amount of bytes to read - * @returns {Buffer} buffer with bytes - */ - const read = n => { - ensureBuffer(); - const rem = - /** @type {Buffer} */ (currentBuffer).length - currentPosition; - if (rem < n) { - const buffers = [read(rem)]; - n -= rem; - ensureBuffer(); - while (/** @type {Buffer} */ (currentBuffer).length < n) { - const b = /** @type {Buffer} */ (currentBuffer); - buffers.push(b); - n -= b.length; - currentDataItem++; - currentBuffer = - currentDataItem < data.length ? data[currentDataItem] : null; - currentIsBuffer = Buffer.isBuffer(currentBuffer); - ensureBuffer(); - } - buffers.push(read(n)); - return Buffer.concat(buffers); - } - const b = /** @type {Buffer} */ (currentBuffer); - const res = Buffer.from(b.buffer, b.byteOffset + currentPosition, n); - currentPosition += n; - checkOverflow(); - return res; - }; - /** - * Reads up to n bytes - * @param {number} n amount of bytes to read - * @returns {Buffer} buffer with bytes - */ - const readUpTo = n => { - ensureBuffer(); - const rem = - /** @type {Buffer} */ - (currentBuffer).length - currentPosition; - if (rem < n) { - n = rem; - } - const b = /** @type {Buffer} */ (currentBuffer); - const res = Buffer.from(b.buffer, b.byteOffset + currentPosition, n); - currentPosition += n; - checkOverflow(); - return res; - }; - /** - * @returns {number} U8 - */ - const readU8 = () => { - ensureBuffer(); - /** - * There is no need to check remaining buffer size here - * since {@link checkOverflow} guarantees at least one byte remaining - */ - const byte = - /** @type {Buffer} */ - (currentBuffer).readUInt8(currentPosition); - currentPosition += I8_SIZE; - checkOverflow(); - return byte; - }; - /** - * @returns {number} U32 - */ - const readU32 = () => read(I32_SIZE).readUInt32LE(0); - /** - * @param {number} data data - * @param {number} n n - */ - const readBits = (data, n) => { - let mask = 1; - while (n !== 0) { - result.push((data & mask) !== 0); - mask = mask << 1; - n--; - } - }; - const dispatchTable = Array.from({ length: 256 }).map((_, header) => { - switch (header) { - case LAZY_HEADER: - return () => { - const count = readU32(); - const lengths = Array.from({ length: count }).map(() => readU32()); - const content = []; - for (let l of lengths) { - if (l === 0) { - if (typeof currentBuffer !== "function") { - throw new Error("Unexpected non-lazy element in stream"); - } - content.push(currentBuffer); - currentDataItem++; - currentBuffer = - currentDataItem < data.length ? data[currentDataItem] : null; - currentIsBuffer = Buffer.isBuffer(currentBuffer); - } else { - do { - const buf = readUpTo(l); - l -= buf.length; - content.push(retainedBuffer(buf)); - } while (l > 0); - } - } - result.push(this._createLazyDeserialized(content, context)); - }; - case BUFFER_HEADER: - return () => { - const len = readU32(); - result.push(retainedBuffer(read(len))); - }; - case TRUE_HEADER: - return () => result.push(true); - case FALSE_HEADER: - return () => result.push(false); - case NULL3_HEADER: - return () => result.push(null, null, null); - case NULL2_HEADER: - return () => result.push(null, null); - case NULL_HEADER: - return () => result.push(null); - case NULL_AND_TRUE_HEADER: - return () => result.push(null, true); - case NULL_AND_FALSE_HEADER: - return () => result.push(null, false); - case NULL_AND_I8_HEADER: - return () => { - if (currentIsBuffer) { - result.push( - null, - /** @type {Buffer} */ (currentBuffer).readInt8(currentPosition) - ); - currentPosition += I8_SIZE; - checkOverflow(); - } else { - result.push(null, read(I8_SIZE).readInt8(0)); - } - }; - case NULL_AND_I32_HEADER: - return () => { - result.push(null); - if (isInCurrentBuffer(I32_SIZE)) { - result.push( - /** @type {Buffer} */ (currentBuffer).readInt32LE( - currentPosition - ) - ); - currentPosition += I32_SIZE; - checkOverflow(); - } else { - result.push(read(I32_SIZE).readInt32LE(0)); - } - }; - case NULLS8_HEADER: - return () => { - const len = readU8() + 4; - for (let i = 0; i < len; i++) { - result.push(null); - } - }; - case NULLS32_HEADER: - return () => { - const len = readU32() + 260; - for (let i = 0; i < len; i++) { - result.push(null); - } - }; - case BOOLEANS_HEADER: - return () => { - const innerHeader = readU8(); - if ((innerHeader & 0xf0) === 0) { - readBits(innerHeader, 3); - } else if ((innerHeader & 0xe0) === 0) { - readBits(innerHeader, 4); - } else if ((innerHeader & 0xc0) === 0) { - readBits(innerHeader, 5); - } else if ((innerHeader & 0x80) === 0) { - readBits(innerHeader, 6); - } else if (innerHeader !== 0xff) { - let count = (innerHeader & 0x7f) + 7; - while (count > 8) { - readBits(readU8(), 8); - count -= 8; - } - readBits(readU8(), count); - } else { - let count = readU32(); - while (count > 8) { - readBits(readU8(), 8); - count -= 8; - } - readBits(readU8(), count); - } - }; - case STRING_HEADER: - return () => { - const len = readU32(); - if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) { - result.push( - /** @type {Buffer} */ - (currentBuffer).toString( - undefined, - currentPosition, - currentPosition + len - ) - ); - currentPosition += len; - checkOverflow(); - } else { - result.push(read(len).toString()); - } - }; - case SHORT_STRING_HEADER: - return () => result.push(""); - case SHORT_STRING_HEADER | 1: - return () => { - if (currentIsBuffer && currentPosition < 0x7ffffffe) { - result.push( - /** @type {Buffer} */ - (currentBuffer).toString( - "latin1", - currentPosition, - currentPosition + 1 - ) - ); - currentPosition++; - checkOverflow(); - } else { - result.push(read(1).toString("latin1")); - } - }; - case I8_HEADER: - return () => { - if (currentIsBuffer) { - result.push( - /** @type {Buffer} */ (currentBuffer).readInt8(currentPosition) - ); - currentPosition++; - checkOverflow(); - } else { - result.push(read(1).readInt8(0)); - } - }; - case BIGINT_I8_HEADER: { - const len = 1; - return () => { - const need = I8_SIZE * len; - - if (isInCurrentBuffer(need)) { - for (let i = 0; i < len; i++) { - const value = - /** @type {Buffer} */ - (currentBuffer).readInt8(currentPosition); - result.push(BigInt(value)); - currentPosition += I8_SIZE; - } - checkOverflow(); - } else { - const buf = read(need); - for (let i = 0; i < len; i++) { - const value = buf.readInt8(i * I8_SIZE); - result.push(BigInt(value)); - } - } - }; - } - case BIGINT_I32_HEADER: { - const len = 1; - return () => { - const need = I32_SIZE * len; - if (isInCurrentBuffer(need)) { - for (let i = 0; i < len; i++) { - const value = /** @type {Buffer} */ (currentBuffer).readInt32LE( - currentPosition - ); - result.push(BigInt(value)); - currentPosition += I32_SIZE; - } - checkOverflow(); - } else { - const buf = read(need); - for (let i = 0; i < len; i++) { - const value = buf.readInt32LE(i * I32_SIZE); - result.push(BigInt(value)); - } - } - }; - } - case BIGINT_HEADER: { - return () => { - const len = readU32(); - if (isInCurrentBuffer(len) && currentPosition + len < 0x7fffffff) { - const value = - /** @type {Buffer} */ - (currentBuffer).toString( - undefined, - currentPosition, - currentPosition + len - ); - - result.push(BigInt(value)); - currentPosition += len; - checkOverflow(); - } else { - const value = read(len).toString(); - result.push(BigInt(value)); - } - }; - } - default: - if (header <= 10) { - return () => result.push(header); - } else if ((header & SHORT_STRING_HEADER) === SHORT_STRING_HEADER) { - const len = header & SHORT_STRING_LENGTH_MASK; - return () => { - if ( - isInCurrentBuffer(len) && - currentPosition + len < 0x7fffffff - ) { - result.push( - /** @type {Buffer} */ - (currentBuffer).toString( - "latin1", - currentPosition, - currentPosition + len - ) - ); - currentPosition += len; - checkOverflow(); - } else { - result.push(read(len).toString("latin1")); - } - }; - } else if ((header & NUMBERS_HEADER_MASK) === F64_HEADER) { - const len = (header & NUMBERS_COUNT_MASK) + 1; - return () => { - const need = F64_SIZE * len; - if (isInCurrentBuffer(need)) { - for (let i = 0; i < len; i++) { - result.push( - /** @type {Buffer} */ (currentBuffer).readDoubleLE( - currentPosition - ) - ); - currentPosition += F64_SIZE; - } - checkOverflow(); - } else { - const buf = read(need); - for (let i = 0; i < len; i++) { - result.push(buf.readDoubleLE(i * F64_SIZE)); - } - } - }; - } else if ((header & NUMBERS_HEADER_MASK) === I32_HEADER) { - const len = (header & NUMBERS_COUNT_MASK) + 1; - return () => { - const need = I32_SIZE * len; - if (isInCurrentBuffer(need)) { - for (let i = 0; i < len; i++) { - result.push( - /** @type {Buffer} */ (currentBuffer).readInt32LE( - currentPosition - ) - ); - currentPosition += I32_SIZE; - } - checkOverflow(); - } else { - const buf = read(need); - for (let i = 0; i < len; i++) { - result.push(buf.readInt32LE(i * I32_SIZE)); - } - } - }; - } else if ((header & NUMBERS_HEADER_MASK) === I8_HEADER) { - const len = (header & NUMBERS_COUNT_MASK) + 1; - return () => { - const need = I8_SIZE * len; - if (isInCurrentBuffer(need)) { - for (let i = 0; i < len; i++) { - result.push( - /** @type {Buffer} */ (currentBuffer).readInt8( - currentPosition - ) - ); - currentPosition += I8_SIZE; - } - checkOverflow(); - } else { - const buf = read(need); - for (let i = 0; i < len; i++) { - result.push(buf.readInt8(i * I8_SIZE)); - } - } - }; - } - return () => { - throw new Error(`Unexpected header byte 0x${header.toString(16)}`); - }; - } - }); - - /** @type {DeserializedType} */ - let result = []; - while (currentBuffer !== null) { - if (typeof currentBuffer === "function") { - result.push(this._deserializeLazy(currentBuffer, context)); - currentDataItem++; - currentBuffer = - currentDataItem < data.length ? data[currentDataItem] : null; - currentIsBuffer = Buffer.isBuffer(currentBuffer); - } else { - const header = readU8(); - dispatchTable[header](); - } - } - - // avoid leaking memory in context - // eslint-disable-next-line prefer-const - let _result = result; - result = /** @type {EXPECTED_ANY} */ (undefined); - return _result; - } -} - -module.exports = BinaryMiddleware; - -module.exports.MEASURE_START_OPERATION = MEASURE_START_OPERATION; -module.exports.MEASURE_END_OPERATION = MEASURE_END_OPERATION; diff --git a/webpack-lib/lib/serialization/DateObjectSerializer.js b/webpack-lib/lib/serialization/DateObjectSerializer.js deleted file mode 100644 index c69ccfe8c7c..00000000000 --- a/webpack-lib/lib/serialization/DateObjectSerializer.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class DateObjectSerializer { - /** - * @param {Date} obj date - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - context.write(obj.getTime()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {Date} date - */ - deserialize(context) { - return new Date(context.read()); - } -} - -module.exports = DateObjectSerializer; diff --git a/webpack-lib/lib/serialization/ErrorObjectSerializer.js b/webpack-lib/lib/serialization/ErrorObjectSerializer.js deleted file mode 100644 index b0869155ff4..00000000000 --- a/webpack-lib/lib/serialization/ErrorObjectSerializer.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ErrorObjectSerializer { - /** - * @param {ErrorConstructor | EvalErrorConstructor | RangeErrorConstructor | ReferenceErrorConstructor | SyntaxErrorConstructor | TypeErrorConstructor} Type error type - */ - constructor(Type) { - this.Type = Type; - } - - /** - * @param {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} obj error - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - context.write(obj.message); - context.write(obj.stack); - context.write(/** @type {Error & { cause: "unknown" }} */ (obj).cause); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {Error | EvalError | RangeError | ReferenceError | SyntaxError | TypeError} error - */ - deserialize(context) { - const err = new this.Type(); - - err.message = context.read(); - err.stack = context.read(); - /** @type {Error & { cause: "unknown" }} */ - (err).cause = context.read(); - - return err; - } -} - -module.exports = ErrorObjectSerializer; diff --git a/webpack-lib/lib/serialization/FileMiddleware.js b/webpack-lib/lib/serialization/FileMiddleware.js deleted file mode 100644 index b8de8a958d9..00000000000 --- a/webpack-lib/lib/serialization/FileMiddleware.js +++ /dev/null @@ -1,731 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { constants } = require("buffer"); -const { pipeline } = require("stream"); -const { - createBrotliCompress, - createBrotliDecompress, - createGzip, - createGunzip, - constants: zConstants -} = require("zlib"); -const createHash = require("../util/createHash"); -const { dirname, join, mkdirp } = require("../util/fs"); -const memoize = require("../util/memoize"); -const SerializerMiddleware = require("./SerializerMiddleware"); - -/** @typedef {typeof import("../util/Hash")} Hash */ -/** @typedef {import("../util/fs").IStats} IStats */ -/** @typedef {import("../util/fs").IntermediateFileSystem} IntermediateFileSystem */ -/** @typedef {import("./types").BufferSerializableType} BufferSerializableType */ - -/* -Format: - -File -> Header Section* - -Version -> u32 -AmountOfSections -> u32 -SectionSize -> i32 (if less than zero represents lazy value) - -Header -> Version AmountOfSections SectionSize* - -Buffer -> n bytes -Section -> Buffer - -*/ - -// "wpc" + 1 in little-endian -const VERSION = 0x01637077; -const WRITE_LIMIT_TOTAL = 0x7fff0000; -const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024; - -/** - * @param {Buffer[]} buffers buffers - * @param {string | Hash} hashFunction hash function to use - * @returns {string} hash - */ -const hashForName = (buffers, hashFunction) => { - const hash = createHash(hashFunction); - for (const buf of buffers) hash.update(buf); - return /** @type {string} */ (hash.digest("hex")); -}; - -const COMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024; -const DECOMPRESSION_CHUNK_SIZE = 100 * 1024 * 1024; - -/** @type {function(Buffer, number, number): void} */ -const writeUInt64LE = Buffer.prototype.writeBigUInt64LE - ? (buf, value, offset) => { - buf.writeBigUInt64LE(BigInt(value), offset); - } - : (buf, value, offset) => { - const low = value % 0x100000000; - const high = (value - low) / 0x100000000; - buf.writeUInt32LE(low, offset); - buf.writeUInt32LE(high, offset + 4); - }; - -/** @type {function(Buffer, number): void} */ -const readUInt64LE = Buffer.prototype.readBigUInt64LE - ? (buf, offset) => Number(buf.readBigUInt64LE(offset)) - : (buf, offset) => { - const low = buf.readUInt32LE(offset); - const high = buf.readUInt32LE(offset + 4); - return high * 0x100000000 + low; - }; - -/** - * @typedef {object} SerializeResult - * @property {string | false} name - * @property {number} size - * @property {Promise=} backgroundJob - */ - -/** - * @param {FileMiddleware} middleware this - * @param {BufferSerializableType[] | Promise} data data to be serialized - * @param {string | boolean} name file base name - * @param {function(string | false, Buffer[], number): Promise} writeFile writes a file - * @param {string | Hash} hashFunction hash function to use - * @returns {Promise} resulting file pointer and promise - */ -const serialize = async ( - middleware, - data, - name, - writeFile, - hashFunction = "md4" -) => { - /** @type {(Buffer[] | Buffer | SerializeResult | Promise)[]} */ - const processedData = []; - /** @type {WeakMap>} */ - const resultToLazy = new WeakMap(); - /** @type {Buffer[] | undefined} */ - let lastBuffers; - for (const item of await data) { - if (typeof item === "function") { - if (!SerializerMiddleware.isLazy(item)) - throw new Error("Unexpected function"); - if (!SerializerMiddleware.isLazy(item, middleware)) { - throw new Error( - "Unexpected lazy value with non-this target (can't pass through lazy values)" - ); - } - lastBuffers = undefined; - const serializedInfo = SerializerMiddleware.getLazySerializedValue(item); - if (serializedInfo) { - if (typeof serializedInfo === "function") { - throw new Error( - "Unexpected lazy value with non-this target (can't pass through lazy values)" - ); - } else { - processedData.push(serializedInfo); - } - } else { - const content = item(); - if (content) { - const options = SerializerMiddleware.getLazyOptions(item); - processedData.push( - serialize( - middleware, - content, - (options && options.name) || true, - writeFile, - hashFunction - ).then(result => { - /** @type {any} */ (item).options.size = result.size; - resultToLazy.set(result, item); - return result; - }) - ); - } else { - throw new Error( - "Unexpected falsy value returned by lazy value function" - ); - } - } - } else if (item) { - if (lastBuffers) { - lastBuffers.push(item); - } else { - lastBuffers = [item]; - processedData.push(lastBuffers); - } - } else { - throw new Error("Unexpected falsy value in items array"); - } - } - /** @type {Promise[]} */ - const backgroundJobs = []; - const resolvedData = ( - await Promise.all( - /** @type {Promise[]} */ - (processedData) - ) - ).map(item => { - if (Array.isArray(item) || Buffer.isBuffer(item)) return item; - - backgroundJobs.push(item.backgroundJob); - // create pointer buffer from size and name - const name = /** @type {string} */ (item.name); - const nameBuffer = Buffer.from(name); - const buf = Buffer.allocUnsafe(8 + nameBuffer.length); - writeUInt64LE(buf, item.size, 0); - nameBuffer.copy(buf, 8, 0); - const lazy = resultToLazy.get(item); - SerializerMiddleware.setLazySerializedValue(lazy, buf); - return buf; - }); - /** @type {number[]} */ - const lengths = []; - for (const item of resolvedData) { - if (Array.isArray(item)) { - let l = 0; - for (const b of item) l += b.length; - while (l > 0x7fffffff) { - lengths.push(0x7fffffff); - l -= 0x7fffffff; - } - lengths.push(l); - } else if (item) { - lengths.push(-item.length); - } else { - throw new Error(`Unexpected falsy value in resolved data ${item}`); - } - } - const header = Buffer.allocUnsafe(8 + lengths.length * 4); - header.writeUInt32LE(VERSION, 0); - header.writeUInt32LE(lengths.length, 4); - for (let i = 0; i < lengths.length; i++) { - header.writeInt32LE(lengths[i], 8 + i * 4); - } - /** @type {Buffer[]} */ - const buf = [header]; - for (const item of resolvedData) { - if (Array.isArray(item)) { - for (const b of item) buf.push(b); - } else if (item) { - buf.push(item); - } - } - if (name === true) { - name = hashForName(buf, hashFunction); - } - let size = 0; - for (const b of buf) size += b.length; - backgroundJobs.push(writeFile(name, buf, size)); - return { - size, - name, - backgroundJob: - backgroundJobs.length === 1 - ? backgroundJobs[0] - : Promise.all(backgroundJobs) - }; -}; - -/** - * @param {FileMiddleware} middleware this - * @param {string | false} name filename - * @param {function(string | false): Promise} readFile read content of a file - * @returns {Promise} deserialized data - */ -const deserialize = async (middleware, name, readFile) => { - const contents = await readFile(name); - if (contents.length === 0) throw new Error(`Empty file ${name}`); - let contentsIndex = 0; - let contentItem = contents[0]; - let contentItemLength = contentItem.length; - let contentPosition = 0; - if (contentItemLength === 0) throw new Error(`Empty file ${name}`); - const nextContent = () => { - contentsIndex++; - contentItem = contents[contentsIndex]; - contentItemLength = contentItem.length; - contentPosition = 0; - }; - /** - * @param {number} n number of bytes to ensure - */ - const ensureData = n => { - if (contentPosition === contentItemLength) { - nextContent(); - } - while (contentItemLength - contentPosition < n) { - const remaining = contentItem.slice(contentPosition); - let lengthFromNext = n - remaining.length; - const buffers = [remaining]; - for (let i = contentsIndex + 1; i < contents.length; i++) { - const l = contents[i].length; - if (l > lengthFromNext) { - buffers.push(contents[i].slice(0, lengthFromNext)); - contents[i] = contents[i].slice(lengthFromNext); - lengthFromNext = 0; - break; - } else { - buffers.push(contents[i]); - contentsIndex = i; - lengthFromNext -= l; - } - } - if (lengthFromNext > 0) throw new Error("Unexpected end of data"); - contentItem = Buffer.concat(buffers, n); - contentItemLength = n; - contentPosition = 0; - } - }; - /** - * @returns {number} value value - */ - const readUInt32LE = () => { - ensureData(4); - const value = contentItem.readUInt32LE(contentPosition); - contentPosition += 4; - return value; - }; - /** - * @returns {number} value value - */ - const readInt32LE = () => { - ensureData(4); - const value = contentItem.readInt32LE(contentPosition); - contentPosition += 4; - return value; - }; - /** - * @param {number} l length - * @returns {Buffer} buffer - */ - const readSlice = l => { - ensureData(l); - if (contentPosition === 0 && contentItemLength === l) { - const result = contentItem; - if (contentsIndex + 1 < contents.length) { - nextContent(); - } else { - contentPosition = l; - } - return result; - } - const result = contentItem.slice(contentPosition, contentPosition + l); - contentPosition += l; - // we clone the buffer here to allow the original content to be garbage collected - return l * 2 < contentItem.buffer.byteLength ? Buffer.from(result) : result; - }; - const version = readUInt32LE(); - if (version !== VERSION) { - throw new Error("Invalid file version"); - } - const sectionCount = readUInt32LE(); - const lengths = []; - let lastLengthPositive = false; - for (let i = 0; i < sectionCount; i++) { - const value = readInt32LE(); - const valuePositive = value >= 0; - if (lastLengthPositive && valuePositive) { - lengths[lengths.length - 1] += value; - } else { - lengths.push(value); - lastLengthPositive = valuePositive; - } - } - const result = []; - for (let length of lengths) { - if (length < 0) { - const slice = readSlice(-length); - const size = Number(readUInt64LE(slice, 0)); - const nameBuffer = slice.slice(8); - const name = nameBuffer.toString(); - result.push( - SerializerMiddleware.createLazy( - memoize(() => deserialize(middleware, name, readFile)), - middleware, - { - name, - size - }, - slice - ) - ); - } else { - if (contentPosition === contentItemLength) { - nextContent(); - } else if (contentPosition !== 0) { - if (length <= contentItemLength - contentPosition) { - result.push( - Buffer.from( - contentItem.buffer, - contentItem.byteOffset + contentPosition, - length - ) - ); - contentPosition += length; - length = 0; - } else { - const l = contentItemLength - contentPosition; - result.push( - Buffer.from( - contentItem.buffer, - contentItem.byteOffset + contentPosition, - l - ) - ); - length -= l; - contentPosition = contentItemLength; - } - } else if (length >= contentItemLength) { - result.push(contentItem); - length -= contentItemLength; - contentPosition = contentItemLength; - } else { - result.push( - Buffer.from(contentItem.buffer, contentItem.byteOffset, length) - ); - contentPosition += length; - length = 0; - } - while (length > 0) { - nextContent(); - if (length >= contentItemLength) { - result.push(contentItem); - length -= contentItemLength; - contentPosition = contentItemLength; - } else { - result.push( - Buffer.from(contentItem.buffer, contentItem.byteOffset, length) - ); - contentPosition += length; - length = 0; - } - } - } - } - return result; -}; - -/** @typedef {{ filename: string, extension?: string }} FileMiddlewareContext */ - -/** - * @typedef {BufferSerializableType[]} DeserializedType - * @typedef {true} SerializedType - * @extends {SerializerMiddleware} - */ -class FileMiddleware extends SerializerMiddleware { - /** - * @param {IntermediateFileSystem} fs filesystem - * @param {string | Hash} hashFunction hash function to use - */ - constructor(fs, hashFunction = "md4") { - super(); - this.fs = fs; - this._hashFunction = hashFunction; - } - - /** - * @param {DeserializedType} data data - * @param {object} context context object - * @returns {SerializedType|Promise} serialized data - */ - serialize(data, context) { - const { filename, extension = "" } = context; - return new Promise((resolve, reject) => { - mkdirp(this.fs, dirname(this.fs, filename), err => { - if (err) return reject(err); - - // It's important that we don't touch existing files during serialization - // because serialize may read existing files (when deserializing) - const allWrittenFiles = new Set(); - /** - * @param {string | false} name name - * @param {Buffer[]} content content - * @param {number} size size - * @returns {Promise} - */ - const writeFile = async (name, content, size) => { - const file = name - ? join(this.fs, filename, `../${name}${extension}`) - : filename; - await new Promise( - /** - * @param {(value?: undefined) => void} resolve resolve - * @param {(reason?: Error | null) => void} reject reject - */ - (resolve, reject) => { - let stream = this.fs.createWriteStream(`${file}_`); - let compression; - if (file.endsWith(".gz")) { - compression = createGzip({ - chunkSize: COMPRESSION_CHUNK_SIZE, - level: zConstants.Z_BEST_SPEED - }); - } else if (file.endsWith(".br")) { - compression = createBrotliCompress({ - chunkSize: COMPRESSION_CHUNK_SIZE, - params: { - [zConstants.BROTLI_PARAM_MODE]: zConstants.BROTLI_MODE_TEXT, - [zConstants.BROTLI_PARAM_QUALITY]: 2, - [zConstants.BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING]: true, - [zConstants.BROTLI_PARAM_SIZE_HINT]: size - } - }); - } - if (compression) { - pipeline(compression, stream, reject); - stream = compression; - stream.on("finish", () => resolve()); - } else { - stream.on("error", err => reject(err)); - stream.on("finish", () => resolve()); - } - // split into chunks for WRITE_LIMIT_CHUNK size - /** @type {TODO[]} */ - const chunks = []; - for (const b of content) { - if (b.length < WRITE_LIMIT_CHUNK) { - chunks.push(b); - } else { - for (let i = 0; i < b.length; i += WRITE_LIMIT_CHUNK) { - chunks.push(b.slice(i, i + WRITE_LIMIT_CHUNK)); - } - } - } - - const len = chunks.length; - let i = 0; - /** - * @param {(Error | null)=} err err - */ - const batchWrite = err => { - // will be handled in "on" error handler - if (err) return; - - if (i === len) { - stream.end(); - return; - } - - // queue up a batch of chunks up to the write limit - // end is exclusive - let end = i; - let sum = chunks[end++].length; - while (end < len) { - sum += chunks[end].length; - if (sum > WRITE_LIMIT_TOTAL) break; - end++; - } - while (i < end - 1) { - stream.write(chunks[i++]); - } - stream.write(chunks[i++], batchWrite); - }; - batchWrite(); - } - ); - if (name) allWrittenFiles.add(file); - }; - - resolve( - serialize(this, data, false, writeFile, this._hashFunction).then( - async ({ backgroundJob }) => { - await backgroundJob; - - // Rename the index file to disallow access during inconsistent file state - await new Promise( - /** - * @param {(value?: undefined) => void} resolve resolve - */ - resolve => { - this.fs.rename(filename, `${filename}.old`, err => { - resolve(); - }); - } - ); - - // update all written files - await Promise.all( - Array.from( - allWrittenFiles, - file => - new Promise( - /** - * @param {(value?: undefined) => void} resolve resolve - * @param {(reason?: Error | null) => void} reject reject - * @returns {void} - */ - (resolve, reject) => { - this.fs.rename(`${file}_`, file, err => { - if (err) return reject(err); - resolve(); - }); - } - ) - ) - ); - - // As final step automatically update the index file to have a consistent pack again - await new Promise( - /** - * @param {(value?: undefined) => void} resolve resolve - * @returns {void} - */ - resolve => { - this.fs.rename(`${filename}_`, filename, err => { - if (err) return reject(err); - resolve(); - }); - } - ); - return /** @type {true} */ (true); - } - ) - ); - }); - }); - } - - /** - * @param {SerializedType} data data - * @param {object} context context object - * @returns {DeserializedType|Promise} deserialized data - */ - deserialize(data, context) { - const { filename, extension = "" } = context; - /** - * @param {string | boolean} name name - * @returns {Promise} result - */ - const readFile = name => - new Promise((resolve, reject) => { - const file = name - ? join(this.fs, filename, `../${name}${extension}`) - : filename; - this.fs.stat(file, (err, stats) => { - if (err) { - reject(err); - return; - } - let remaining = /** @type {IStats} */ (stats).size; - /** @type {Buffer | undefined} */ - let currentBuffer; - /** @type {number | undefined} */ - let currentBufferUsed; - /** @type {any[]} */ - const buf = []; - /** @type {import("zlib").Zlib & import("stream").Transform | undefined} */ - let decompression; - if (file.endsWith(".gz")) { - decompression = createGunzip({ - chunkSize: DECOMPRESSION_CHUNK_SIZE - }); - } else if (file.endsWith(".br")) { - decompression = createBrotliDecompress({ - chunkSize: DECOMPRESSION_CHUNK_SIZE - }); - } - if (decompression) { - let newResolve; - let newReject; - resolve( - Promise.all([ - new Promise((rs, rj) => { - newResolve = rs; - newReject = rj; - }), - new Promise((resolve, reject) => { - decompression.on("data", chunk => buf.push(chunk)); - decompression.on("end", () => resolve()); - decompression.on("error", err => reject(err)); - }) - ]).then(() => buf) - ); - resolve = newResolve; - reject = newReject; - } - this.fs.open(file, "r", (err, _fd) => { - if (err) { - reject(err); - return; - } - const fd = /** @type {number} */ (_fd); - const read = () => { - if (currentBuffer === undefined) { - currentBuffer = Buffer.allocUnsafeSlow( - Math.min( - constants.MAX_LENGTH, - remaining, - decompression ? DECOMPRESSION_CHUNK_SIZE : Infinity - ) - ); - currentBufferUsed = 0; - } - let readBuffer = currentBuffer; - let readOffset = /** @type {number} */ (currentBufferUsed); - let readLength = - currentBuffer.length - - /** @type {number} */ (currentBufferUsed); - // values passed to fs.read must be valid int32 values - if (readOffset > 0x7fffffff) { - readBuffer = currentBuffer.slice(readOffset); - readOffset = 0; - } - if (readLength > 0x7fffffff) { - readLength = 0x7fffffff; - } - this.fs.read( - fd, - readBuffer, - readOffset, - readLength, - null, - (err, bytesRead) => { - if (err) { - this.fs.close(fd, () => { - reject(err); - }); - return; - } - /** @type {number} */ - (currentBufferUsed) += bytesRead; - remaining -= bytesRead; - if ( - currentBufferUsed === - /** @type {Buffer} */ (currentBuffer).length - ) { - if (decompression) { - decompression.write(currentBuffer); - } else { - buf.push(currentBuffer); - } - currentBuffer = undefined; - if (remaining === 0) { - if (decompression) { - decompression.end(); - } - this.fs.close(fd, err => { - if (err) { - reject(err); - return; - } - resolve(buf); - }); - return; - } - } - read(); - } - ); - }; - read(); - }); - }); - }); - return deserialize(this, false, readFile); - } -} - -module.exports = FileMiddleware; diff --git a/webpack-lib/lib/serialization/MapObjectSerializer.js b/webpack-lib/lib/serialization/MapObjectSerializer.js deleted file mode 100644 index 0b1f4182b96..00000000000 --- a/webpack-lib/lib/serialization/MapObjectSerializer.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class MapObjectSerializer { - /** - * @template K, V - * @param {Map} obj map - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - context.write(obj.size); - for (const key of obj.keys()) { - context.write(key); - } - for (const value of obj.values()) { - context.write(value); - } - } - - /** - * @template K, V - * @param {ObjectDeserializerContext} context context - * @returns {Map} map - */ - deserialize(context) { - /** @type {number} */ - const size = context.read(); - /** @type {Map} */ - const map = new Map(); - /** @type {K[]} */ - const keys = []; - for (let i = 0; i < size; i++) { - keys.push(context.read()); - } - for (let i = 0; i < size; i++) { - map.set(keys[i], context.read()); - } - return map; - } -} - -module.exports = MapObjectSerializer; diff --git a/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js b/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js deleted file mode 100644 index 32adaeaaede..00000000000 --- a/webpack-lib/lib/serialization/NullPrototypeObjectSerializer.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class NullPrototypeObjectSerializer { - /** - * @template {object} T - * @param {T} obj null object - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - /** @type {string[]} */ - const keys = Object.keys(obj); - for (const key of keys) { - context.write(key); - } - context.write(null); - for (const key of keys) { - context.write(obj[/** @type {keyof T} */ (key)]); - } - } - - /** - * @template {object} T - * @param {ObjectDeserializerContext} context context - * @returns {T} null object - */ - deserialize(context) { - /** @type {T} */ - const obj = Object.create(null); - /** @type {string[]} */ - const keys = []; - /** @type {string | null} */ - let key = context.read(); - while (key !== null) { - keys.push(key); - key = context.read(); - } - for (const key of keys) { - obj[/** @type {keyof T} */ (key)] = context.read(); - } - return obj; - } -} - -module.exports = NullPrototypeObjectSerializer; diff --git a/webpack-lib/lib/serialization/ObjectMiddleware.js b/webpack-lib/lib/serialization/ObjectMiddleware.js deleted file mode 100644 index 13464478199..00000000000 --- a/webpack-lib/lib/serialization/ObjectMiddleware.js +++ /dev/null @@ -1,778 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const createHash = require("../util/createHash"); -const ArraySerializer = require("./ArraySerializer"); -const DateObjectSerializer = require("./DateObjectSerializer"); -const ErrorObjectSerializer = require("./ErrorObjectSerializer"); -const MapObjectSerializer = require("./MapObjectSerializer"); -const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer"); -const PlainObjectSerializer = require("./PlainObjectSerializer"); -const RegExpObjectSerializer = require("./RegExpObjectSerializer"); -const SerializerMiddleware = require("./SerializerMiddleware"); -const SetObjectSerializer = require("./SetObjectSerializer"); - -/** @typedef {typeof import("../util/Hash")} Hash */ -/** @typedef {import("./types").ComplexSerializableType} ComplexSerializableType */ -/** @typedef {import("./types").PrimitiveSerializableType} PrimitiveSerializableType */ - -/** @typedef {new (...params: any[]) => any} Constructor */ - -/* - -Format: - -File -> Section* -Section -> ObjectSection | ReferenceSection | EscapeSection | OtherSection - -ObjectSection -> ESCAPE ( - number:relativeOffset (number > 0) | - string:request (string|null):export -) Section:value* ESCAPE ESCAPE_END_OBJECT -ReferenceSection -> ESCAPE number:relativeOffset (number < 0) -EscapeSection -> ESCAPE ESCAPE_ESCAPE_VALUE (escaped value ESCAPE) -EscapeSection -> ESCAPE ESCAPE_UNDEFINED (escaped value ESCAPE) -OtherSection -> any (except ESCAPE) - -Why using null as escape value? -Multiple null values can merged by the BinaryMiddleware, which makes it very efficient -Technically any value can be used. - -*/ - -/** - * @typedef {object} ObjectSerializerContext - * @property {function(any): void} write - * @property {(function(any): void)=} writeLazy - * @property {(function(any, object=): (() => Promise | any))=} writeSeparate - * @property {function(any): void} setCircularReference - */ - -/** - * @typedef {object} ObjectDeserializerContext - * @property {function(): any} read - * @property {function(any): void} setCircularReference - */ - -/** - * @typedef {object} ObjectSerializer - * @property {function(any, ObjectSerializerContext): void} serialize - * @property {function(ObjectDeserializerContext): any} deserialize - */ - -/** - * @template T - * @param {Set} set set - * @param {number} size count of items to keep - */ -const setSetSize = (set, size) => { - let i = 0; - for (const item of set) { - if (i++ >= size) { - set.delete(item); - } - } -}; - -/** - * @template K, X - * @param {Map} map map - * @param {number} size count of items to keep - */ -const setMapSize = (map, size) => { - let i = 0; - for (const item of map.keys()) { - if (i++ >= size) { - map.delete(item); - } - } -}; - -/** - * @param {Buffer} buffer buffer - * @param {string | Hash} hashFunction hash function to use - * @returns {string} hash - */ -const toHash = (buffer, hashFunction) => { - const hash = createHash(hashFunction); - hash.update(buffer); - return /** @type {string} */ (hash.digest("latin1")); -}; - -const ESCAPE = null; -const ESCAPE_ESCAPE_VALUE = null; -const ESCAPE_END_OBJECT = true; -const ESCAPE_UNDEFINED = false; - -const CURRENT_VERSION = 2; - -/** @type {Map} */ -const serializers = new Map(); -/** @type {Map} */ -const serializerInversed = new Map(); - -/** @type {Set} */ -const loadedRequests = new Set(); - -const NOT_SERIALIZABLE = {}; - -const jsTypes = new Map(); -jsTypes.set(Object, new PlainObjectSerializer()); -jsTypes.set(Array, new ArraySerializer()); -jsTypes.set(null, new NullPrototypeObjectSerializer()); -jsTypes.set(Map, new MapObjectSerializer()); -jsTypes.set(Set, new SetObjectSerializer()); -jsTypes.set(Date, new DateObjectSerializer()); -jsTypes.set(RegExp, new RegExpObjectSerializer()); -jsTypes.set(Error, new ErrorObjectSerializer(Error)); -jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError)); -jsTypes.set(RangeError, new ErrorObjectSerializer(RangeError)); -jsTypes.set(ReferenceError, new ErrorObjectSerializer(ReferenceError)); -jsTypes.set(SyntaxError, new ErrorObjectSerializer(SyntaxError)); -jsTypes.set(TypeError, new ErrorObjectSerializer(TypeError)); - -// If in a sandboxed environment (e. g. jest), this escapes the sandbox and registers -// real Object and Array types to. These types may occur in the wild too, e. g. when -// using Structured Clone in postMessage. -// eslint-disable-next-line n/exports-style -if (exports.constructor !== Object) { - // eslint-disable-next-line jsdoc/check-types, n/exports-style - const Obj = /** @type {typeof Object} */ (exports.constructor); - const Fn = /** @type {typeof Function} */ (Obj.constructor); - for (const [type, config] of Array.from(jsTypes)) { - if (type) { - const Type = new Fn(`return ${type.name};`)(); - jsTypes.set(Type, config); - } - } -} - -{ - let i = 1; - for (const [type, serializer] of jsTypes) { - serializers.set(type, { - request: "", - name: i++, - serializer - }); - } -} - -for (const { request, name, serializer } of serializers.values()) { - serializerInversed.set( - `${request}/${name}`, - /** @type {ObjectSerializer} */ (serializer) - ); -} - -/** @type {Map boolean>} */ -const loaders = new Map(); - -/** - * @typedef {ComplexSerializableType[]} DeserializedType - * @typedef {PrimitiveSerializableType[]} SerializedType - * @extends {SerializerMiddleware} - */ -class ObjectMiddleware extends SerializerMiddleware { - /** - * @param {function(any): void} extendContext context extensions - * @param {string | Hash} hashFunction hash function to use - */ - constructor(extendContext, hashFunction = "md4") { - super(); - this.extendContext = extendContext; - this._hashFunction = hashFunction; - } - - /** - * @param {RegExp} regExp RegExp for which the request is tested - * @param {function(string): boolean} loader loader to load the request, returns true when successful - * @returns {void} - */ - static registerLoader(regExp, loader) { - loaders.set(regExp, loader); - } - - /** - * @param {Constructor} Constructor the constructor - * @param {string} request the request which will be required when deserializing - * @param {string | null} name the name to make multiple serializer unique when sharing a request - * @param {ObjectSerializer} serializer the serializer - * @returns {void} - */ - static register(Constructor, request, name, serializer) { - const key = `${request}/${name}`; - - if (serializers.has(Constructor)) { - throw new Error( - `ObjectMiddleware.register: serializer for ${Constructor.name} is already registered` - ); - } - - if (serializerInversed.has(key)) { - throw new Error( - `ObjectMiddleware.register: serializer for ${key} is already registered` - ); - } - - serializers.set(Constructor, { - request, - name, - serializer - }); - - serializerInversed.set(key, serializer); - } - - /** - * @param {Constructor} Constructor the constructor - * @returns {void} - */ - static registerNotSerializable(Constructor) { - if (serializers.has(Constructor)) { - throw new Error( - `ObjectMiddleware.registerNotSerializable: serializer for ${Constructor.name} is already registered` - ); - } - - serializers.set(Constructor, NOT_SERIALIZABLE); - } - - static getSerializerFor(object) { - const proto = Object.getPrototypeOf(object); - let c; - if (proto === null) { - // Object created with Object.create(null) - c = null; - } else { - c = proto.constructor; - if (!c) { - throw new Error( - "Serialization of objects with prototype without valid constructor property not possible" - ); - } - } - const config = serializers.get(c); - - if (!config) throw new Error(`No serializer registered for ${c.name}`); - if (config === NOT_SERIALIZABLE) throw NOT_SERIALIZABLE; - - return config; - } - - /** - * @param {string} request request - * @param {TODO} name name - * @returns {ObjectSerializer} serializer - */ - static getDeserializerFor(request, name) { - const key = `${request}/${name}`; - const serializer = serializerInversed.get(key); - - if (serializer === undefined) { - throw new Error(`No deserializer registered for ${key}`); - } - - return serializer; - } - - /** - * @param {string} request request - * @param {string} name name - * @returns {ObjectSerializer | undefined} serializer - */ - static _getDeserializerForWithoutError(request, name) { - const key = `${request}/${name}`; - const serializer = serializerInversed.get(key); - return serializer; - } - - /** - * @param {DeserializedType} data data - * @param {object} context context object - * @returns {SerializedType|Promise} serialized data - */ - serialize(data, context) { - /** @type {any[]} */ - let result = [CURRENT_VERSION]; - let currentPos = 0; - let referenceable = new Map(); - const addReferenceable = item => { - referenceable.set(item, currentPos++); - }; - let bufferDedupeMap = new Map(); - /** - * @param {Buffer} buf buffer - * @returns {Buffer} deduped buffer - */ - const dedupeBuffer = buf => { - const len = buf.length; - const entry = bufferDedupeMap.get(len); - if (entry === undefined) { - bufferDedupeMap.set(len, buf); - return buf; - } - if (Buffer.isBuffer(entry)) { - if (len < 32) { - if (buf.equals(entry)) { - return entry; - } - bufferDedupeMap.set(len, [entry, buf]); - return buf; - } - const hash = toHash(entry, this._hashFunction); - const newMap = new Map(); - newMap.set(hash, entry); - bufferDedupeMap.set(len, newMap); - const hashBuf = toHash(buf, this._hashFunction); - if (hash === hashBuf) { - return entry; - } - return buf; - } else if (Array.isArray(entry)) { - if (entry.length < 16) { - for (const item of entry) { - if (buf.equals(item)) { - return item; - } - } - entry.push(buf); - return buf; - } - const newMap = new Map(); - const hash = toHash(buf, this._hashFunction); - let found; - for (const item of entry) { - const itemHash = toHash(item, this._hashFunction); - newMap.set(itemHash, item); - if (found === undefined && itemHash === hash) found = item; - } - bufferDedupeMap.set(len, newMap); - if (found === undefined) { - newMap.set(hash, buf); - return buf; - } - return found; - } - const hash = toHash(buf, this._hashFunction); - const item = entry.get(hash); - if (item !== undefined) { - return item; - } - entry.set(hash, buf); - return buf; - }; - let currentPosTypeLookup = 0; - let objectTypeLookup = new Map(); - const cycleStack = new Set(); - const stackToString = item => { - const arr = Array.from(cycleStack); - arr.push(item); - return arr - .map(item => { - if (typeof item === "string") { - if (item.length > 100) { - return `String ${JSON.stringify(item.slice(0, 100)).slice( - 0, - -1 - )}..."`; - } - return `String ${JSON.stringify(item)}`; - } - try { - const { request, name } = ObjectMiddleware.getSerializerFor(item); - if (request) { - return `${request}${name ? `.${name}` : ""}`; - } - } catch (_err) { - // ignore -> fallback - } - if (typeof item === "object" && item !== null) { - if (item.constructor) { - if (item.constructor === Object) - return `Object { ${Object.keys(item).join(", ")} }`; - if (item.constructor === Map) return `Map { ${item.size} items }`; - if (item.constructor === Array) - return `Array { ${item.length} items }`; - if (item.constructor === Set) return `Set { ${item.size} items }`; - if (item.constructor === RegExp) return item.toString(); - return `${item.constructor.name}`; - } - return `Object [null prototype] { ${Object.keys(item).join( - ", " - )} }`; - } - if (typeof item === "bigint") { - return `BigInt ${item}n`; - } - try { - return `${item}`; - } catch (err) { - return `(${err.message})`; - } - }) - .join(" -> "); - }; - /** @type {WeakSet} */ - let hasDebugInfoAttached; - let ctx = { - write(value, key) { - try { - process(value); - } catch (err) { - if (err !== NOT_SERIALIZABLE) { - if (hasDebugInfoAttached === undefined) - hasDebugInfoAttached = new WeakSet(); - if (!hasDebugInfoAttached.has(/** @type {Error} */ (err))) { - /** @type {Error} */ - (err).message += `\nwhile serializing ${stackToString(value)}`; - hasDebugInfoAttached.add(/** @type {Error} */ (err)); - } - } - throw err; - } - }, - setCircularReference(ref) { - addReferenceable(ref); - }, - snapshot() { - return { - length: result.length, - cycleStackSize: cycleStack.size, - referenceableSize: referenceable.size, - currentPos, - objectTypeLookupSize: objectTypeLookup.size, - currentPosTypeLookup - }; - }, - rollback(snapshot) { - result.length = snapshot.length; - setSetSize(cycleStack, snapshot.cycleStackSize); - setMapSize(referenceable, snapshot.referenceableSize); - currentPos = snapshot.currentPos; - setMapSize(objectTypeLookup, snapshot.objectTypeLookupSize); - currentPosTypeLookup = snapshot.currentPosTypeLookup; - }, - ...context - }; - this.extendContext(ctx); - const process = item => { - if (Buffer.isBuffer(item)) { - // check if we can emit a reference - const ref = referenceable.get(item); - if (ref !== undefined) { - result.push(ESCAPE, ref - currentPos); - return; - } - const alreadyUsedBuffer = dedupeBuffer(item); - if (alreadyUsedBuffer !== item) { - const ref = referenceable.get(alreadyUsedBuffer); - if (ref !== undefined) { - referenceable.set(item, ref); - result.push(ESCAPE, ref - currentPos); - return; - } - item = alreadyUsedBuffer; - } - addReferenceable(item); - - result.push(item); - } else if (item === ESCAPE) { - result.push(ESCAPE, ESCAPE_ESCAPE_VALUE); - } else if ( - typeof item === "object" - // We don't have to check for null as ESCAPE is null and this has been checked before - ) { - // check if we can emit a reference - const ref = referenceable.get(item); - if (ref !== undefined) { - result.push(ESCAPE, ref - currentPos); - return; - } - - if (cycleStack.has(item)) { - throw new Error( - "This is a circular references. To serialize circular references use 'setCircularReference' somewhere in the circle during serialize and deserialize." - ); - } - - const { request, name, serializer } = - ObjectMiddleware.getSerializerFor(item); - const key = `${request}/${name}`; - const lastIndex = objectTypeLookup.get(key); - - if (lastIndex === undefined) { - objectTypeLookup.set(key, currentPosTypeLookup++); - - result.push(ESCAPE, request, name); - } else { - result.push(ESCAPE, currentPosTypeLookup - lastIndex); - } - - cycleStack.add(item); - - try { - serializer.serialize(item, ctx); - } finally { - cycleStack.delete(item); - } - - result.push(ESCAPE, ESCAPE_END_OBJECT); - - addReferenceable(item); - } else if (typeof item === "string") { - if (item.length > 1) { - // short strings are shorter when not emitting a reference (this saves 1 byte per empty string) - // check if we can emit a reference - const ref = referenceable.get(item); - if (ref !== undefined) { - result.push(ESCAPE, ref - currentPos); - return; - } - addReferenceable(item); - } - - if (item.length > 102400 && context.logger) { - context.logger.warn( - `Serializing big strings (${Math.round( - item.length / 1024 - )}kiB) impacts deserialization performance (consider using Buffer instead and decode when needed)` - ); - } - - result.push(item); - } else if (typeof item === "function") { - if (!SerializerMiddleware.isLazy(item)) - throw new Error(`Unexpected function ${item}`); - /** @type {SerializedType} */ - const serializedData = - SerializerMiddleware.getLazySerializedValue(item); - if (serializedData !== undefined) { - if (typeof serializedData === "function") { - result.push(serializedData); - } else { - throw new Error("Not implemented"); - } - } else if (SerializerMiddleware.isLazy(item, this)) { - throw new Error("Not implemented"); - } else { - const data = SerializerMiddleware.serializeLazy(item, data => - this.serialize([data], context) - ); - SerializerMiddleware.setLazySerializedValue(item, data); - result.push(data); - } - } else if (item === undefined) { - result.push(ESCAPE, ESCAPE_UNDEFINED); - } else { - result.push(item); - } - }; - - try { - for (const item of data) { - process(item); - } - return result; - } catch (err) { - if (err === NOT_SERIALIZABLE) return null; - - throw err; - } finally { - // Get rid of these references to avoid leaking memory - // This happens because the optimized code v8 generates - // is optimized for our "ctx.write" method so it will reference - // it from e. g. Dependency.prototype.serialize -(IC)-> ctx.write - data = - result = - referenceable = - bufferDedupeMap = - objectTypeLookup = - ctx = - /** @type {EXPECTED_ANY} */ - (undefined); - } - } - - /** - * @param {SerializedType} data data - * @param {object} context context object - * @returns {DeserializedType|Promise} deserialized data - */ - deserialize(data, context) { - let currentDataPos = 0; - const read = () => { - if (currentDataPos >= data.length) - throw new Error("Unexpected end of stream"); - - return data[currentDataPos++]; - }; - - if (read() !== CURRENT_VERSION) - throw new Error("Version mismatch, serializer changed"); - - let currentPos = 0; - let referenceable = []; - const addReferenceable = item => { - referenceable.push(item); - currentPos++; - }; - let currentPosTypeLookup = 0; - let objectTypeLookup = []; - let result = []; - let ctx = { - read() { - return decodeValue(); - }, - setCircularReference(ref) { - addReferenceable(ref); - }, - ...context - }; - this.extendContext(ctx); - const decodeValue = () => { - const item = read(); - - if (item === ESCAPE) { - const nextItem = read(); - - if (nextItem === ESCAPE_ESCAPE_VALUE) { - return ESCAPE; - } else if (nextItem === ESCAPE_UNDEFINED) { - // Nothing - } else if (nextItem === ESCAPE_END_OBJECT) { - throw new Error( - `Unexpected end of object at position ${currentDataPos - 1}` - ); - } else { - const request = nextItem; - let serializer; - - if (typeof request === "number") { - if (request < 0) { - // relative reference - return referenceable[currentPos + request]; - } - serializer = objectTypeLookup[currentPosTypeLookup - request]; - } else { - if (typeof request !== "string") { - throw new Error( - `Unexpected type (${typeof request}) of request ` + - `at position ${currentDataPos - 1}` - ); - } - const name = /** @type {string} */ (read()); - - serializer = ObjectMiddleware._getDeserializerForWithoutError( - request, - name - ); - - if (serializer === undefined) { - if (request && !loadedRequests.has(request)) { - let loaded = false; - for (const [regExp, loader] of loaders) { - if (regExp.test(request) && loader(request)) { - loaded = true; - break; - } - } - if (!loaded) { - require(request); - } - - loadedRequests.add(request); - } - - serializer = ObjectMiddleware.getDeserializerFor(request, name); - } - - objectTypeLookup.push(serializer); - currentPosTypeLookup++; - } - try { - const item = serializer.deserialize(ctx); - const end1 = read(); - - if (end1 !== ESCAPE) { - throw new Error("Expected end of object"); - } - - const end2 = read(); - - if (end2 !== ESCAPE_END_OBJECT) { - throw new Error("Expected end of object"); - } - - addReferenceable(item); - - return item; - } catch (err) { - // As this is only for error handling, we omit creating a Map for - // faster access to this information, as this would affect performance - // in the good case - let serializerEntry; - for (const entry of serializers) { - if (entry[1].serializer === serializer) { - serializerEntry = entry; - break; - } - } - const name = !serializerEntry - ? "unknown" - : !serializerEntry[1].request - ? serializerEntry[0].name - : serializerEntry[1].name - ? `${serializerEntry[1].request} ${serializerEntry[1].name}` - : serializerEntry[1].request; - /** @type {Error} */ - (err).message += `\n(during deserialization of ${name})`; - throw err; - } - } - } else if (typeof item === "string") { - if (item.length > 1) { - addReferenceable(item); - } - - return item; - } else if (Buffer.isBuffer(item)) { - addReferenceable(item); - - return item; - } else if (typeof item === "function") { - return SerializerMiddleware.deserializeLazy( - item, - data => this.deserialize(data, context)[0] - ); - } else { - return item; - } - }; - - try { - while (currentDataPos < data.length) { - result.push(decodeValue()); - } - return result; - } finally { - // Get rid of these references to avoid leaking memory - // This happens because the optimized code v8 generates - // is optimized for our "ctx.read" method so it will reference - // it from e. g. Dependency.prototype.deserialize -(IC)-> ctx.read - result = - referenceable = - data = - objectTypeLookup = - ctx = - /** @type {EXPECTED_ANY} */ - (undefined); - } - } -} - -module.exports = ObjectMiddleware; -module.exports.NOT_SERIALIZABLE = NOT_SERIALIZABLE; diff --git a/webpack-lib/lib/serialization/PlainObjectSerializer.js b/webpack-lib/lib/serialization/PlainObjectSerializer.js deleted file mode 100644 index 428825e388e..00000000000 --- a/webpack-lib/lib/serialization/PlainObjectSerializer.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** @typedef {(arg0?: any) => void} CacheAssoc */ - -/** - * @template T - * @typedef {WeakMap>} - */ -const cache = new WeakMap(); - -/** - * @template T - */ -class ObjectStructure { - constructor() { - this.keys = undefined; - this.children = undefined; - } - - /** - * @param {keyof T[]} keys keys - * @returns {keyof T[]} keys - */ - getKeys(keys) { - if (this.keys === undefined) this.keys = keys; - return this.keys; - } - - /** - * @param {keyof T} key key - * @returns {ObjectStructure} object structure - */ - key(key) { - if (this.children === undefined) this.children = new Map(); - const child = this.children.get(key); - if (child !== undefined) return child; - const newChild = new ObjectStructure(); - this.children.set(key, newChild); - return newChild; - } -} - -/** - * @template T - * @param {(keyof T)[]} keys keys - * @param {CacheAssoc} cacheAssoc cache assoc fn - * @returns {(keyof T)[]} keys - */ -const getCachedKeys = (keys, cacheAssoc) => { - let root = cache.get(cacheAssoc); - if (root === undefined) { - root = new ObjectStructure(); - cache.set(cacheAssoc, root); - } - let current = root; - for (const key of keys) { - current = current.key(key); - } - return current.getKeys(keys); -}; - -class PlainObjectSerializer { - /** - * @template {object} T - * @param {T} obj plain object - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - const keys = /** @type {(keyof T)[]} */ (Object.keys(obj)); - if (keys.length > 128) { - // Objects with so many keys are unlikely to share structure - // with other objects - context.write(keys); - for (const key of keys) { - context.write(obj[key]); - } - } else if (keys.length > 1) { - context.write(getCachedKeys(keys, context.write)); - for (const key of keys) { - context.write(obj[key]); - } - } else if (keys.length === 1) { - const key = keys[0]; - context.write(key); - context.write(obj[key]); - } else { - context.write(null); - } - } - - /** - * @template {object} T - * @param {ObjectDeserializerContext} context context - * @returns {T} plain object - */ - deserialize(context) { - const keys = context.read(); - const obj = /** @type {T} */ ({}); - if (Array.isArray(keys)) { - for (const key of keys) { - obj[/** @type {keyof T} */ (key)] = context.read(); - } - } else if (keys !== null) { - obj[/** @type {keyof T} */ (keys)] = context.read(); - } - return obj; - } -} - -module.exports = PlainObjectSerializer; diff --git a/webpack-lib/lib/serialization/RegExpObjectSerializer.js b/webpack-lib/lib/serialization/RegExpObjectSerializer.js deleted file mode 100644 index 5ac7eec5105..00000000000 --- a/webpack-lib/lib/serialization/RegExpObjectSerializer.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class RegExpObjectSerializer { - /** - * @param {RegExp} obj regexp - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - context.write(obj.source); - context.write(obj.flags); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RegExp} regexp - */ - deserialize(context) { - return new RegExp(context.read(), context.read()); - } -} - -module.exports = RegExpObjectSerializer; diff --git a/webpack-lib/lib/serialization/Serializer.js b/webpack-lib/lib/serialization/Serializer.js deleted file mode 100644 index ce241c67047..00000000000 --- a/webpack-lib/lib/serialization/Serializer.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** - * @template T, K - * @typedef {import("./SerializerMiddleware")} SerializerMiddleware - */ - -class Serializer { - /** - * @param {SerializerMiddleware[]} middlewares serializer middlewares - * @param {TODO=} context context - */ - constructor(middlewares, context) { - this.serializeMiddlewares = middlewares.slice(); - this.deserializeMiddlewares = middlewares.slice().reverse(); - this.context = context; - } - - /** - * @param {any} obj object - * @param {TODO} context content - * @returns {Promise} result - */ - serialize(obj, context) { - const ctx = { ...context, ...this.context }; - let current = obj; - for (const middleware of this.serializeMiddlewares) { - if (current && typeof current.then === "function") { - current = current.then(data => data && middleware.serialize(data, ctx)); - } else if (current) { - try { - current = middleware.serialize(current, ctx); - } catch (err) { - current = Promise.reject(err); - } - } else break; - } - return current; - } - - /** - * @param {any} value value - * @param {TODO} context context - * @returns {Promise} result - */ - deserialize(value, context) { - const ctx = { ...context, ...this.context }; - /** @type {any} */ - let current = value; - for (const middleware of this.deserializeMiddlewares) { - current = - current && typeof current.then === "function" - ? current.then(data => middleware.deserialize(data, ctx)) - : middleware.deserialize(current, ctx); - } - return current; - } -} - -module.exports = Serializer; diff --git a/webpack-lib/lib/serialization/SerializerMiddleware.js b/webpack-lib/lib/serialization/SerializerMiddleware.js deleted file mode 100644 index 0053fb0b6b6..00000000000 --- a/webpack-lib/lib/serialization/SerializerMiddleware.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const memoize = require("../util/memoize"); - -const LAZY_TARGET = Symbol("lazy serialization target"); -const LAZY_SERIALIZED_VALUE = Symbol("lazy serialization data"); - -/** - * @template DeserializedType - * @template SerializedType - */ -class SerializerMiddleware { - /* istanbul ignore next */ - /** - * @abstract - * @param {DeserializedType} data data - * @param {object} context context object - * @returns {SerializedType|Promise} serialized data - */ - serialize(data, context) { - const AbstractMethodError = require("../AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * @abstract - * @param {SerializedType} data data - * @param {object} context context object - * @returns {DeserializedType|Promise} deserialized data - */ - deserialize(data, context) { - const AbstractMethodError = require("../AbstractMethodError"); - throw new AbstractMethodError(); - } - - /** - * @param {any | function(): Promise | any} value contained value or function to value - * @param {SerializerMiddleware} target target middleware - * @param {object=} options lazy options - * @param {any=} serializedValue serialized value - * @returns {function(): Promise | any} lazy function - */ - static createLazy(value, target, options = {}, serializedValue = undefined) { - if (SerializerMiddleware.isLazy(value, target)) return value; - const fn = typeof value === "function" ? value : () => value; - fn[LAZY_TARGET] = target; - /** @type {any} */ (fn).options = options; - fn[LAZY_SERIALIZED_VALUE] = serializedValue; - return fn; - } - - /** - * @param {function(): Promise | any} fn lazy function - * @param {SerializerMiddleware=} target target middleware - * @returns {boolean} true, when fn is a lazy function (optionally of that target) - */ - static isLazy(fn, target) { - if (typeof fn !== "function") return false; - const t = fn[LAZY_TARGET]; - return target ? t === target : Boolean(t); - } - - /** - * @param {function(): Promise | any} fn lazy function - * @returns {object | undefined} options - */ - static getLazyOptions(fn) { - if (typeof fn !== "function") return; - return /** @type {any} */ (fn).options; - } - - /** - * @param {function(): Promise | any} fn lazy function - * @returns {any | undefined} serialized value - */ - static getLazySerializedValue(fn) { - if (typeof fn !== "function") return; - return fn[LAZY_SERIALIZED_VALUE]; - } - - /** - * @param {function(): Promise | any} fn lazy function - * @param {any} value serialized value - * @returns {void} - */ - static setLazySerializedValue(fn, value) { - fn[LAZY_SERIALIZED_VALUE] = value; - } - - /** - * @param {function(): Promise | any} lazy lazy function - * @param {function(any): Promise | any} serialize serialize function - * @returns {function(): Promise | any} new lazy - */ - static serializeLazy(lazy, serialize) { - const fn = memoize(() => { - const r = lazy(); - if (r && typeof r.then === "function") { - return r.then(data => data && serialize(data)); - } - return serialize(r); - }); - fn[LAZY_TARGET] = lazy[LAZY_TARGET]; - /** @type {any} */ (fn).options = /** @type {any} */ (lazy).options; - lazy[LAZY_SERIALIZED_VALUE] = fn; - return fn; - } - - /** - * @template T - * @param {function(): Promise | any} lazy lazy function - * @param {function(T): Promise | T} deserialize deserialize function - * @returns {function(): Promise | T} new lazy - */ - static deserializeLazy(lazy, deserialize) { - const fn = memoize(() => { - const r = lazy(); - if (r && typeof r.then === "function") { - return r.then(data => deserialize(data)); - } - return deserialize(r); - }); - fn[LAZY_TARGET] = lazy[LAZY_TARGET]; - /** @type {any} */ (fn).options = /** @type {any} */ (lazy).options; - fn[LAZY_SERIALIZED_VALUE] = lazy; - return fn; - } - - /** - * @param {function(): Promise | any} lazy lazy function - * @returns {function(): Promise | any} new lazy - */ - static unMemoizeLazy(lazy) { - if (!SerializerMiddleware.isLazy(lazy)) return lazy; - const fn = () => { - throw new Error( - "A lazy value that has been unmemorized can't be called again" - ); - }; - fn[LAZY_SERIALIZED_VALUE] = SerializerMiddleware.unMemoizeLazy( - lazy[LAZY_SERIALIZED_VALUE] - ); - fn[LAZY_TARGET] = lazy[LAZY_TARGET]; - fn.options = /** @type {any} */ (lazy).options; - return fn; - } -} - -module.exports = SerializerMiddleware; diff --git a/webpack-lib/lib/serialization/SetObjectSerializer.js b/webpack-lib/lib/serialization/SetObjectSerializer.js deleted file mode 100644 index 66811b87d16..00000000000 --- a/webpack-lib/lib/serialization/SetObjectSerializer.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {import("./ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class SetObjectSerializer { - /** - * @template T - * @param {Set} obj set - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - context.write(obj.size); - for (const value of obj) { - context.write(value); - } - } - - /** - * @template T - * @param {ObjectDeserializerContext} context context - * @returns {Set} date - */ - deserialize(context) { - /** @type {number} */ - const size = context.read(); - /** @type {Set} */ - const set = new Set(); - for (let i = 0; i < size; i++) { - set.add(context.read()); - } - return set; - } -} - -module.exports = SetObjectSerializer; diff --git a/webpack-lib/lib/serialization/SingleItemMiddleware.js b/webpack-lib/lib/serialization/SingleItemMiddleware.js deleted file mode 100644 index faf95de6a17..00000000000 --- a/webpack-lib/lib/serialization/SingleItemMiddleware.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const SerializerMiddleware = require("./SerializerMiddleware"); - -/** - * @typedef {any} DeserializedType - * @typedef {any[]} SerializedType - * @extends {SerializerMiddleware} - */ -class SingleItemMiddleware extends SerializerMiddleware { - /** - * @param {DeserializedType} data data - * @param {object} context context object - * @returns {SerializedType|Promise} serialized data - */ - serialize(data, context) { - return [data]; - } - - /** - * @param {SerializedType} data data - * @param {object} context context object - * @returns {DeserializedType|Promise} deserialized data - */ - deserialize(data, context) { - return data[0]; - } -} - -module.exports = SingleItemMiddleware; diff --git a/webpack-lib/lib/serialization/types.js b/webpack-lib/lib/serialization/types.js deleted file mode 100644 index b0f022f20d1..00000000000 --- a/webpack-lib/lib/serialization/types.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @typedef {undefined | null | number | string | boolean | Buffer | object | (() => ComplexSerializableType[] | Promise)} ComplexSerializableType */ - -/** @typedef {undefined | null | number | bigint | string | boolean | Buffer | (() => PrimitiveSerializableType[] | Promise)} PrimitiveSerializableType */ - -/** @typedef {Buffer | (() => BufferSerializableType[] | Promise)} BufferSerializableType */ - -module.exports = {}; diff --git a/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js b/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js deleted file mode 100644 index bd6eefef13f..00000000000 --- a/webpack-lib/lib/sharing/ConsumeSharedFallbackDependency.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("../dependencies/ModuleDependency"); -const makeSerializable = require("../util/makeSerializable"); - -class ConsumeSharedFallbackDependency extends ModuleDependency { - /** - * @param {string} request the request - */ - constructor(request) { - super(request); - } - - get type() { - return "consume shared fallback"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ConsumeSharedFallbackDependency, - "webpack/lib/sharing/ConsumeSharedFallbackDependency" -); - -module.exports = ConsumeSharedFallbackDependency; diff --git a/webpack-lib/lib/sharing/ConsumeSharedModule.js b/webpack-lib/lib/sharing/ConsumeSharedModule.js deleted file mode 100644 index f9a745e3116..00000000000 --- a/webpack-lib/lib/sharing/ConsumeSharedModule.js +++ /dev/null @@ -1,267 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const Module = require("../Module"); -const { CONSUME_SHARED_TYPES } = require("../ModuleSourceTypesConstants"); -const { - WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE -} = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const { rangeToString, stringifyHoley } = require("../util/semver"); -const ConsumeSharedFallbackDependency = require("./ConsumeSharedFallbackDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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/semver").SemVerRange} SemVerRange */ - -/** - * @typedef {object} ConsumeOptions - * @property {string=} import fallback request - * @property {string=} importResolved resolved fallback request - * @property {string} shareKey global share key - * @property {string} shareScope share scope - * @property {SemVerRange | false | undefined} requiredVersion version requirement - * @property {string=} packageName package name to determine required version automatically - * @property {boolean} strictVersion don't use shared version even if version isn't valid - * @property {boolean} singleton use single global version - * @property {boolean} eager include the fallback module in a sync way - */ - -class ConsumeSharedModule extends Module { - /** - * @param {string} context context - * @param {ConsumeOptions} options consume options - */ - constructor(context, options) { - super(WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE, context); - this.options = options; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - const { - shareKey, - shareScope, - importResolved, - requiredVersion, - strictVersion, - singleton, - eager - } = this.options; - return `${WEBPACK_MODULE_TYPE_CONSUME_SHARED_MODULE}|${shareScope}|${shareKey}|${ - requiredVersion && rangeToString(requiredVersion) - }|${strictVersion}|${importResolved}|${singleton}|${eager}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - const { - shareKey, - shareScope, - importResolved, - requiredVersion, - strictVersion, - singleton, - eager - } = this.options; - return `consume shared module (${shareScope}) ${shareKey}@${ - requiredVersion ? rangeToString(requiredVersion) : "*" - }${strictVersion ? " (strict)" : ""}${singleton ? " (singleton)" : ""}${ - importResolved - ? ` (fallback: ${requestShortener.shorten(importResolved)})` - : "" - }${eager ? " (eager)" : ""}`; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - const { shareKey, shareScope, import: request } = this.options; - return `${ - this.layer ? `(${this.layer})/` : "" - }webpack/sharing/consume/${shareScope}/${shareKey}${ - request ? `/${request}` : "" - }`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - callback(null, !this.buildInfo); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = {}; - if (this.options.import) { - const dep = new ConsumeSharedFallbackDependency(this.options.import); - if (this.options.eager) { - this.addDependency(dep); - } else { - const block = new AsyncDependenciesBlock({}); - block.addDependency(dep); - this.addBlock(block); - } - } - callback(); - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return CONSUME_SHARED_TYPES; - } - - /** - * @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; - } - - /** - * @param {Hash} hash the hash used to track dependencies - * @param {UpdateHashContext} context context - * @returns {void} - */ - updateHash(hash, context) { - hash.update(JSON.stringify(this.options)); - super.updateHash(hash, context); - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ chunkGraph, moduleGraph, runtimeTemplate }) { - const runtimeRequirements = new Set([RuntimeGlobals.shareScopeMap]); - const { - shareScope, - shareKey, - strictVersion, - requiredVersion, - import: request, - singleton, - eager - } = this.options; - let fallbackCode; - if (request) { - if (eager) { - const dep = this.dependencies[0]; - fallbackCode = runtimeTemplate.syncModuleFactory({ - dependency: dep, - chunkGraph, - runtimeRequirements, - request: this.options.import - }); - } else { - const block = this.blocks[0]; - fallbackCode = runtimeTemplate.asyncModuleFactory({ - block, - chunkGraph, - runtimeRequirements, - request: this.options.import - }); - } - } - - const args = [ - JSON.stringify(shareScope), - JSON.stringify(shareKey), - JSON.stringify(eager) - ]; - if (requiredVersion) { - args.push(stringifyHoley(requiredVersion)); - } - if (fallbackCode) { - args.push(fallbackCode); - } - - let fn; - - if (requiredVersion) { - if (strictVersion) { - fn = singleton ? "loadStrictSingletonVersion" : "loadStrictVersion"; - } else { - fn = singleton ? "loadSingletonVersion" : "loadVersion"; - } - } else { - fn = singleton ? "loadSingleton" : "load"; - } - - const code = runtimeTemplate.returningFunction(`${fn}(${args.join(", ")})`); - const sources = new Map(); - sources.set("consume-shared", new RawSource(code)); - return { - runtimeRequirements, - sources - }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this.options); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - */ - deserialize(context) { - const { read } = context; - this.options = read(); - super.deserialize(context); - } -} - -makeSerializable( - ConsumeSharedModule, - "webpack/lib/sharing/ConsumeSharedModule" -); - -module.exports = ConsumeSharedModule; diff --git a/webpack-lib/lib/sharing/ConsumeSharedPlugin.js b/webpack-lib/lib/sharing/ConsumeSharedPlugin.js deleted file mode 100644 index e6fab76f1d9..00000000000 --- a/webpack-lib/lib/sharing/ConsumeSharedPlugin.js +++ /dev/null @@ -1,379 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleNotFoundError = require("../ModuleNotFoundError"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const WebpackError = require("../WebpackError"); -const { parseOptions } = require("../container/options"); -const LazySet = require("../util/LazySet"); -const createSchemaValidation = require("../util/create-schema-validation"); -const { parseRange } = require("../util/semver"); -const ConsumeSharedFallbackDependency = require("./ConsumeSharedFallbackDependency"); -const ConsumeSharedModule = require("./ConsumeSharedModule"); -const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule"); -const ProvideForSharedDependency = require("./ProvideForSharedDependency"); -const { resolveMatchedConfigs } = require("./resolveMatchedConfigs"); -const { - isRequiredVersion, - getDescriptionFile, - getRequiredVersionFromDescriptionFile -} = require("./utils"); - -/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */ -/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */ -/** @typedef {import("../util/semver").SemVerRange} SemVerRange */ -/** @typedef {import("./ConsumeSharedModule").ConsumeOptions} ConsumeOptions */ -/** @typedef {import("./utils").DescriptionFile} DescriptionFile */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/sharing/ConsumeSharedPlugin.check.js"), - () => require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json"), - { - name: "Consume Shared Plugin", - baseDataPath: "options" - } -); - -/** @type {ResolveOptionsWithDependencyType} */ -const RESOLVE_OPTIONS = { dependencyType: "esm" }; -const PLUGIN_NAME = "ConsumeSharedPlugin"; - -class ConsumeSharedPlugin { - /** - * @param {ConsumeSharedPluginOptions} options options - */ - constructor(options) { - if (typeof options !== "string") { - validate(options); - } - - /** @type {[string, ConsumeOptions][]} */ - this._consumes = parseOptions( - options.consumes, - (item, key) => { - if (Array.isArray(item)) throw new Error("Unexpected array in options"); - /** @type {ConsumeOptions} */ - const result = - item === key || !isRequiredVersion(item) - ? // item is a request/key - { - import: key, - shareScope: options.shareScope || "default", - shareKey: key, - requiredVersion: undefined, - packageName: undefined, - strictVersion: false, - singleton: false, - eager: false - } - : // key is a request/key - // item is a version - { - import: key, - shareScope: options.shareScope || "default", - shareKey: key, - requiredVersion: parseRange(item), - strictVersion: true, - packageName: undefined, - singleton: false, - eager: false - }; - return result; - }, - (item, key) => ({ - import: item.import === false ? undefined : item.import || key, - shareScope: item.shareScope || options.shareScope || "default", - shareKey: item.shareKey || key, - requiredVersion: - typeof item.requiredVersion === "string" - ? parseRange(item.requiredVersion) - : item.requiredVersion, - strictVersion: - typeof item.strictVersion === "boolean" - ? item.strictVersion - : item.import !== false && !item.singleton, - packageName: item.packageName, - singleton: Boolean(item.singleton), - eager: Boolean(item.eager) - }) - ); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - ConsumeSharedFallbackDependency, - normalModuleFactory - ); - - /** @type {Map} */ - let unresolvedConsumes; - /** @type {Map} */ - let resolvedConsumes; - /** @type {Map} */ - let prefixedConsumes; - const promise = resolveMatchedConfigs(compilation, this._consumes).then( - ({ resolved, unresolved, prefixed }) => { - resolvedConsumes = resolved; - unresolvedConsumes = unresolved; - prefixedConsumes = prefixed; - } - ); - - const resolver = compilation.resolverFactory.get( - "normal", - RESOLVE_OPTIONS - ); - - /** - * @param {string} context issuer directory - * @param {string} request request - * @param {ConsumeOptions} config options - * @returns {Promise} create module - */ - const createConsumeSharedModule = (context, request, config) => { - /** - * @param {string} details details - */ - const requiredVersionWarning = details => { - const error = new WebpackError( - `No required version specified and unable to automatically determine one. ${details}` - ); - error.file = `shared module ${request}`; - compilation.warnings.push(error); - }; - const directFallback = - config.import && - /^(\.\.?(\/|$)|\/|[A-Za-z]:|\\\\)/.test(config.import); - return Promise.all([ - new Promise( - /** - * @param {(value?: string) => void} resolve resolve - */ - resolve => { - if (!config.import) { - resolve(); - return; - } - const resolveContext = { - /** @type {LazySet} */ - fileDependencies: new LazySet(), - /** @type {LazySet} */ - contextDependencies: new LazySet(), - /** @type {LazySet} */ - missingDependencies: new LazySet() - }; - resolver.resolve( - {}, - directFallback ? compiler.context : context, - config.import, - resolveContext, - (err, result) => { - compilation.contextDependencies.addAll( - resolveContext.contextDependencies - ); - compilation.fileDependencies.addAll( - resolveContext.fileDependencies - ); - compilation.missingDependencies.addAll( - resolveContext.missingDependencies - ); - if (err) { - compilation.errors.push( - new ModuleNotFoundError(null, err, { - name: `resolving fallback for shared module ${request}` - }) - ); - return resolve(); - } - resolve(/** @type {string} */ (result)); - } - ); - } - ), - new Promise( - /** - * @param {(value?: SemVerRange) => void} resolve resolve - */ - resolve => { - if (config.requiredVersion !== undefined) { - resolve(/** @type {SemVerRange} */ (config.requiredVersion)); - return; - } - let packageName = config.packageName; - if (packageName === undefined) { - if (/^(\/|[A-Za-z]:|\\\\)/.test(request)) { - // For relative or absolute requests we don't automatically use a packageName. - // If wished one can specify one with the packageName option. - resolve(); - return; - } - const match = /^((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(request); - if (!match) { - requiredVersionWarning( - "Unable to extract the package name from request." - ); - resolve(); - return; - } - packageName = match[0]; - } - - getDescriptionFile( - compilation.inputFileSystem, - context, - ["package.json"], - (err, result, checkedDescriptionFilePaths) => { - if (err) { - requiredVersionWarning( - `Unable to read description file: ${err}` - ); - return resolve(); - } - const { data } = - /** @type {DescriptionFile} */ - (result || {}); - if (!data) { - if (checkedDescriptionFilePaths) { - requiredVersionWarning( - [ - `Unable to find required version for "${packageName}" in description file/s`, - checkedDescriptionFilePaths.join("\n"), - "It need to be in dependencies, devDependencies or peerDependencies." - ].join("\n") - ); - } else { - requiredVersionWarning( - `Unable to find description file in ${context}.` - ); - } - - return resolve(); - } - if (data.name === packageName) { - // Package self-referencing - return resolve(); - } - const requiredVersion = - getRequiredVersionFromDescriptionFile(data, packageName); - - if (requiredVersion) { - return resolve(parseRange(requiredVersion)); - } - - resolve(); - }, - result => { - if (!result) return false; - const maybeRequiredVersion = - getRequiredVersionFromDescriptionFile( - result.data, - packageName - ); - return ( - result.data.name === packageName || - typeof maybeRequiredVersion === "string" - ); - } - ); - } - ) - ]).then( - ([importResolved, requiredVersion]) => - new ConsumeSharedModule( - directFallback ? compiler.context : context, - { - ...config, - importResolved, - import: importResolved ? config.import : undefined, - requiredVersion - } - ) - ); - }; - - normalModuleFactory.hooks.factorize.tapPromise( - PLUGIN_NAME, - ({ context, request, dependencies }) => - // wait for resolving to be complete - promise.then(() => { - if ( - dependencies[0] instanceof ConsumeSharedFallbackDependency || - dependencies[0] instanceof ProvideForSharedDependency - ) { - return; - } - const match = unresolvedConsumes.get(request); - if (match !== undefined) { - return createConsumeSharedModule(context, request, match); - } - for (const [prefix, options] of prefixedConsumes) { - if (request.startsWith(prefix)) { - const remainder = request.slice(prefix.length); - return createConsumeSharedModule(context, request, { - ...options, - import: options.import - ? options.import + remainder - : undefined, - shareKey: options.shareKey + remainder - }); - } - } - }) - ); - normalModuleFactory.hooks.createModule.tapPromise( - PLUGIN_NAME, - ({ resource }, { context, dependencies }) => { - if ( - dependencies[0] instanceof ConsumeSharedFallbackDependency || - dependencies[0] instanceof ProvideForSharedDependency - ) { - return Promise.resolve(); - } - const options = resolvedConsumes.get( - /** @type {string} */ (resource) - ); - if (options !== undefined) { - return createConsumeSharedModule( - context, - /** @type {string} */ (resource), - options - ); - } - return Promise.resolve(); - } - ); - compilation.hooks.additionalTreeRuntimeRequirements.tap( - PLUGIN_NAME, - (chunk, set) => { - set.add(RuntimeGlobals.module); - set.add(RuntimeGlobals.moduleCache); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.shareScopeMap); - set.add(RuntimeGlobals.initializeSharing); - set.add(RuntimeGlobals.hasOwnProperty); - compilation.addRuntimeModule( - chunk, - new ConsumeSharedRuntimeModule(set) - ); - } - ); - } - ); - } -} - -module.exports = ConsumeSharedPlugin; diff --git a/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js b/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js deleted file mode 100644 index 095d24c099b..00000000000 --- a/webpack-lib/lib/sharing/ConsumeSharedRuntimeModule.js +++ /dev/null @@ -1,355 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - parseVersionRuntimeCode, - versionLtRuntimeCode, - rangeToStringRuntimeCode, - satisfyRuntimeCode -} = require("../util/semver"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("./ConsumeSharedModule")} ConsumeSharedModule */ - -class ConsumeSharedRuntimeModule extends RuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("consumes", RuntimeModule.STAGE_ATTACH); - this._runtimeRequirements = runtimeRequirements; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const { runtimeTemplate, codeGenerationResults } = compilation; - /** @type {Record} */ - const chunkToModuleMapping = {}; - /** @type {Map} */ - const moduleIdToSourceMapping = new Map(); - /** @type {(string | number)[]} */ - const initialConsumes = []; - /** - * @param {Iterable} modules modules - * @param {Chunk} chunk the chunk - * @param {(string | number)[]} list list of ids - */ - const addModules = (modules, chunk, list) => { - for (const m of modules) { - const module = m; - const id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - list.push(id); - moduleIdToSourceMapping.set( - id, - codeGenerationResults.getSource( - module, - chunk.runtime, - "consume-shared" - ) - ); - } - }; - for (const chunk of /** @type {Chunk} */ ( - this.chunk - ).getAllReferencedChunks()) { - const modules = chunkGraph.getChunkModulesIterableBySourceType( - chunk, - "consume-shared" - ); - if (!modules) continue; - addModules( - modules, - chunk, - (chunkToModuleMapping[/** @type {ChunkId} */ (chunk.id)] = []) - ); - } - for (const chunk of /** @type {Chunk} */ ( - this.chunk - ).getAllInitialChunks()) { - const modules = chunkGraph.getChunkModulesIterableBySourceType( - chunk, - "consume-shared" - ); - if (!modules) continue; - addModules(modules, chunk, initialConsumes); - } - if (moduleIdToSourceMapping.size === 0) return null; - return Template.asString([ - parseVersionRuntimeCode(runtimeTemplate), - versionLtRuntimeCode(runtimeTemplate), - rangeToStringRuntimeCode(runtimeTemplate), - satisfyRuntimeCode(runtimeTemplate), - `var exists = ${runtimeTemplate.basicFunction("scope, key", [ - `return scope && ${RuntimeGlobals.hasOwnProperty}(scope, key);` - ])}`, - `var get = ${runtimeTemplate.basicFunction("entry", [ - "entry.loaded = 1;", - "return entry.get()" - ])};`, - `var eagerOnly = ${runtimeTemplate.basicFunction("versions", [ - `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction( - "filtered, version", - Template.indent([ - "if (versions[version].eager) {", - Template.indent(["filtered[version] = versions[version];"]), - "}", - "return filtered;" - ]) - )}, {});` - ])};`, - `var findLatestVersion = ${runtimeTemplate.basicFunction( - "scope, key, eager", - [ - "var versions = eager ? eagerOnly(scope[key]) : scope[key];", - `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction( - "a, b", - ["return !a || versionLt(a, b) ? b : a;"] - )}, 0);`, - "return key && versions[key];" - ] - )};`, - `var findSatisfyingVersion = ${runtimeTemplate.basicFunction( - "scope, key, requiredVersion, eager", - [ - "var versions = eager ? eagerOnly(scope[key]) : scope[key];", - `var key = Object.keys(versions).reduce(${runtimeTemplate.basicFunction( - "a, b", - [ - "if (!satisfy(requiredVersion, b)) return a;", - "return !a || versionLt(a, b) ? b : a;" - ] - )}, 0);`, - "return key && versions[key]" - ] - )};`, - `var findSingletonVersionKey = ${runtimeTemplate.basicFunction( - "scope, key, eager", - [ - "var versions = eager ? eagerOnly(scope[key]) : scope[key];", - `return Object.keys(versions).reduce(${runtimeTemplate.basicFunction( - "a, b", - ["return !a || (!versions[a].loaded && versionLt(a, b)) ? b : a;"] - )}, 0);` - ] - )};`, - `var getInvalidSingletonVersionMessage = ${runtimeTemplate.basicFunction( - "scope, key, version, requiredVersion", - [ - 'return "Unsatisfied version " + version + " from " + (version && scope[key][version].from) + " of shared singleton module " + key + " (required " + rangeToString(requiredVersion) + ")"' - ] - )};`, - `var getInvalidVersionMessage = ${runtimeTemplate.basicFunction( - "scope, scopeName, key, requiredVersion, eager", - [ - "var versions = scope[key];", - 'return "No satisfying version (" + rangeToString(requiredVersion) + ")" + (eager ? " for eager consumption" : "") + " of shared module " + key + " found in shared scope " + scopeName + ".\\n" +', - `\t"Available versions: " + Object.keys(versions).map(${runtimeTemplate.basicFunction( - "key", - ['return key + " from " + versions[key].from;'] - )}).join(", ");` - ] - )};`, - `var fail = ${runtimeTemplate.basicFunction("msg", [ - "throw new Error(msg);" - ])}`, - `var failAsNotExist = ${runtimeTemplate.basicFunction("scopeName, key", [ - 'return fail("Shared module " + key + " doesn\'t exist in shared scope " + scopeName);' - ])}`, - `var warn = /*#__PURE__*/ ${ - compilation.outputOptions.ignoreBrowserWarnings - ? runtimeTemplate.basicFunction("", "") - : runtimeTemplate.basicFunction("msg", [ - 'if (typeof console !== "undefined" && console.warn) console.warn(msg);' - ]) - };`, - `var init = ${runtimeTemplate.returningFunction( - Template.asString([ - "function(scopeName, key, eager, c, d) {", - Template.indent([ - `var promise = ${RuntimeGlobals.initializeSharing}(scopeName);`, - // if we require eager shared, we expect it to be already loaded before it requested, no need to wait the whole scope loaded. - "if (promise && promise.then && !eager) { ", - Template.indent([ - `return promise.then(fn.bind(fn, scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, false, c, d));` - ]), - "}", - `return fn(scopeName, ${RuntimeGlobals.shareScopeMap}[scopeName], key, eager, c, d);` - ]), - "}" - ]), - "fn" - )};`, - "", - `var useFallback = ${runtimeTemplate.basicFunction( - "scopeName, key, fallback", - ["return fallback ? fallback() : failAsNotExist(scopeName, key);"] - )}`, - `var load = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "return get(findLatestVersion(scope, key, eager));" - ] - )});`, - `var loadVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, requiredVersion, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);", - "if (satisfyingVersion) return get(satisfyingVersion);", - "warn(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager))", - "return get(findLatestVersion(scope, key, eager));" - ] - )});`, - `var loadStrictVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, requiredVersion, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);", - "if (satisfyingVersion) return get(satisfyingVersion);", - "if (fallback) return fallback();", - "fail(getInvalidVersionMessage(scope, scopeName, key, requiredVersion, eager));" - ] - )});`, - `var loadSingleton = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "var version = findSingletonVersionKey(scope, key, eager);", - "return get(scope[key][version]);" - ] - )});`, - `var loadSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, requiredVersion, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "var version = findSingletonVersionKey(scope, key, eager);", - "if (!satisfy(requiredVersion, version)) {", - Template.indent([ - "warn(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));" - ]), - "}", - "return get(scope[key][version]);" - ] - )});`, - `var loadStrictSingletonVersion = /*#__PURE__*/ init(${runtimeTemplate.basicFunction( - "scopeName, scope, key, eager, requiredVersion, fallback", - [ - "if (!exists(scope, key)) return useFallback(scopeName, key, fallback);", - "var version = findSingletonVersionKey(scope, key, eager);", - "if (!satisfy(requiredVersion, version)) {", - Template.indent([ - "fail(getInvalidSingletonVersionMessage(scope, key, version, requiredVersion));" - ]), - "}", - "return get(scope[key][version]);" - ] - )});`, - "var installedModules = {};", - "var moduleToHandlerMapping = {", - Template.indent( - Array.from( - moduleIdToSourceMapping, - ([key, source]) => `${JSON.stringify(key)}: ${source.source()}` - ).join(",\n") - ), - "};", - - initialConsumes.length > 0 - ? Template.asString([ - `var initialConsumes = ${JSON.stringify(initialConsumes)};`, - `initialConsumes.forEach(${runtimeTemplate.basicFunction("id", [ - `${ - RuntimeGlobals.moduleFactories - }[id] = ${runtimeTemplate.basicFunction("module", [ - "// Handle case when module is used sync", - "installedModules[id] = 0;", - `delete ${RuntimeGlobals.moduleCache}[id];`, - "var factory = moduleToHandlerMapping[id]();", - 'if(typeof factory !== "function") throw new Error("Shared module is not available for eager consumption: " + id);', - "module.exports = factory();" - ])}` - ])});` - ]) - : "// no consumes in initial chunks", - this._runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) - ? Template.asString([ - `var chunkMapping = ${JSON.stringify( - chunkToModuleMapping, - null, - "\t" - )};`, - "var startedInstallModules = {};", - `${ - RuntimeGlobals.ensureChunkHandlers - }.consumes = ${runtimeTemplate.basicFunction("chunkId, promises", [ - `if(${RuntimeGlobals.hasOwnProperty}(chunkMapping, chunkId)) {`, - Template.indent([ - `chunkMapping[chunkId].forEach(${runtimeTemplate.basicFunction( - "id", - [ - `if(${RuntimeGlobals.hasOwnProperty}(installedModules, id)) return promises.push(installedModules[id]);`, - "if(!startedInstallModules[id]) {", - `var onFactory = ${runtimeTemplate.basicFunction( - "factory", - [ - "installedModules[id] = 0;", - `${ - RuntimeGlobals.moduleFactories - }[id] = ${runtimeTemplate.basicFunction("module", [ - `delete ${RuntimeGlobals.moduleCache}[id];`, - "module.exports = factory();" - ])}` - ] - )};`, - "startedInstallModules[id] = true;", - `var onError = ${runtimeTemplate.basicFunction("error", [ - "delete installedModules[id];", - `${ - RuntimeGlobals.moduleFactories - }[id] = ${runtimeTemplate.basicFunction("module", [ - `delete ${RuntimeGlobals.moduleCache}[id];`, - "throw error;" - ])}` - ])};`, - "try {", - Template.indent([ - "var promise = moduleToHandlerMapping[id]();", - "if(promise.then) {", - Template.indent( - "promises.push(installedModules[id] = promise.then(onFactory)['catch'](onError));" - ), - "} else onFactory(promise);" - ]), - "} catch(e) { onError(e); }", - "}" - ] - )});` - ]), - "}" - ])}` - ]) - : "// no chunk loading of consumes" - ]); - } -} - -module.exports = ConsumeSharedRuntimeModule; diff --git a/webpack-lib/lib/sharing/ProvideForSharedDependency.js b/webpack-lib/lib/sharing/ProvideForSharedDependency.js deleted file mode 100644 index 4de679a6a74..00000000000 --- a/webpack-lib/lib/sharing/ProvideForSharedDependency.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleDependency = require("../dependencies/ModuleDependency"); -const makeSerializable = require("../util/makeSerializable"); - -class ProvideForSharedDependency extends ModuleDependency { - /** - * @param {string} request request string - */ - constructor(request) { - super(request); - } - - get type() { - return "provide module for shared"; - } - - get category() { - return "esm"; - } -} - -makeSerializable( - ProvideForSharedDependency, - "webpack/lib/sharing/ProvideForSharedDependency" -); - -module.exports = ProvideForSharedDependency; diff --git a/webpack-lib/lib/sharing/ProvideSharedDependency.js b/webpack-lib/lib/sharing/ProvideSharedDependency.js deleted file mode 100644 index 2df18a618ed..00000000000 --- a/webpack-lib/lib/sharing/ProvideSharedDependency.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Dependency = require("../Dependency"); -const makeSerializable = require("../util/makeSerializable"); - -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -class ProvideSharedDependency extends Dependency { - /** - * @param {string} shareScope share scope - * @param {string} name module name - * @param {string | false} version version - * @param {string} request request - * @param {boolean} eager true, if this is an eager dependency - */ - constructor(shareScope, name, version, request, eager) { - super(); - this.shareScope = shareScope; - this.name = name; - this.version = version; - this.request = request; - this.eager = eager; - } - - get type() { - return "provide shared module"; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return `provide module (${this.shareScope}) ${this.request} as ${ - this.name - } @ ${this.version}${this.eager ? " (eager)" : ""}`; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - context.write(this.shareScope); - context.write(this.name); - context.write(this.request); - context.write(this.version); - context.write(this.eager); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ProvideSharedDependency} deserialize fallback dependency - */ - static deserialize(context) { - const { read } = context; - const obj = new ProvideSharedDependency( - read(), - read(), - read(), - read(), - read() - ); - this.shareScope = context.read(); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - ProvideSharedDependency, - "webpack/lib/sharing/ProvideSharedDependency" -); - -module.exports = ProvideSharedDependency; diff --git a/webpack-lib/lib/sharing/ProvideSharedModule.js b/webpack-lib/lib/sharing/ProvideSharedModule.js deleted file mode 100644 index a0c1d0fd838..00000000000 --- a/webpack-lib/lib/sharing/ProvideSharedModule.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const AsyncDependenciesBlock = require("../AsyncDependenciesBlock"); -const Module = require("../Module"); -const { SHARED_INIT_TYPES } = require("../ModuleSourceTypesConstants"); -const { WEBPACK_MODULE_TYPE_PROVIDE } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const makeSerializable = require("../util/makeSerializable"); -const ProvideForSharedDependency = require("./ProvideForSharedDependency"); - -/** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */ -/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */ -/** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */ -/** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */ -/** @typedef {import("../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 */ - -class ProvideSharedModule extends Module { - /** - * @param {string} shareScope shared scope name - * @param {string} name shared key - * @param {string | false} version version - * @param {string} request request to the provided module - * @param {boolean} eager include the module in sync way - */ - constructor(shareScope, name, version, request, eager) { - super(WEBPACK_MODULE_TYPE_PROVIDE); - this._shareScope = shareScope; - this._name = name; - this._version = version; - this._request = request; - this._eager = eager; - } - - /** - * @returns {string} a unique identifier of the module - */ - identifier() { - return `provide module (${this._shareScope}) ${this._name}@${this._version} = ${this._request}`; - } - - /** - * @param {RequestShortener} requestShortener the request shortener - * @returns {string} a user readable identifier of the module - */ - readableIdentifier(requestShortener) { - return `provide shared module (${this._shareScope}) ${this._name}@${ - this._version - } = ${requestShortener.shorten(this._request)}`; - } - - /** - * @param {LibIdentOptions} options options - * @returns {string | null} an identifier for library inclusion - */ - libIdent(options) { - return `${this.layer ? `(${this.layer})/` : ""}webpack/sharing/provide/${ - this._shareScope - }/${this._name}`; - } - - /** - * @param {NeedBuildContext} context context info - * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild - * @returns {void} - */ - needBuild(context, callback) { - callback(null, !this.buildInfo); - } - - /** - * @param {WebpackOptions} options webpack options - * @param {Compilation} compilation the compilation - * @param {ResolverWithOptions} resolver the resolver - * @param {InputFileSystem} fs the file system - * @param {function(WebpackError=): void} callback callback function - * @returns {void} - */ - build(options, compilation, resolver, fs, callback) { - this.buildMeta = {}; - this.buildInfo = { - strict: true - }; - - this.clearDependenciesAndBlocks(); - const dep = new ProvideForSharedDependency(this._request); - if (this._eager) { - this.addDependency(dep); - } else { - const block = new AsyncDependenciesBlock({}); - block.addDependency(dep); - this.addBlock(block); - } - - callback(); - } - - /** - * @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; - } - - /** - * @returns {SourceTypes} types available (do not mutate) - */ - getSourceTypes() { - return SHARED_INIT_TYPES; - } - - /** - * @param {CodeGenerationContext} context context for code generation - * @returns {CodeGenerationResult} result - */ - codeGeneration({ runtimeTemplate, moduleGraph, chunkGraph }) { - const runtimeRequirements = new Set([RuntimeGlobals.initializeSharing]); - const code = `register(${JSON.stringify(this._name)}, ${JSON.stringify( - this._version || "0" - )}, ${ - this._eager - ? runtimeTemplate.syncModuleFactory({ - dependency: this.dependencies[0], - chunkGraph, - request: this._request, - runtimeRequirements - }) - : runtimeTemplate.asyncModuleFactory({ - block: this.blocks[0], - chunkGraph, - request: this._request, - runtimeRequirements - }) - }${this._eager ? ", 1" : ""});`; - const sources = new Map(); - const data = new Map(); - data.set("share-init", [ - { - shareScope: this._shareScope, - initStage: 10, - init: code - } - ]); - return { sources, data, runtimeRequirements }; - } - - /** - * @param {ObjectSerializerContext} context context - */ - serialize(context) { - const { write } = context; - write(this._shareScope); - write(this._name); - write(this._version); - write(this._request); - write(this._eager); - super.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ProvideSharedModule} deserialize fallback dependency - */ - static deserialize(context) { - const { read } = context; - const obj = new ProvideSharedModule(read(), read(), read(), read(), read()); - obj.deserialize(context); - return obj; - } -} - -makeSerializable( - ProvideSharedModule, - "webpack/lib/sharing/ProvideSharedModule" -); - -module.exports = ProvideSharedModule; diff --git a/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js b/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js deleted file mode 100644 index d5bcc829f99..00000000000 --- a/webpack-lib/lib/sharing/ProvideSharedModuleFactory.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const ModuleFactory = require("../ModuleFactory"); -const ProvideSharedModule = require("./ProvideSharedModule"); - -/** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ -/** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ -/** @typedef {import("./ProvideSharedDependency")} ProvideSharedDependency */ - -class ProvideSharedModuleFactory extends ModuleFactory { - /** - * @param {ModuleFactoryCreateData} data data object - * @param {function((Error | null)=, ModuleFactoryResult=): void} callback callback - * @returns {void} - */ - create(data, callback) { - const dep = /** @type {ProvideSharedDependency} */ (data.dependencies[0]); - callback(null, { - module: new ProvideSharedModule( - dep.shareScope, - dep.name, - dep.version, - dep.request, - dep.eager - ) - }); - } -} - -module.exports = ProvideSharedModuleFactory; diff --git a/webpack-lib/lib/sharing/ProvideSharedPlugin.js b/webpack-lib/lib/sharing/ProvideSharedPlugin.js deleted file mode 100644 index c57b76324ab..00000000000 --- a/webpack-lib/lib/sharing/ProvideSharedPlugin.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); -const { parseOptions } = require("../container/options"); -const createSchemaValidation = require("../util/create-schema-validation"); -const ProvideForSharedDependency = require("./ProvideForSharedDependency"); -const ProvideSharedDependency = require("./ProvideSharedDependency"); -const ProvideSharedModuleFactory = require("./ProvideSharedModuleFactory"); - -/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../NormalModuleFactory").NormalModuleCreateData} NormalModuleCreateData */ - -const validate = createSchemaValidation( - require("../../schemas/plugins/sharing/ProvideSharedPlugin.check.js"), - () => require("../../schemas/plugins/sharing/ProvideSharedPlugin.json"), - { - name: "Provide Shared Plugin", - baseDataPath: "options" - } -); - -/** - * @typedef {object} ProvideOptions - * @property {string} shareKey - * @property {string} shareScope - * @property {string | undefined | false} version - * @property {boolean} eager - */ - -/** @typedef {Map} ResolvedProvideMap */ - -class ProvideSharedPlugin { - /** - * @param {ProvideSharedPluginOptions} options options - */ - constructor(options) { - validate(options); - - this._provides = /** @type {[string, ProvideOptions][]} */ ( - parseOptions( - options.provides, - item => { - if (Array.isArray(item)) - throw new Error("Unexpected array of provides"); - /** @type {ProvideOptions} */ - const result = { - shareKey: item, - version: undefined, - shareScope: options.shareScope || "default", - eager: false - }; - return result; - }, - item => ({ - shareKey: item.shareKey, - version: item.version, - shareScope: item.shareScope || options.shareScope || "default", - eager: Boolean(item.eager) - }) - ) - ); - this._provides.sort(([a], [b]) => { - if (a < b) return -1; - if (b < a) return 1; - return 0; - }); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - /** @type {WeakMap} */ - const compilationData = new WeakMap(); - - compiler.hooks.compilation.tap( - "ProvideSharedPlugin", - (compilation, { normalModuleFactory }) => { - /** @type {ResolvedProvideMap} */ - const resolvedProvideMap = new Map(); - /** @type {Map} */ - const matchProvides = new Map(); - /** @type {Map} */ - const prefixMatchProvides = new Map(); - for (const [request, config] of this._provides) { - if (/^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))/.test(request)) { - // relative request - resolvedProvideMap.set(request, { - config, - version: config.version - }); - } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) { - // absolute path - resolvedProvideMap.set(request, { - config, - version: config.version - }); - } else if (request.endsWith("/")) { - // module request prefix - prefixMatchProvides.set(request, config); - } else { - // module request - matchProvides.set(request, config); - } - } - compilationData.set(compilation, resolvedProvideMap); - /** - * @param {string} key key - * @param {ProvideOptions} config config - * @param {NormalModuleCreateData["resource"]} resource resource - * @param {NormalModuleCreateData["resourceResolveData"]} resourceResolveData resource resolve data - */ - const provideSharedModule = ( - key, - config, - resource, - resourceResolveData - ) => { - let version = config.version; - if (version === undefined) { - let details = ""; - if (!resourceResolveData) { - details = "No resolve data provided from resolver."; - } else { - const descriptionFileData = - resourceResolveData.descriptionFileData; - if (!descriptionFileData) { - details = - "No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config."; - } else if (!descriptionFileData.version) { - details = `No version in description file (usually package.json). Add version to description file ${resourceResolveData.descriptionFilePath}, or manually specify version in shared config.`; - } else { - version = descriptionFileData.version; - } - } - if (!version) { - const error = new WebpackError( - `No version specified and unable to automatically determine one. ${details}` - ); - error.file = `shared module ${key} -> ${resource}`; - compilation.warnings.push(error); - } - } - resolvedProvideMap.set(resource, { - config, - version - }); - }; - normalModuleFactory.hooks.module.tap( - "ProvideSharedPlugin", - (module, { resource, resourceResolveData }, resolveData) => { - if (resolvedProvideMap.has(/** @type {string} */ (resource))) { - return module; - } - const { request } = resolveData; - { - const config = matchProvides.get(request); - if (config !== undefined) { - provideSharedModule( - request, - config, - /** @type {string} */ (resource), - resourceResolveData - ); - resolveData.cacheable = false; - } - } - for (const [prefix, config] of prefixMatchProvides) { - if (request.startsWith(prefix)) { - const remainder = request.slice(prefix.length); - provideSharedModule( - /** @type {string} */ (resource), - { - ...config, - shareKey: config.shareKey + remainder - }, - /** @type {string} */ (resource), - resourceResolveData - ); - resolveData.cacheable = false; - } - } - return module; - } - ); - } - ); - compiler.hooks.finishMake.tapPromise("ProvideSharedPlugin", compilation => { - const resolvedProvideMap = compilationData.get(compilation); - if (!resolvedProvideMap) return Promise.resolve(); - return Promise.all( - Array.from( - resolvedProvideMap, - ([resource, { config, version }]) => - new Promise((resolve, reject) => { - compilation.addInclude( - compiler.context, - new ProvideSharedDependency( - config.shareScope, - config.shareKey, - version || false, - resource, - config.eager - ), - { - name: undefined - }, - err => { - if (err) return reject(err); - resolve(null); - } - ); - }) - ) - ).then(() => {}); - }); - - compiler.hooks.compilation.tap( - "ProvideSharedPlugin", - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - ProvideForSharedDependency, - normalModuleFactory - ); - - compilation.dependencyFactories.set( - ProvideSharedDependency, - new ProvideSharedModuleFactory() - ); - } - ); - } -} - -module.exports = ProvideSharedPlugin; diff --git a/webpack-lib/lib/sharing/SharePlugin.js b/webpack-lib/lib/sharing/SharePlugin.js deleted file mode 100644 index 65935b30b99..00000000000 --- a/webpack-lib/lib/sharing/SharePlugin.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra and Zackary Jackson @ScriptedAlchemy -*/ - -"use strict"; - -const { parseOptions } = require("../container/options"); -const ConsumeSharedPlugin = require("./ConsumeSharedPlugin"); -const ProvideSharedPlugin = require("./ProvideSharedPlugin"); -const { isRequiredVersion } = require("./utils"); - -/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */ -/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumesConfig} ConsumesConfig */ -/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */ -/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvidesConfig} ProvidesConfig */ -/** @typedef {import("../../declarations/plugins/sharing/SharePlugin").SharePluginOptions} SharePluginOptions */ -/** @typedef {import("../../declarations/plugins/sharing/SharePlugin").SharedConfig} SharedConfig */ -/** @typedef {import("../Compiler")} Compiler */ - -class SharePlugin { - /** - * @param {SharePluginOptions} options options - */ - constructor(options) { - /** @type {[string, SharedConfig][]} */ - const sharedOptions = parseOptions( - options.shared, - (item, key) => { - if (typeof item !== "string") - throw new Error("Unexpected array in shared"); - /** @type {SharedConfig} */ - const config = - item === key || !isRequiredVersion(item) - ? { - import: item - } - : { - import: key, - requiredVersion: item - }; - return config; - }, - item => item - ); - /** @type {Record[]} */ - const consumes = sharedOptions.map(([key, options]) => ({ - [key]: { - import: options.import, - shareKey: options.shareKey || key, - shareScope: options.shareScope, - requiredVersion: options.requiredVersion, - strictVersion: options.strictVersion, - singleton: options.singleton, - packageName: options.packageName, - eager: options.eager - } - })); - /** @type {Record[]} */ - const provides = sharedOptions - .filter(([, options]) => options.import !== false) - .map(([key, options]) => ({ - [options.import || key]: { - shareKey: options.shareKey || key, - shareScope: options.shareScope, - version: options.version, - eager: options.eager - } - })); - this._shareScope = options.shareScope; - this._consumes = consumes; - this._provides = provides; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - new ConsumeSharedPlugin({ - shareScope: this._shareScope, - consumes: this._consumes - }).apply(compiler); - new ProvideSharedPlugin({ - shareScope: this._shareScope, - provides: this._provides - }).apply(compiler); - } -} - -module.exports = SharePlugin; diff --git a/webpack-lib/lib/sharing/ShareRuntimeModule.js b/webpack-lib/lib/sharing/ShareRuntimeModule.js deleted file mode 100644 index 0f63ef68d7d..00000000000 --- a/webpack-lib/lib/sharing/ShareRuntimeModule.js +++ /dev/null @@ -1,151 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - compareModulesByIdentifier, - compareStrings -} = require("../util/comparators"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ - -class ShareRuntimeModule extends RuntimeModule { - constructor() { - super("sharing"); - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { - runtimeTemplate, - codeGenerationResults, - outputOptions: { uniqueName, ignoreBrowserWarnings } - } = compilation; - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - /** @type {Map>>} */ - const initCodePerScope = new Map(); - for (const chunk of /** @type {Chunk} */ ( - this.chunk - ).getAllReferencedChunks()) { - const modules = chunkGraph.getOrderedChunkModulesIterableBySourceType( - chunk, - "share-init", - compareModulesByIdentifier - ); - if (!modules) continue; - for (const m of modules) { - const data = codeGenerationResults.getData( - m, - chunk.runtime, - "share-init" - ); - if (!data) continue; - for (const item of data) { - const { shareScope, initStage, init } = item; - let stages = initCodePerScope.get(shareScope); - if (stages === undefined) { - initCodePerScope.set(shareScope, (stages = new Map())); - } - let list = stages.get(initStage || 0); - if (list === undefined) { - stages.set(initStage || 0, (list = new Set())); - } - list.add(init); - } - } - } - return Template.asString([ - `${RuntimeGlobals.shareScopeMap} = {};`, - "var initPromises = {};", - "var initTokens = {};", - `${RuntimeGlobals.initializeSharing} = ${runtimeTemplate.basicFunction( - "name, initScope", - [ - "if(!initScope) initScope = [];", - "// handling circular init calls", - "var 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(!${RuntimeGlobals.hasOwnProperty}(${RuntimeGlobals.shareScopeMap}, name)) ${RuntimeGlobals.shareScopeMap}[name] = {};`, - "// runs all init snippets from all modules reachable", - `var scope = ${RuntimeGlobals.shareScopeMap}[name];`, - `var warn = ${ - ignoreBrowserWarnings - ? runtimeTemplate.basicFunction("", "") - : runtimeTemplate.basicFunction("msg", [ - 'if (typeof console !== "undefined" && console.warn) console.warn(msg);' - ]) - };`, - `var uniqueName = ${JSON.stringify(uniqueName || undefined)};`, - `var register = ${runtimeTemplate.basicFunction( - "name, version, factory, eager", - [ - "var versions = scope[name] = scope[name] || {};", - "var activeVersion = versions[version];", - "if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager };" - ] - )};`, - `var initExternal = ${runtimeTemplate.basicFunction("id", [ - `var handleError = ${runtimeTemplate.expressionFunction( - 'warn("Initialization of sharing external failed: " + err)', - "err" - )};`, - "try {", - Template.indent([ - `var module = ${RuntimeGlobals.require}(id);`, - "if(!module) return;", - `var initFn = ${runtimeTemplate.returningFunction( - `module && module.init && module.init(${RuntimeGlobals.shareScopeMap}[name], initScope)`, - "module" - )}`, - "if(module.then) return promises.push(module.then(initFn, handleError));", - "var initResult = initFn(module);", - "if(initResult && initResult.then) return promises.push(initResult['catch'](handleError));" - ]), - "} catch(err) { handleError(err); }" - ])}`, - "var promises = [];", - "switch(name) {", - ...Array.from(initCodePerScope) - .sort(([a], [b]) => compareStrings(a, b)) - .map(([name, stages]) => - Template.indent([ - `case ${JSON.stringify(name)}: {`, - Template.indent( - Array.from(stages) - .sort(([a], [b]) => a - b) - .map(([, initCode]) => - Template.asString(Array.from(initCode)) - ) - ), - "}", - "break;" - ]) - ), - "}", - "if(!promises.length) return initPromises[name] = 1;", - `return initPromises[name] = Promise.all(promises).then(${runtimeTemplate.returningFunction( - "initPromises[name] = 1" - )});` - ] - )};` - ]); - } -} - -module.exports = ShareRuntimeModule; diff --git a/webpack-lib/lib/sharing/resolveMatchedConfigs.js b/webpack-lib/lib/sharing/resolveMatchedConfigs.js deleted file mode 100644 index a54a76abb41..00000000000 --- a/webpack-lib/lib/sharing/resolveMatchedConfigs.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ModuleNotFoundError = require("../ModuleNotFoundError"); -const LazySet = require("../util/LazySet"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */ - -/** - * @template T - * @typedef {object} MatchedConfigs - * @property {Map} resolved - * @property {Map} unresolved - * @property {Map} prefixed - */ - -/** @type {ResolveOptionsWithDependencyType} */ -const RESOLVE_OPTIONS = { dependencyType: "esm" }; - -/** - * @template T - * @param {Compilation} compilation the compilation - * @param {[string, T][]} configs to be processed configs - * @returns {Promise>} resolved matchers - */ -module.exports.resolveMatchedConfigs = (compilation, configs) => { - /** @type {Map} */ - const resolved = new Map(); - /** @type {Map} */ - const unresolved = new Map(); - /** @type {Map} */ - const prefixed = new Map(); - const resolveContext = { - /** @type {LazySet} */ - fileDependencies: new LazySet(), - /** @type {LazySet} */ - contextDependencies: new LazySet(), - /** @type {LazySet} */ - missingDependencies: new LazySet() - }; - const resolver = compilation.resolverFactory.get("normal", RESOLVE_OPTIONS); - const context = compilation.compiler.context; - - return Promise.all( - // eslint-disable-next-line array-callback-return - configs.map(([request, config]) => { - if (/^\.\.?(\/|$)/.test(request)) { - // relative request - return new Promise(resolve => { - resolver.resolve( - {}, - context, - request, - resolveContext, - (err, result) => { - if (err || result === false) { - err = err || new Error(`Can't resolve ${request}`); - compilation.errors.push( - new ModuleNotFoundError(null, err, { - name: `shared module ${request}` - }) - ); - return resolve(null); - } - resolved.set(/** @type {string} */ (result), config); - resolve(null); - } - ); - }); - } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) { - // absolute path - resolved.set(request, config); - } else if (request.endsWith("/")) { - // module request prefix - prefixed.set(request, config); - } else { - // module request - unresolved.set(request, config); - } - }) - ).then(() => { - compilation.contextDependencies.addAll(resolveContext.contextDependencies); - compilation.fileDependencies.addAll(resolveContext.fileDependencies); - compilation.missingDependencies.addAll(resolveContext.missingDependencies); - return { resolved, unresolved, prefixed }; - }); -}; diff --git a/webpack-lib/lib/sharing/utils.js b/webpack-lib/lib/sharing/utils.js deleted file mode 100644 index bdeba0045c0..00000000000 --- a/webpack-lib/lib/sharing/utils.js +++ /dev/null @@ -1,425 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { join, dirname, readJson } = require("../util/fs"); - -/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */ -/** @typedef {import("../util/fs").JsonObject} JsonObject */ -/** @typedef {import("../util/fs").JsonPrimitive} JsonPrimitive */ - -// Extreme shorthand only for github. eg: foo/bar -const RE_URL_GITHUB_EXTREME_SHORT = /^[^/@:.\s][^/@:\s]*\/[^@:\s]*[^/@:\s]#\S+/; - -// Short url with specific protocol. eg: github:foo/bar -const RE_GIT_URL_SHORT = /^(github|gitlab|bitbucket|gist):\/?[^/.]+\/?/i; - -// Currently supported protocols -const RE_PROTOCOL = - /^((git\+)?(ssh|https?|file)|git|github|gitlab|bitbucket|gist):$/i; - -// Has custom protocol -const RE_CUSTOM_PROTOCOL = /^((git\+)?(ssh|https?|file)|git):\/\//i; - -// Valid hash format for npm / yarn ... -const RE_URL_HASH_VERSION = /#(?:semver:)?(.+)/; - -// Simple hostname validate -const RE_HOSTNAME = /^(?:[^/.]+(\.[^/]+)+|localhost)$/; - -// For hostname with colon. eg: ssh://user@github.com:foo/bar -const RE_HOSTNAME_WITH_COLON = - /([^/@#:.]+(?:\.[^/@#:.]+)+|localhost):([^#/0-9]+)/; - -// Reg for url without protocol -const RE_NO_PROTOCOL = /^([^/@#:.]+(?:\.[^/@#:.]+)+)/; - -// RegExp for version string -const VERSION_PATTERN_REGEXP = /^([\d^=v<>~]|[*xX]$)/; - -// Specific protocol for short url without normal hostname -const PROTOCOLS_FOR_SHORT = [ - "github:", - "gitlab:", - "bitbucket:", - "gist:", - "file:" -]; - -// Default protocol for git url -const DEF_GIT_PROTOCOL = "git+ssh://"; - -// thanks to https://github.com/npm/hosted-git-info/blob/latest/git-host-info.js -const extractCommithashByDomain = { - /** - * @param {string} pathname pathname - * @param {string} hash hash - * @returns {string | undefined} hash - */ - "github.com": (pathname, hash) => { - let [, user, project, type, commithash] = pathname.split("/", 5); - if (type && type !== "tree") { - return; - } - - commithash = !type ? hash : `#${commithash}`; - - if (project && project.endsWith(".git")) { - project = project.slice(0, -4); - } - - if (!user || !project) { - return; - } - - return commithash; - }, - /** - * @param {string} pathname pathname - * @param {string} hash hash - * @returns {string | undefined} hash - */ - "gitlab.com": (pathname, hash) => { - const path = pathname.slice(1); - if (path.includes("/-/") || path.includes("/archive.tar.gz")) { - return; - } - - const segments = path.split("/"); - let project = /** @type {string} */ (segments.pop()); - if (project.endsWith(".git")) { - project = project.slice(0, -4); - } - - const user = segments.join("/"); - if (!user || !project) { - return; - } - - return hash; - }, - /** - * @param {string} pathname pathname - * @param {string} hash hash - * @returns {string | undefined} hash - */ - "bitbucket.org": (pathname, hash) => { - let [, user, project, aux] = pathname.split("/", 4); - if (["get"].includes(aux)) { - return; - } - - if (project && project.endsWith(".git")) { - project = project.slice(0, -4); - } - - if (!user || !project) { - return; - } - - return hash; - }, - /** - * @param {string} pathname pathname - * @param {string} hash hash - * @returns {string | undefined} hash - */ - "gist.github.com": (pathname, hash) => { - let [, user, project, aux] = pathname.split("/", 4); - if (aux === "raw") { - return; - } - - if (!project) { - if (!user) { - return; - } - - project = user; - } - - if (project.endsWith(".git")) { - project = project.slice(0, -4); - } - - return hash; - } -}; - -/** - * extract commit hash from parsed url - * @inner - * @param {URL} urlParsed parsed url - * @returns {string} commithash - */ -function getCommithash(urlParsed) { - let { hostname, pathname, hash } = urlParsed; - hostname = hostname.replace(/^www\./, ""); - - try { - hash = decodeURIComponent(hash); - // eslint-disable-next-line no-empty - } catch (_err) {} - - if ( - extractCommithashByDomain[ - /** @type {keyof extractCommithashByDomain} */ (hostname) - ] - ) { - return ( - extractCommithashByDomain[ - /** @type {keyof extractCommithashByDomain} */ (hostname) - ](pathname, hash) || "" - ); - } - - return hash; -} - -/** - * make url right for URL parse - * @inner - * @param {string} gitUrl git url - * @returns {string} fixed url - */ -function correctUrl(gitUrl) { - // like: - // proto://hostname.com:user/repo -> proto://hostname.com/user/repo - return gitUrl.replace(RE_HOSTNAME_WITH_COLON, "$1/$2"); -} - -/** - * make url protocol right for URL parse - * @inner - * @param {string} gitUrl git url - * @returns {string} fixed url - */ -function correctProtocol(gitUrl) { - // eg: github:foo/bar#v1.0. Should not add double slash, in case of error parsed `pathname` - if (RE_GIT_URL_SHORT.test(gitUrl)) { - return gitUrl; - } - - // eg: user@github.com:foo/bar - if (!RE_CUSTOM_PROTOCOL.test(gitUrl)) { - return `${DEF_GIT_PROTOCOL}${gitUrl}`; - } - - return gitUrl; -} - -/** - * extract git dep version from hash - * @inner - * @param {string} hash hash - * @returns {string} git dep version - */ -function getVersionFromHash(hash) { - const matched = hash.match(RE_URL_HASH_VERSION); - - return (matched && matched[1]) || ""; -} - -/** - * if string can be decoded - * @inner - * @param {string} str str to be checked - * @returns {boolean} if can be decoded - */ -function canBeDecoded(str) { - try { - decodeURIComponent(str); - } catch (_err) { - return false; - } - - return true; -} - -/** - * get right dep version from git url - * @inner - * @param {string} gitUrl git url - * @returns {string} dep version - */ -function getGitUrlVersion(gitUrl) { - const oriGitUrl = gitUrl; - // github extreme shorthand - gitUrl = RE_URL_GITHUB_EXTREME_SHORT.test(gitUrl) - ? `github:${gitUrl}` - : correctProtocol(gitUrl); - - gitUrl = correctUrl(gitUrl); - - let parsed; - try { - parsed = new URL(gitUrl); - // eslint-disable-next-line no-empty - } catch (_err) {} - - if (!parsed) { - return ""; - } - - const { protocol, hostname, pathname, username, password } = parsed; - if (!RE_PROTOCOL.test(protocol)) { - return ""; - } - - // pathname shouldn't be empty or URL malformed - if (!pathname || !canBeDecoded(pathname)) { - return ""; - } - - // without protocol, there should have auth info - if (RE_NO_PROTOCOL.test(oriGitUrl) && !username && !password) { - return ""; - } - - if (!PROTOCOLS_FOR_SHORT.includes(protocol.toLowerCase())) { - if (!RE_HOSTNAME.test(hostname)) { - return ""; - } - - const commithash = getCommithash(parsed); - return getVersionFromHash(commithash) || commithash; - } - - // for protocol short - return getVersionFromHash(gitUrl); -} - -/** - * @param {string} str maybe required version - * @returns {boolean} true, if it looks like a version - */ -function isRequiredVersion(str) { - return VERSION_PATTERN_REGEXP.test(str); -} - -module.exports.isRequiredVersion = isRequiredVersion; - -/** - * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#urls-as-dependencies - * @param {string} versionDesc version to be normalized - * @returns {string} normalized version - */ -function normalizeVersion(versionDesc) { - versionDesc = (versionDesc && versionDesc.trim()) || ""; - - if (isRequiredVersion(versionDesc)) { - return versionDesc; - } - - // add handle for URL Dependencies - return getGitUrlVersion(versionDesc.toLowerCase()); -} - -module.exports.normalizeVersion = normalizeVersion; - -/** @typedef {{ data: JsonObject, path: string }} DescriptionFile */ - -/** - * @param {InputFileSystem} fs file system - * @param {string} directory directory to start looking into - * @param {string[]} descriptionFiles possible description filenames - * @param {function((Error | null)=, DescriptionFile=, string[]=): void} callback callback - * @param {function(DescriptionFile=): boolean} satisfiesDescriptionFileData file data compliance check - * @param {Set} checkedFilePaths set of file paths that have been checked - */ -const getDescriptionFile = ( - fs, - directory, - descriptionFiles, - callback, - satisfiesDescriptionFileData, - checkedFilePaths = new Set() -) => { - let i = 0; - - const satisfiesDescriptionFileDataInternal = { - check: satisfiesDescriptionFileData, - checkedFilePaths - }; - - const tryLoadCurrent = () => { - if (i >= descriptionFiles.length) { - const parentDirectory = dirname(fs, directory); - if (!parentDirectory || parentDirectory === directory) { - return callback( - null, - undefined, - Array.from(satisfiesDescriptionFileDataInternal.checkedFilePaths) - ); - } - return getDescriptionFile( - fs, - parentDirectory, - descriptionFiles, - callback, - satisfiesDescriptionFileDataInternal.check, - satisfiesDescriptionFileDataInternal.checkedFilePaths - ); - } - const filePath = join(fs, directory, descriptionFiles[i]); - readJson(fs, filePath, (err, data) => { - if (err) { - if ("code" in err && err.code === "ENOENT") { - i++; - return tryLoadCurrent(); - } - return callback(err); - } - if (!data || typeof data !== "object" || Array.isArray(data)) { - return callback( - new Error(`Description file ${filePath} is not an object`) - ); - } - if ( - typeof satisfiesDescriptionFileDataInternal.check === "function" && - !satisfiesDescriptionFileDataInternal.check({ data, path: filePath }) - ) { - i++; - satisfiesDescriptionFileDataInternal.checkedFilePaths.add(filePath); - return tryLoadCurrent(); - } - callback(null, { data, path: filePath }); - }); - }; - tryLoadCurrent(); -}; -module.exports.getDescriptionFile = getDescriptionFile; - -/** - * @param {JsonObject} data description file data i.e.: package.json - * @param {string} packageName name of the dependency - * @returns {string | undefined} normalized version - */ -const getRequiredVersionFromDescriptionFile = (data, packageName) => { - const dependencyTypes = [ - "optionalDependencies", - "dependencies", - "peerDependencies", - "devDependencies" - ]; - - for (const dependencyType of dependencyTypes) { - const dependency = /** @type {JsonObject} */ (data[dependencyType]); - if ( - dependency && - typeof dependency === "object" && - packageName in dependency - ) { - return normalizeVersion( - /** @type {Exclude} */ ( - dependency[packageName] - ) - ); - } - } -}; -module.exports.getRequiredVersionFromDescriptionFile = - getRequiredVersionFromDescriptionFile; diff --git a/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js b/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js deleted file mode 100644 index 471d7135296..00000000000 --- a/webpack-lib/lib/stats/DefaultStatsFactoryPlugin.js +++ /dev/null @@ -1,2614 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const { WEBPACK_MODULE_TYPE_RUNTIME } = require("../ModuleTypeConstants"); -const ModuleDependency = require("../dependencies/ModuleDependency"); -const formatLocation = require("../formatLocation"); -const { LogType } = require("../logging/Logger"); -const AggressiveSplittingPlugin = require("../optimize/AggressiveSplittingPlugin"); -const SizeLimitsPlugin = require("../performance/SizeLimitsPlugin"); -const { countIterable } = require("../util/IterableHelpers"); -const { - compareLocations, - compareChunksById, - compareNumbers, - compareIds, - concatComparators, - compareSelect, - compareModulesByIdentifier -} = require("../util/comparators"); -const { makePathsRelative, parseResource } = require("../util/identifier"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../ChunkGroup").OriginRecord} OriginRecord */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").Asset} Asset */ -/** @typedef {import("../Compilation").AssetInfo} AssetInfo */ -/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../ModuleGraphConnection")} ModuleGraphConnection */ -/** @typedef {import("../ModuleProfile")} ModuleProfile */ -/** @typedef {import("../RequestShortener")} RequestShortener */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../TemplatedPathPlugin").TemplatePath} TemplatePath */ -/** - * @template T - * @typedef {import("../util/comparators").Comparator} Comparator - */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** - * @template T, R - * @typedef {import("../util/smartGrouping").GroupConfig} GroupConfig - */ -/** @typedef {import("./StatsFactory")} StatsFactory */ -/** @typedef {import("./StatsFactory").StatsFactoryContext} StatsFactoryContext */ -/** @typedef {Record & KnownStatsCompilation} StatsCompilation */ -/** - * @typedef {object} KnownStatsCompilation - * @property {any=} env - * @property {string=} name - * @property {string=} hash - * @property {string=} version - * @property {number=} time - * @property {number=} builtAt - * @property {boolean=} needAdditionalPass - * @property {string=} publicPath - * @property {string=} outputPath - * @property {Record=} assetsByChunkName - * @property {StatsAsset[]=} assets - * @property {number=} filteredAssets - * @property {StatsChunk[]=} chunks - * @property {StatsModule[]=} modules - * @property {number=} filteredModules - * @property {Record=} entrypoints - * @property {Record=} namedChunkGroups - * @property {StatsError[]=} errors - * @property {number=} errorsCount - * @property {StatsError[]=} warnings - * @property {number=} warningsCount - * @property {StatsCompilation[]=} children - * @property {Record=} logging - */ - -/** @typedef {Record & KnownStatsLogging} StatsLogging */ -/** - * @typedef {object} KnownStatsLogging - * @property {StatsLoggingEntry[]} entries - * @property {number} filteredEntries - * @property {boolean} debug - */ - -/** @typedef {Record & KnownStatsLoggingEntry} StatsLoggingEntry */ -/** - * @typedef {object} KnownStatsLoggingEntry - * @property {string} type - * @property {string=} message - * @property {string[]=} trace - * @property {StatsLoggingEntry[]=} children - * @property {any[]=} args - * @property {number=} time - */ - -/** @typedef {Record & KnownStatsAsset} StatsAsset */ -/** - * @typedef {object} KnownStatsAsset - * @property {string} type - * @property {string} name - * @property {AssetInfo} info - * @property {number} size - * @property {boolean} emitted - * @property {boolean} comparedForEmit - * @property {boolean} cached - * @property {StatsAsset[]=} related - * @property {(string|number)[]=} chunkNames - * @property {(string|number)[]=} chunkIdHints - * @property {(string|number)[]=} chunks - * @property {(string|number)[]=} auxiliaryChunkNames - * @property {(string|number)[]=} auxiliaryChunks - * @property {(string|number)[]=} auxiliaryChunkIdHints - * @property {number=} filteredRelated - * @property {boolean=} isOverSizeLimit - */ - -/** @typedef {Record & KnownStatsChunkGroup} StatsChunkGroup */ -/** - * @typedef {object} KnownStatsChunkGroup - * @property {(string | null)=} name - * @property {(string | number)[]=} chunks - * @property {({ name: string, size?: number })[]=} assets - * @property {number=} filteredAssets - * @property {number=} assetsSize - * @property {({ name: string, size?: number })[]=} auxiliaryAssets - * @property {number=} filteredAuxiliaryAssets - * @property {number=} auxiliaryAssetsSize - * @property {{ [x: string]: StatsChunkGroup[] }=} children - * @property {{ [x: string]: string[] }=} childAssets - * @property {boolean=} isOverSizeLimit - */ - -/** @typedef {Record & KnownStatsModule} StatsModule */ -/** - * @typedef {object} KnownStatsModule - * @property {string=} type - * @property {string=} moduleType - * @property {(string | null)=} layer - * @property {string=} identifier - * @property {string=} name - * @property {(string | null)=} nameForCondition - * @property {number=} index - * @property {number=} preOrderIndex - * @property {number=} index2 - * @property {number=} postOrderIndex - * @property {number=} size - * @property {{ [x: string]: number }=} sizes - * @property {boolean=} cacheable - * @property {boolean=} built - * @property {boolean=} codeGenerated - * @property {boolean=} buildTimeExecuted - * @property {boolean=} cached - * @property {boolean=} optional - * @property {boolean=} orphan - * @property {string | number=} id - * @property {string | number | null=} issuerId - * @property {(string | number)[]=} chunks - * @property {(string | number)[]=} assets - * @property {boolean=} dependent - * @property {(string | null)=} issuer - * @property {(string | null)=} issuerName - * @property {StatsModuleIssuer[]=} issuerPath - * @property {boolean=} failed - * @property {number=} errors - * @property {number=} warnings - * @property {StatsProfile=} profile - * @property {StatsModuleReason[]=} reasons - * @property {(boolean | null | string[])=} usedExports - * @property {(string[] | null)=} providedExports - * @property {string[]=} optimizationBailout - * @property {(number | null)=} depth - * @property {StatsModule[]=} modules - * @property {number=} filteredModules - * @property {ReturnType=} source - */ - -/** @typedef {Record & KnownStatsProfile} StatsProfile */ -/** - * @typedef {object} KnownStatsProfile - * @property {number} total - * @property {number} resolving - * @property {number} restoring - * @property {number} building - * @property {number} integration - * @property {number} storing - * @property {number} additionalResolving - * @property {number} additionalIntegration - * @property {number} factory - * @property {number} dependencies - */ - -/** @typedef {Record & KnownStatsModuleIssuer} StatsModuleIssuer */ -/** - * @typedef {object} KnownStatsModuleIssuer - * @property {string} identifier - * @property {string} name - * @property {(string|number)=} id - * @property {StatsProfile} profile - */ - -/** @typedef {Record & KnownStatsModuleReason} StatsModuleReason */ -/** - * @typedef {object} KnownStatsModuleReason - * @property {string | null} moduleIdentifier - * @property {string | null} module - * @property {string | null} moduleName - * @property {string | null} resolvedModuleIdentifier - * @property {string | null} resolvedModule - * @property {string | null} type - * @property {boolean} active - * @property {string | null} explanation - * @property {string | null} userRequest - * @property {(string | null)=} loc - * @property {(string | number | null)=} moduleId - * @property {(string | number | null)=} resolvedModuleId - */ - -/** @typedef {Record & KnownStatsChunk} StatsChunk */ -/** - * @typedef {object} KnownStatsChunk - * @property {boolean} rendered - * @property {boolean} initial - * @property {boolean} entry - * @property {boolean} recorded - * @property {string=} reason - * @property {number} size - * @property {Record} sizes - * @property {string[]} names - * @property {string[]} idHints - * @property {string[]=} runtime - * @property {string[]} files - * @property {string[]} auxiliaryFiles - * @property {string} hash - * @property {Record} childrenByOrder - * @property {(string|number)=} id - * @property {(string|number)[]=} siblings - * @property {(string|number)[]=} parents - * @property {(string|number)[]=} children - * @property {StatsModule[]=} modules - * @property {number=} filteredModules - * @property {StatsChunkOrigin[]=} origins - */ - -/** @typedef {Record & KnownStatsChunkOrigin} StatsChunkOrigin */ -/** - * @typedef {object} KnownStatsChunkOrigin - * @property {string} module - * @property {string} moduleIdentifier - * @property {string} moduleName - * @property {string} loc - * @property {string} request - * @property {(string | number)=} moduleId - */ - -/** @typedef { Record & KnownStatsModuleTraceItem} StatsModuleTraceItem */ -/** - * @typedef {object} KnownStatsModuleTraceItem - * @property {string=} originIdentifier - * @property {string=} originName - * @property {string=} moduleIdentifier - * @property {string=} moduleName - * @property {StatsModuleTraceDependency[]=} dependencies - * @property {(string|number)=} originId - * @property {(string|number)=} moduleId - */ - -/** @typedef {Record & KnownStatsModuleTraceDependency} StatsModuleTraceDependency */ -/** - * @typedef {object} KnownStatsModuleTraceDependency - * @property {string=} loc - */ - -/** @typedef {Record & KnownStatsError} StatsError */ -/** - * @typedef {object} KnownStatsError - * @property {string} message - * @property {string=} chunkName - * @property {boolean=} chunkEntry - * @property {boolean=} chunkInitial - * @property {string=} file - * @property {string=} moduleIdentifier - * @property {string=} moduleName - * @property {string=} loc - * @property {ChunkId=} chunkId - * @property {string|number=} moduleId - * @property {StatsModuleTraceItem[]=} moduleTrace - * @property {any=} details - * @property {string=} stack - */ - -/** @typedef {Asset & { type: string, related: PreprocessedAsset[] | undefined }} PreprocessedAsset */ - -/** - * @template T - * @template O - * @typedef {Record void>} ExtractorsByOption - */ - -/** - * @typedef {object} SimpleExtractors - * @property {ExtractorsByOption} compilation - * @property {ExtractorsByOption} asset - * @property {ExtractorsByOption} asset$visible - * @property {ExtractorsByOption<{ name: string, chunkGroup: ChunkGroup }, StatsChunkGroup>} chunkGroup - * @property {ExtractorsByOption} module - * @property {ExtractorsByOption} module$visible - * @property {ExtractorsByOption} moduleIssuer - * @property {ExtractorsByOption} profile - * @property {ExtractorsByOption} moduleReason - * @property {ExtractorsByOption} chunk - * @property {ExtractorsByOption} chunkOrigin - * @property {ExtractorsByOption} error - * @property {ExtractorsByOption} warning - * @property {ExtractorsByOption<{ origin: Module, module: Module }, StatsModuleTraceItem>} moduleTraceItem - * @property {ExtractorsByOption} moduleTraceDependency - */ - -/** - * @template T - * @template I - * @param {Iterable} items items to select from - * @param {function(T): Iterable} selector selector function to select values from item - * @returns {I[]} array of values - */ -const uniqueArray = (items, selector) => { - /** @type {Set} */ - const set = new Set(); - for (const item of items) { - for (const i of selector(item)) { - set.add(i); - } - } - return Array.from(set); -}; - -/** - * @template T - * @template I - * @param {Iterable} items items to select from - * @param {function(T): Iterable} selector selector function to select values from item - * @param {Comparator} comparator comparator function - * @returns {I[]} array of values - */ -const uniqueOrderedArray = (items, selector, comparator) => - uniqueArray(items, selector).sort(comparator); - -/** @template T @template R @typedef {{ [P in keyof T]: R }} MappedValues */ - -/** - * @template {object} T - * @template {object} R - * @param {T} obj object to be mapped - * @param {function(T[keyof T], keyof T): R} fn mapping function - * @returns {MappedValues} mapped object - */ -const mapObject = (obj, fn) => { - const newObj = Object.create(null); - for (const key of Object.keys(obj)) { - newObj[key] = fn( - obj[/** @type {keyof T} */ (key)], - /** @type {keyof T} */ (key) - ); - } - return newObj; -}; - -/** - * @param {Compilation} compilation the compilation - * @param {function(Compilation, string): any[]} getItems get items - * @returns {number} total number - */ -const countWithChildren = (compilation, getItems) => { - let count = getItems(compilation, "").length; - for (const child of compilation.children) { - count += countWithChildren(child, (c, type) => - getItems(c, `.children[].compilation${type}`) - ); - } - return count; -}; - -/** @type {ExtractorsByOption} */ -const EXTRACT_ERROR = { - _: (object, error, context, { requestShortener }) => { - // TODO webpack 6 disallow strings in the errors/warnings list - if (typeof error === "string") { - object.message = error; - } else { - if (error.chunk) { - object.chunkName = error.chunk.name; - object.chunkEntry = error.chunk.hasRuntime(); - object.chunkInitial = error.chunk.canBeInitial(); - } - if (error.file) { - object.file = error.file; - } - if (error.module) { - object.moduleIdentifier = error.module.identifier(); - object.moduleName = error.module.readableIdentifier(requestShortener); - } - if (error.loc) { - object.loc = formatLocation(error.loc); - } - object.message = error.message; - } - }, - ids: (object, error, { compilation: { chunkGraph } }) => { - if (typeof error !== "string") { - if (error.chunk) { - object.chunkId = /** @type {ChunkId} */ (error.chunk.id); - } - if (error.module) { - object.moduleId = - /** @type {ModuleId} */ - (chunkGraph.getModuleId(error.module)); - } - } - }, - moduleTrace: (object, error, context, options, factory) => { - if (typeof error !== "string" && error.module) { - const { - type, - compilation: { moduleGraph } - } = context; - /** @type {Set} */ - const visitedModules = new Set(); - const moduleTrace = []; - let current = error.module; - while (current) { - if (visitedModules.has(current)) break; // circular (technically impossible, but how knows) - visitedModules.add(current); - const origin = moduleGraph.getIssuer(current); - if (!origin) break; - moduleTrace.push({ origin, module: current }); - current = origin; - } - object.moduleTrace = factory.create( - `${type}.moduleTrace`, - moduleTrace, - context - ); - } - }, - errorDetails: ( - object, - error, - { type, compilation, cachedGetErrors, cachedGetWarnings }, - { errorDetails } - ) => { - if ( - typeof error !== "string" && - (errorDetails === true || - (type.endsWith(".error") && cachedGetErrors(compilation).length < 3)) - ) { - object.details = error.details; - } - }, - errorStack: (object, error) => { - if (typeof error !== "string") { - object.stack = error.stack; - } - } -}; - -/** @type {SimpleExtractors} */ -const SIMPLE_EXTRACTORS = { - compilation: { - _: (object, compilation, context, options) => { - if (!context.makePathsRelative) { - context.makePathsRelative = makePathsRelative.bindContextCache( - compilation.compiler.context, - compilation.compiler.root - ); - } - if (!context.cachedGetErrors) { - const map = new WeakMap(); - context.cachedGetErrors = compilation => - map.get(compilation) || - // eslint-disable-next-line no-sequences - (errors => (map.set(compilation, errors), errors))( - compilation.getErrors() - ); - } - if (!context.cachedGetWarnings) { - const map = new WeakMap(); - context.cachedGetWarnings = compilation => - map.get(compilation) || - // eslint-disable-next-line no-sequences - (warnings => (map.set(compilation, warnings), warnings))( - compilation.getWarnings() - ); - } - if (compilation.name) { - object.name = compilation.name; - } - if (compilation.needAdditionalPass) { - object.needAdditionalPass = true; - } - - const { logging, loggingDebug, loggingTrace } = options; - if (logging || (loggingDebug && loggingDebug.length > 0)) { - const util = require("util"); - object.logging = {}; - let acceptedTypes; - let collapsedGroups = false; - switch (logging) { - case "error": - acceptedTypes = new Set([LogType.error]); - break; - case "warn": - acceptedTypes = new Set([LogType.error, LogType.warn]); - break; - case "info": - acceptedTypes = new Set([ - LogType.error, - LogType.warn, - LogType.info - ]); - break; - case "log": - acceptedTypes = new Set([ - LogType.error, - LogType.warn, - LogType.info, - LogType.log, - LogType.group, - LogType.groupEnd, - LogType.groupCollapsed, - LogType.clear - ]); - break; - case "verbose": - acceptedTypes = new Set([ - LogType.error, - LogType.warn, - LogType.info, - LogType.log, - LogType.group, - LogType.groupEnd, - LogType.groupCollapsed, - LogType.profile, - LogType.profileEnd, - LogType.time, - LogType.status, - LogType.clear - ]); - collapsedGroups = true; - break; - default: - acceptedTypes = new Set(); - break; - } - const cachedMakePathsRelative = makePathsRelative.bindContextCache( - options.context, - compilation.compiler.root - ); - let depthInCollapsedGroup = 0; - for (const [origin, logEntries] of compilation.logging) { - const debugMode = loggingDebug.some(fn => fn(origin)); - if (logging === false && !debugMode) continue; - /** @type {KnownStatsLoggingEntry[]} */ - const groupStack = []; - /** @type {KnownStatsLoggingEntry[]} */ - const rootList = []; - let currentList = rootList; - let processedLogEntries = 0; - for (const entry of logEntries) { - let type = entry.type; - if (!debugMode && !acceptedTypes.has(type)) continue; - - // Expand groups in verbose and debug modes - if ( - type === LogType.groupCollapsed && - (debugMode || collapsedGroups) - ) - type = LogType.group; - - if (depthInCollapsedGroup === 0) { - processedLogEntries++; - } - - if (type === LogType.groupEnd) { - groupStack.pop(); - currentList = - groupStack.length > 0 - ? /** @type {KnownStatsLoggingEntry[]} */ ( - groupStack[groupStack.length - 1].children - ) - : rootList; - if (depthInCollapsedGroup > 0) depthInCollapsedGroup--; - continue; - } - let message; - if (entry.type === LogType.time) { - const [label, first, second] = - /** @type {[string, number, number]} */ - (entry.args); - message = `${label}: ${first * 1000 + second / 1000000} ms`; - } else if (entry.args && entry.args.length > 0) { - message = util.format(entry.args[0], ...entry.args.slice(1)); - } - /** @type {KnownStatsLoggingEntry} */ - const newEntry = { - ...entry, - type, - message, - trace: loggingTrace ? entry.trace : undefined, - children: - type === LogType.group || type === LogType.groupCollapsed - ? [] - : undefined - }; - currentList.push(newEntry); - if (newEntry.children) { - groupStack.push(newEntry); - currentList = newEntry.children; - if (depthInCollapsedGroup > 0) { - depthInCollapsedGroup++; - } else if (type === LogType.groupCollapsed) { - depthInCollapsedGroup = 1; - } - } - } - let name = cachedMakePathsRelative(origin).replace(/\|/g, " "); - if (name in object.logging) { - let i = 1; - while (`${name}#${i}` in object.logging) { - i++; - } - name = `${name}#${i}`; - } - object.logging[name] = { - entries: rootList, - filteredEntries: logEntries.length - processedLogEntries, - debug: debugMode - }; - } - } - }, - hash: (object, compilation) => { - object.hash = /** @type {string} */ (compilation.hash); - }, - version: object => { - object.version = require("../../package.json").version; - }, - env: (object, compilation, context, { _env }) => { - object.env = _env; - }, - timings: (object, compilation) => { - object.time = - /** @type {number} */ (compilation.endTime) - - /** @type {number} */ (compilation.startTime); - }, - builtAt: (object, compilation) => { - object.builtAt = /** @type {number} */ (compilation.endTime); - }, - publicPath: (object, compilation) => { - object.publicPath = compilation.getPath( - /** @type {TemplatePath} */ - (compilation.outputOptions.publicPath) - ); - }, - outputPath: (object, compilation) => { - object.outputPath = /** @type {string} */ ( - compilation.outputOptions.path - ); - }, - assets: (object, compilation, context, options, factory) => { - const { type } = context; - /** @type {Map} */ - const compilationFileToChunks = new Map(); - /** @type {Map} */ - const compilationAuxiliaryFileToChunks = new Map(); - for (const chunk of compilation.chunks) { - for (const file of chunk.files) { - let array = compilationFileToChunks.get(file); - if (array === undefined) { - array = []; - compilationFileToChunks.set(file, array); - } - array.push(chunk); - } - for (const file of chunk.auxiliaryFiles) { - let array = compilationAuxiliaryFileToChunks.get(file); - if (array === undefined) { - array = []; - compilationAuxiliaryFileToChunks.set(file, array); - } - array.push(chunk); - } - } - /** @type {Map} */ - const assetMap = new Map(); - /** @type {Set} */ - const assets = new Set(); - for (const asset of compilation.getAssets()) { - /** @type {PreprocessedAsset} */ - const item = { - ...asset, - type: "asset", - related: undefined - }; - assets.add(item); - assetMap.set(asset.name, item); - } - for (const item of assetMap.values()) { - const related = item.info.related; - if (!related) continue; - for (const type of Object.keys(related)) { - const relatedEntry = related[type]; - const deps = Array.isArray(relatedEntry) - ? relatedEntry - : [relatedEntry]; - for (const dep of deps) { - const depItem = assetMap.get(dep); - if (!depItem) continue; - assets.delete(depItem); - depItem.type = type; - item.related = item.related || []; - item.related.push(depItem); - } - } - } - - object.assetsByChunkName = {}; - for (const [file, chunks] of compilationFileToChunks) { - for (const chunk of chunks) { - const name = chunk.name; - if (!name) continue; - if ( - !Object.prototype.hasOwnProperty.call( - object.assetsByChunkName, - name - ) - ) { - object.assetsByChunkName[name] = []; - } - object.assetsByChunkName[name].push(file); - } - } - - const groupedAssets = factory.create( - `${type}.assets`, - Array.from(assets), - { - ...context, - compilationFileToChunks, - compilationAuxiliaryFileToChunks - } - ); - const limited = spaceLimited( - groupedAssets, - /** @type {number} */ (options.assetsSpace) - ); - object.assets = limited.children; - object.filteredAssets = limited.filteredChildren; - }, - chunks: (object, compilation, context, options, factory) => { - const { type } = context; - object.chunks = factory.create( - `${type}.chunks`, - Array.from(compilation.chunks), - context - ); - }, - modules: (object, compilation, context, options, factory) => { - const { type } = context; - const array = Array.from(compilation.modules); - const groupedModules = factory.create(`${type}.modules`, array, context); - const limited = spaceLimited(groupedModules, options.modulesSpace); - object.modules = limited.children; - object.filteredModules = limited.filteredChildren; - }, - entrypoints: ( - object, - compilation, - context, - { entrypoints, chunkGroups, chunkGroupAuxiliary, chunkGroupChildren }, - factory - ) => { - const { type } = context; - const array = Array.from(compilation.entrypoints, ([key, value]) => ({ - name: key, - chunkGroup: value - })); - if (entrypoints === "auto" && !chunkGroups) { - if (array.length > 5) return; - if ( - !chunkGroupChildren && - array.every(({ chunkGroup }) => { - if (chunkGroup.chunks.length !== 1) return false; - const chunk = chunkGroup.chunks[0]; - return ( - chunk.files.size === 1 && - (!chunkGroupAuxiliary || chunk.auxiliaryFiles.size === 0) - ); - }) - ) { - return; - } - } - object.entrypoints = factory.create( - `${type}.entrypoints`, - array, - context - ); - }, - chunkGroups: (object, compilation, context, options, factory) => { - const { type } = context; - const array = Array.from( - compilation.namedChunkGroups, - ([key, value]) => ({ - name: key, - chunkGroup: value - }) - ); - object.namedChunkGroups = factory.create( - `${type}.namedChunkGroups`, - array, - context - ); - }, - errors: (object, compilation, context, options, factory) => { - const { type, cachedGetErrors } = context; - const rawErrors = cachedGetErrors(compilation); - const factorizedErrors = factory.create( - `${type}.errors`, - cachedGetErrors(compilation), - context - ); - let filtered = 0; - if (options.errorDetails === "auto" && rawErrors.length >= 3) { - filtered = rawErrors - .map(e => typeof e !== "string" && e.details) - .filter(Boolean).length; - } - if ( - options.errorDetails === true || - !Number.isFinite(options.errorsSpace) - ) { - object.errors = factorizedErrors; - if (filtered) object.filteredErrorDetailsCount = filtered; - return; - } - const [errors, filteredBySpace] = errorsSpaceLimit( - factorizedErrors, - /** @type {number} */ - (options.errorsSpace) - ); - object.filteredErrorDetailsCount = filtered + filteredBySpace; - object.errors = errors; - }, - errorsCount: (object, compilation, { cachedGetErrors }) => { - object.errorsCount = countWithChildren(compilation, c => - cachedGetErrors(c) - ); - }, - warnings: (object, compilation, context, options, factory) => { - const { type, cachedGetWarnings } = context; - const rawWarnings = factory.create( - `${type}.warnings`, - cachedGetWarnings(compilation), - context - ); - let filtered = 0; - if (options.errorDetails === "auto") { - filtered = cachedGetWarnings(compilation) - .map(e => typeof e !== "string" && e.details) - .filter(Boolean).length; - } - if ( - options.errorDetails === true || - !Number.isFinite(options.warningsSpace) - ) { - object.warnings = rawWarnings; - if (filtered) object.filteredWarningDetailsCount = filtered; - return; - } - const [warnings, filteredBySpace] = errorsSpaceLimit( - rawWarnings, - /** @type {number} */ - (options.warningsSpace) - ); - object.filteredWarningDetailsCount = filtered + filteredBySpace; - object.warnings = warnings; - }, - warningsCount: ( - object, - compilation, - context, - { warningsFilter }, - factory - ) => { - const { type, cachedGetWarnings } = context; - object.warningsCount = countWithChildren(compilation, (c, childType) => { - if ( - !warningsFilter && - /** @type {((warning: StatsError, textValue: string) => boolean)[]} */ - (warningsFilter).length === 0 - ) - return cachedGetWarnings(c); - return factory - .create(`${type}${childType}.warnings`, cachedGetWarnings(c), context) - .filter( - /** - * @param {TODO} warning warning - * @returns {boolean} result - */ - warning => { - const warningString = Object.keys(warning) - .map( - key => - `${warning[/** @type {keyof KnownStatsError} */ (key)]}` - ) - .join("\n"); - return !warningsFilter.some(filter => - filter(warning, warningString) - ); - } - ); - }); - }, - children: (object, compilation, context, options, factory) => { - const { type } = context; - object.children = factory.create( - `${type}.children`, - compilation.children, - context - ); - } - }, - asset: { - _: (object, asset, context, options, factory) => { - const { compilation } = context; - object.type = asset.type; - object.name = asset.name; - object.size = asset.source.size(); - object.emitted = compilation.emittedAssets.has(asset.name); - object.comparedForEmit = compilation.comparedForEmitAssets.has( - asset.name - ); - const cached = !object.emitted && !object.comparedForEmit; - object.cached = cached; - object.info = asset.info; - if (!cached || options.cachedAssets) { - Object.assign( - object, - factory.create(`${context.type}$visible`, asset, context) - ); - } - } - }, - asset$visible: { - _: ( - object, - asset, - { compilation, compilationFileToChunks, compilationAuxiliaryFileToChunks } - ) => { - const chunks = compilationFileToChunks.get(asset.name) || []; - const auxiliaryChunks = - compilationAuxiliaryFileToChunks.get(asset.name) || []; - object.chunkNames = uniqueOrderedArray( - chunks, - c => (c.name ? [c.name] : []), - compareIds - ); - object.chunkIdHints = uniqueOrderedArray( - chunks, - c => Array.from(c.idNameHints), - compareIds - ); - object.auxiliaryChunkNames = uniqueOrderedArray( - auxiliaryChunks, - c => (c.name ? [c.name] : []), - compareIds - ); - object.auxiliaryChunkIdHints = uniqueOrderedArray( - auxiliaryChunks, - c => Array.from(c.idNameHints), - compareIds - ); - object.filteredRelated = asset.related ? asset.related.length : undefined; - }, - relatedAssets: (object, asset, context, options, factory) => { - const { type } = context; - object.related = factory.create( - `${type.slice(0, -8)}.related`, - asset.related || [], - context - ); - object.filteredRelated = asset.related - ? asset.related.length - - /** @type {StatsAsset[]} */ (object.related).length - : undefined; - }, - ids: ( - object, - asset, - { compilationFileToChunks, compilationAuxiliaryFileToChunks } - ) => { - const chunks = compilationFileToChunks.get(asset.name) || []; - const auxiliaryChunks = - compilationAuxiliaryFileToChunks.get(asset.name) || []; - object.chunks = uniqueOrderedArray( - chunks, - c => /** @type {ChunkId[]} */ (c.ids), - compareIds - ); - object.auxiliaryChunks = uniqueOrderedArray( - auxiliaryChunks, - c => /** @type {ChunkId[]} */ (c.ids), - compareIds - ); - }, - performance: (object, asset) => { - object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(asset.source); - } - }, - chunkGroup: { - _: ( - object, - { name, chunkGroup }, - { compilation, compilation: { moduleGraph, chunkGraph } }, - { ids, chunkGroupAuxiliary, chunkGroupChildren, chunkGroupMaxAssets } - ) => { - const children = - chunkGroupChildren && - chunkGroup.getChildrenByOrders(moduleGraph, chunkGraph); - /** - * @param {string} name Name - * @returns {{ name: string, size: number }} Asset object - */ - const toAsset = name => { - const asset = compilation.getAsset(name); - return { - name, - size: /** @type {number} */ (asset ? asset.info.size : -1) - }; - }; - /** @type {(total: number, asset: { size: number }) => number} */ - const sizeReducer = (total, { size }) => total + size; - const assets = uniqueArray(chunkGroup.chunks, c => c.files).map(toAsset); - const auxiliaryAssets = uniqueOrderedArray( - chunkGroup.chunks, - c => c.auxiliaryFiles, - compareIds - ).map(toAsset); - const assetsSize = assets.reduce(sizeReducer, 0); - const auxiliaryAssetsSize = auxiliaryAssets.reduce(sizeReducer, 0); - /** @type {KnownStatsChunkGroup} */ - const statsChunkGroup = { - name, - chunks: ids - ? /** @type {ChunkId[]} */ (chunkGroup.chunks.map(c => c.id)) - : undefined, - assets: assets.length <= chunkGroupMaxAssets ? assets : undefined, - filteredAssets: - assets.length <= chunkGroupMaxAssets ? 0 : assets.length, - assetsSize, - auxiliaryAssets: - chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets - ? auxiliaryAssets - : undefined, - filteredAuxiliaryAssets: - chunkGroupAuxiliary && auxiliaryAssets.length <= chunkGroupMaxAssets - ? 0 - : auxiliaryAssets.length, - auxiliaryAssetsSize, - children: children - ? mapObject(children, groups => - groups.map(group => { - const assets = uniqueArray(group.chunks, c => c.files).map( - toAsset - ); - const auxiliaryAssets = uniqueOrderedArray( - group.chunks, - c => c.auxiliaryFiles, - compareIds - ).map(toAsset); - - /** @type {KnownStatsChunkGroup} */ - const childStatsChunkGroup = { - name: group.name, - chunks: ids - ? /** @type {ChunkId[]} */ - (group.chunks.map(c => c.id)) - : undefined, - assets: - assets.length <= chunkGroupMaxAssets ? assets : undefined, - filteredAssets: - assets.length <= chunkGroupMaxAssets ? 0 : assets.length, - auxiliaryAssets: - chunkGroupAuxiliary && - auxiliaryAssets.length <= chunkGroupMaxAssets - ? auxiliaryAssets - : undefined, - filteredAuxiliaryAssets: - chunkGroupAuxiliary && - auxiliaryAssets.length <= chunkGroupMaxAssets - ? 0 - : auxiliaryAssets.length - }; - - return childStatsChunkGroup; - }) - ) - : undefined, - childAssets: children - ? mapObject(children, groups => { - /** @type {Set} */ - const set = new Set(); - for (const group of groups) { - for (const chunk of group.chunks) { - for (const asset of chunk.files) { - set.add(asset); - } - } - } - return Array.from(set); - }) - : undefined - }; - Object.assign(object, statsChunkGroup); - }, - performance: (object, { chunkGroup }) => { - object.isOverSizeLimit = SizeLimitsPlugin.isOverSizeLimit(chunkGroup); - } - }, - module: { - _: (object, module, context, options, factory) => { - const { type } = context; - const compilation = /** @type {Compilation} */ (context.compilation); - const built = compilation.builtModules.has(module); - const codeGenerated = compilation.codeGeneratedModules.has(module); - const buildTimeExecuted = - compilation.buildTimeExecutedModules.has(module); - /** @type {{[x: string]: number}} */ - const sizes = {}; - for (const sourceType of module.getSourceTypes()) { - sizes[sourceType] = module.size(sourceType); - } - /** @type {KnownStatsModule} */ - const statsModule = { - type: "module", - moduleType: module.type, - layer: module.layer, - size: module.size(), - sizes, - built, - codeGenerated, - buildTimeExecuted, - cached: !built && !codeGenerated - }; - Object.assign(object, statsModule); - - if (built || codeGenerated || options.cachedModules) { - Object.assign( - object, - factory.create(`${type}$visible`, module, context) - ); - } - } - }, - module$visible: { - _: (object, module, context, { requestShortener }, factory) => { - const { type, rootModules } = context; - const compilation = /** @type {Compilation} */ (context.compilation); - const { moduleGraph } = compilation; - /** @type {Module[]} */ - const path = []; - const issuer = moduleGraph.getIssuer(module); - let current = issuer; - while (current) { - path.push(current); - current = moduleGraph.getIssuer(current); - } - path.reverse(); - const profile = moduleGraph.getProfile(module); - const errors = module.getErrors(); - const errorsCount = errors !== undefined ? countIterable(errors) : 0; - const warnings = module.getWarnings(); - const warningsCount = - warnings !== undefined ? countIterable(warnings) : 0; - /** @type {KnownStatsModule} */ - const statsModule = { - identifier: module.identifier(), - name: module.readableIdentifier(requestShortener), - nameForCondition: module.nameForCondition(), - index: /** @type {number} */ (moduleGraph.getPreOrderIndex(module)), - preOrderIndex: /** @type {number} */ ( - moduleGraph.getPreOrderIndex(module) - ), - index2: /** @type {number} */ (moduleGraph.getPostOrderIndex(module)), - postOrderIndex: /** @type {number} */ ( - moduleGraph.getPostOrderIndex(module) - ), - cacheable: /** @type {BuildInfo} */ (module.buildInfo).cacheable, - optional: module.isOptional(moduleGraph), - orphan: - !type.endsWith("module.modules[].module$visible") && - compilation.chunkGraph.getNumberOfModuleChunks(module) === 0, - dependent: rootModules ? !rootModules.has(module) : undefined, - issuer: issuer && issuer.identifier(), - issuerName: issuer && issuer.readableIdentifier(requestShortener), - issuerPath: - issuer && - factory.create(`${type.slice(0, -8)}.issuerPath`, path, context), - failed: errorsCount > 0, - errors: errorsCount, - warnings: warningsCount - }; - Object.assign(object, statsModule); - if (profile) { - object.profile = factory.create( - `${type.slice(0, -8)}.profile`, - profile, - context - ); - } - }, - ids: (object, module, { compilation: { chunkGraph, moduleGraph } }) => { - object.id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - const issuer = moduleGraph.getIssuer(module); - object.issuerId = issuer && chunkGraph.getModuleId(issuer); - object.chunks = - /** @type {ChunkId[]} */ - ( - Array.from( - chunkGraph.getOrderedModuleChunksIterable( - module, - compareChunksById - ), - chunk => chunk.id - ) - ); - }, - moduleAssets: (object, module) => { - object.assets = /** @type {BuildInfo} */ (module.buildInfo).assets - ? Object.keys(/** @type {BuildInfo} */ (module.buildInfo).assets) - : []; - }, - reasons: (object, module, context, options, factory) => { - const { - type, - compilation: { moduleGraph } - } = context; - const groupsReasons = factory.create( - `${type.slice(0, -8)}.reasons`, - Array.from(moduleGraph.getIncomingConnections(module)), - context - ); - const limited = spaceLimited( - groupsReasons, - /** @type {number} */ - (options.reasonsSpace) - ); - object.reasons = limited.children; - object.filteredReasons = limited.filteredChildren; - }, - usedExports: ( - object, - module, - { runtime, compilation: { moduleGraph } } - ) => { - const usedExports = moduleGraph.getUsedExports(module, runtime); - if (usedExports === null) { - object.usedExports = null; - } else if (typeof usedExports === "boolean") { - object.usedExports = usedExports; - } else { - object.usedExports = Array.from(usedExports); - } - }, - providedExports: (object, module, { compilation: { moduleGraph } }) => { - const providedExports = moduleGraph.getProvidedExports(module); - object.providedExports = Array.isArray(providedExports) - ? providedExports - : null; - }, - optimizationBailout: ( - object, - module, - { compilation: { moduleGraph } }, - { requestShortener } - ) => { - object.optimizationBailout = moduleGraph - .getOptimizationBailout(module) - .map(item => { - if (typeof item === "function") return item(requestShortener); - return item; - }); - }, - depth: (object, module, { compilation: { moduleGraph } }) => { - object.depth = moduleGraph.getDepth(module); - }, - nestedModules: (object, module, context, options, factory) => { - const { type } = context; - const innerModules = /** @type {Module & { modules?: Module[] }} */ ( - module - ).modules; - if (Array.isArray(innerModules)) { - const groupedModules = factory.create( - `${type.slice(0, -8)}.modules`, - innerModules, - context - ); - const limited = spaceLimited( - groupedModules, - options.nestedModulesSpace - ); - object.modules = limited.children; - object.filteredModules = limited.filteredChildren; - } - }, - source: (object, module) => { - const originalSource = module.originalSource(); - if (originalSource) { - object.source = originalSource.source(); - } - } - }, - profile: { - _: (object, profile) => { - /** @type {KnownStatsProfile} */ - const statsProfile = { - total: - profile.factory + - profile.restoring + - profile.integration + - profile.building + - profile.storing, - resolving: profile.factory, - restoring: profile.restoring, - building: profile.building, - integration: profile.integration, - storing: profile.storing, - additionalResolving: profile.additionalFactories, - additionalIntegration: profile.additionalIntegration, - // TODO remove this in webpack 6 - factory: profile.factory, - // TODO remove this in webpack 6 - dependencies: profile.additionalFactories - }; - Object.assign(object, statsProfile); - } - }, - moduleIssuer: { - _: (object, module, context, { requestShortener }, factory) => { - const { type } = context; - const compilation = /** @type {Compilation} */ (context.compilation); - const { moduleGraph } = compilation; - const profile = moduleGraph.getProfile(module); - /** @type {Partial} */ - const statsModuleIssuer = { - identifier: module.identifier(), - name: module.readableIdentifier(requestShortener) - }; - Object.assign(object, statsModuleIssuer); - if (profile) { - object.profile = factory.create(`${type}.profile`, profile, context); - } - }, - ids: (object, module, { compilation: { chunkGraph } }) => { - object.id = /** @type {ModuleId} */ (chunkGraph.getModuleId(module)); - } - }, - moduleReason: { - _: (object, reason, { runtime }, { requestShortener }) => { - const dep = reason.dependency; - const moduleDep = - dep && dep instanceof ModuleDependency ? dep : undefined; - /** @type {KnownStatsModuleReason} */ - const statsModuleReason = { - moduleIdentifier: reason.originModule - ? reason.originModule.identifier() - : null, - module: reason.originModule - ? reason.originModule.readableIdentifier(requestShortener) - : null, - moduleName: reason.originModule - ? reason.originModule.readableIdentifier(requestShortener) - : null, - resolvedModuleIdentifier: reason.resolvedOriginModule - ? reason.resolvedOriginModule.identifier() - : null, - resolvedModule: reason.resolvedOriginModule - ? reason.resolvedOriginModule.readableIdentifier(requestShortener) - : null, - type: reason.dependency ? reason.dependency.type : null, - active: reason.isActive(runtime), - explanation: reason.explanation, - userRequest: (moduleDep && moduleDep.userRequest) || null - }; - Object.assign(object, statsModuleReason); - if (reason.dependency) { - const locInfo = formatLocation(reason.dependency.loc); - if (locInfo) { - object.loc = locInfo; - } - } - }, - ids: (object, reason, { compilation: { chunkGraph } }) => { - object.moduleId = reason.originModule - ? chunkGraph.getModuleId(reason.originModule) - : null; - object.resolvedModuleId = reason.resolvedOriginModule - ? chunkGraph.getModuleId(reason.resolvedOriginModule) - : null; - } - }, - chunk: { - _: (object, chunk, { makePathsRelative, compilation: { chunkGraph } }) => { - const childIdByOrder = chunk.getChildIdsByOrders(chunkGraph); - - /** @type {KnownStatsChunk} */ - const statsChunk = { - rendered: chunk.rendered, - initial: chunk.canBeInitial(), - entry: chunk.hasRuntime(), - recorded: AggressiveSplittingPlugin.wasChunkRecorded(chunk), - reason: chunk.chunkReason, - size: chunkGraph.getChunkModulesSize(chunk), - sizes: chunkGraph.getChunkModulesSizes(chunk), - names: chunk.name ? [chunk.name] : [], - idHints: Array.from(chunk.idNameHints), - runtime: - chunk.runtime === undefined - ? undefined - : typeof chunk.runtime === "string" - ? [makePathsRelative(chunk.runtime)] - : Array.from(chunk.runtime.sort(), makePathsRelative), - files: Array.from(chunk.files), - auxiliaryFiles: Array.from(chunk.auxiliaryFiles).sort(compareIds), - hash: /** @type {string} */ (chunk.renderedHash), - childrenByOrder: childIdByOrder - }; - Object.assign(object, statsChunk); - }, - ids: (object, chunk) => { - object.id = /** @type {ChunkId} */ (chunk.id); - }, - chunkRelations: (object, chunk, { compilation: { chunkGraph } }) => { - /** @type {Set} */ - const parents = new Set(); - /** @type {Set} */ - const children = new Set(); - /** @type {Set} */ - const siblings = new Set(); - - for (const chunkGroup of chunk.groupsIterable) { - for (const parentGroup of chunkGroup.parentsIterable) { - for (const chunk of parentGroup.chunks) { - parents.add(/** @type {ChunkId} */ (chunk.id)); - } - } - for (const childGroup of chunkGroup.childrenIterable) { - for (const chunk of childGroup.chunks) { - children.add(/** @type {ChunkId} */ (chunk.id)); - } - } - for (const sibling of chunkGroup.chunks) { - if (sibling !== chunk) - siblings.add(/** @type {ChunkId} */ (sibling.id)); - } - } - object.siblings = Array.from(siblings).sort(compareIds); - object.parents = Array.from(parents).sort(compareIds); - object.children = Array.from(children).sort(compareIds); - }, - chunkModules: (object, chunk, context, options, factory) => { - const { - type, - compilation: { chunkGraph } - } = context; - const array = chunkGraph.getChunkModules(chunk); - const groupedModules = factory.create(`${type}.modules`, array, { - ...context, - runtime: chunk.runtime, - rootModules: new Set(chunkGraph.getChunkRootModules(chunk)) - }); - const limited = spaceLimited(groupedModules, options.chunkModulesSpace); - object.modules = limited.children; - object.filteredModules = limited.filteredChildren; - }, - chunkOrigins: (object, chunk, context, options, factory) => { - const { - type, - compilation: { chunkGraph } - } = context; - /** @type {Set} */ - const originsKeySet = new Set(); - const origins = []; - for (const g of chunk.groupsIterable) { - origins.push(...g.origins); - } - const array = origins.filter(origin => { - const key = [ - origin.module ? chunkGraph.getModuleId(origin.module) : undefined, - formatLocation(origin.loc), - origin.request - ].join(); - if (originsKeySet.has(key)) return false; - originsKeySet.add(key); - return true; - }); - object.origins = factory.create(`${type}.origins`, array, context); - } - }, - chunkOrigin: { - _: (object, origin, context, { requestShortener }) => { - /** @type {KnownStatsChunkOrigin} */ - const statsChunkOrigin = { - 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 - }; - Object.assign(object, statsChunkOrigin); - }, - ids: (object, origin, { compilation: { chunkGraph } }) => { - object.moduleId = origin.module - ? /** @type {ModuleId} */ (chunkGraph.getModuleId(origin.module)) - : undefined; - } - }, - error: EXTRACT_ERROR, - warning: EXTRACT_ERROR, - moduleTraceItem: { - _: (object, { origin, module }, context, { requestShortener }, factory) => { - const { - type, - compilation: { moduleGraph } - } = context; - object.originIdentifier = origin.identifier(); - object.originName = origin.readableIdentifier(requestShortener); - object.moduleIdentifier = module.identifier(); - object.moduleName = module.readableIdentifier(requestShortener); - const dependencies = Array.from( - moduleGraph.getIncomingConnections(module) - ) - .filter(c => c.resolvedOriginModule === origin && c.dependency) - .map(c => c.dependency); - object.dependencies = factory.create( - `${type}.dependencies`, - Array.from(new Set(dependencies)), - context - ); - }, - ids: (object, { origin, module }, { compilation: { chunkGraph } }) => { - object.originId = - /** @type {ModuleId} */ - (chunkGraph.getModuleId(origin)); - object.moduleId = - /** @type {ModuleId} */ - (chunkGraph.getModuleId(module)); - } - }, - moduleTraceDependency: { - _: (object, dependency) => { - object.loc = formatLocation(dependency.loc); - } - } -}; - -/** @type {Record boolean | undefined>>} */ -const FILTER = { - "module.reasons": { - "!orphanModules": (reason, { compilation: { chunkGraph } }) => { - if ( - reason.originModule && - chunkGraph.getNumberOfModuleChunks(reason.originModule) === 0 - ) { - return false; - } - } - } -}; - -/** @type {Record boolean | undefined>>} */ -const FILTER_RESULTS = { - "compilation.warnings": { - warningsFilter: util.deprecate( - (warning, context, { warningsFilter }) => { - const warningString = Object.keys(warning) - .map(key => `${warning[/** @type {keyof KnownStatsError} */ (key)]}`) - .join("\n"); - return !warningsFilter.some(filter => filter(warning, warningString)); - }, - "config.stats.warningsFilter is deprecated in favor of config.ignoreWarnings", - "DEP_WEBPACK_STATS_WARNINGS_FILTER" - ) - } -}; - -/** @type {Record void>} */ -const MODULES_SORTER = { - _: (comparators, { compilation: { moduleGraph } }) => { - comparators.push( - compareSelect( - /** - * @param {Module} m module - * @returns {number | null} depth - */ - m => moduleGraph.getDepth(m), - compareNumbers - ), - compareSelect( - /** - * @param {Module} m module - * @returns {number | null} index - */ - m => moduleGraph.getPreOrderIndex(m), - compareNumbers - ), - compareSelect( - /** - * @param {Module} m module - * @returns {string} identifier - */ - m => m.identifier(), - compareIds - ) - ); - } -}; - -/** @type {Record void>>} */ -const SORTERS = { - "compilation.chunks": { - _: comparators => { - comparators.push(compareSelect(c => c.id, compareIds)); - } - }, - "compilation.modules": MODULES_SORTER, - "chunk.rootModules": MODULES_SORTER, - "chunk.modules": MODULES_SORTER, - "module.modules": MODULES_SORTER, - "module.reasons": { - _: (comparators, { compilation: { chunkGraph } }) => { - comparators.push( - compareSelect(x => x.originModule, compareModulesByIdentifier) - ); - comparators.push( - compareSelect(x => x.resolvedOriginModule, compareModulesByIdentifier) - ); - comparators.push( - compareSelect( - x => x.dependency, - concatComparators( - compareSelect( - /** - * @param {Dependency} x dependency - * @returns {DependencyLocation} location - */ - x => x.loc, - compareLocations - ), - compareSelect(x => x.type, compareIds) - ) - ) - ); - } - }, - "chunk.origins": { - _: (comparators, { compilation: { chunkGraph } }) => { - comparators.push( - compareSelect( - origin => - origin.module ? chunkGraph.getModuleId(origin.module) : undefined, - compareIds - ), - compareSelect(origin => formatLocation(origin.loc), compareIds), - compareSelect(origin => origin.request, compareIds) - ); - } - } -}; - -/** - * @template T - * @typedef {T & { children: Children[] | undefined, filteredChildren?: number }} Children - */ - -/** - * @template T - * @param {Children} item item - * @returns {number} item size - */ -const getItemSize = item => - // Each item takes 1 line - // + the size of the children - // + 1 extra line when it has children and filteredChildren - !item.children - ? 1 - : item.filteredChildren - ? 2 + getTotalSize(item.children) - : 1 + getTotalSize(item.children); - -/** - * @template T - * @param {Children[]} children children - * @returns {number} total size - */ -const getTotalSize = children => { - let size = 0; - for (const child of children) { - size += getItemSize(child); - } - return size; -}; - -/** - * @template T - * @param {Children[]} children children - * @returns {number} total items - */ -const getTotalItems = children => { - let count = 0; - for (const child of children) { - if (!child.children && !child.filteredChildren) { - count++; - } else { - if (child.children) count += getTotalItems(child.children); - if (child.filteredChildren) count += child.filteredChildren; - } - } - return count; -}; - -/** - * @template T - * @param {Children[]} children children - * @returns {Children[]} collapsed children - */ -const collapse = children => { - // After collapse each child must take exactly one line - const newChildren = []; - for (const child of children) { - if (child.children) { - let filteredChildren = child.filteredChildren || 0; - filteredChildren += getTotalItems(child.children); - newChildren.push({ - ...child, - children: undefined, - filteredChildren - }); - } else { - newChildren.push(child); - } - } - return newChildren; -}; - -/** - * @template T - * @param {Children[]} itemsAndGroups item and groups - * @param {number} max max - * @param {boolean=} filteredChildrenLineReserved filtered children line reserved - * @returns {Children} result - */ -const spaceLimited = ( - itemsAndGroups, - max, - filteredChildrenLineReserved = false -) => { - if (max < 1) { - return /** @type {Children} */ ({ - children: undefined, - filteredChildren: getTotalItems(itemsAndGroups) - }); - } - /** @type {Children[] | undefined} */ - let children; - /** @type {number | undefined} */ - let filteredChildren; - // This are the groups, which take 1+ lines each - /** @type {Children[] | undefined} */ - const groups = []; - // The sizes of the groups are stored in groupSizes - /** @type {number[]} */ - const groupSizes = []; - // This are the items, which take 1 line each - const items = []; - // The total of group sizes - let groupsSize = 0; - - for (const itemOrGroup of itemsAndGroups) { - // is item - if (!itemOrGroup.children && !itemOrGroup.filteredChildren) { - items.push(itemOrGroup); - } else { - groups.push(itemOrGroup); - const size = getItemSize(itemOrGroup); - groupSizes.push(size); - groupsSize += size; - } - } - - if (groupsSize + items.length <= max) { - // The total size in the current state fits into the max - // keep all - children = groups.length > 0 ? groups.concat(items) : items; - } else if (groups.length === 0) { - // slice items to max - // inner space marks that lines for filteredChildren already reserved - const limit = max - (filteredChildrenLineReserved ? 0 : 1); - filteredChildren = items.length - limit; - items.length = limit; - children = items; - } else { - // limit is the size when all groups are collapsed - const limit = - groups.length + - (filteredChildrenLineReserved || items.length === 0 ? 0 : 1); - if (limit < max) { - // calculate how much we are over the size limit - // this allows to approach the limit faster - let oversize; - // If each group would take 1 line the total would be below the maximum - // collapse some groups, keep items - while ( - (oversize = - groupsSize + - items.length + - (filteredChildren && !filteredChildrenLineReserved ? 1 : 0) - - max) > 0 - ) { - // Find the maximum group and process only this one - const maxGroupSize = Math.max(...groupSizes); - if (maxGroupSize < items.length) { - filteredChildren = items.length; - items.length = 0; - continue; - } - for (let i = 0; i < groups.length; i++) { - if (groupSizes[i] === maxGroupSize) { - const group = groups[i]; - // run this algorithm recursively and limit the size of the children to - // current size - oversize / number of groups - // So it should always end up being smaller - const headerSize = group.filteredChildren ? 2 : 1; - const limited = spaceLimited( - /** @type {Children} */ (group.children), - maxGroupSize - - // we should use ceil to always feet in max - Math.ceil(oversize / groups.length) - - // we substitute size of group head - headerSize, - headerSize === 2 - ); - groups[i] = { - ...group, - children: limited.children, - filteredChildren: limited.filteredChildren - ? (group.filteredChildren || 0) + limited.filteredChildren - : group.filteredChildren - }; - const newSize = getItemSize(groups[i]); - groupsSize -= maxGroupSize - newSize; - groupSizes[i] = newSize; - break; - } - } - } - children = groups.concat(items); - } else if (limit === max) { - // If we have only enough space to show one line per group and one line for the filtered items - // collapse all groups and items - children = collapse(groups); - filteredChildren = items.length; - } else { - // If we have no space - // collapse complete group - filteredChildren = getTotalItems(itemsAndGroups); - } - } - - return /** @type {Children} */ ({ children, filteredChildren }); -}; - -/** - * @param {StatsError[]} errors errors - * @param {number} max max - * @returns {[StatsError[], number]} error space limit - */ -const errorsSpaceLimit = (errors, max) => { - let filtered = 0; - // Can not fit into limit - // print only messages - if (errors.length + 1 >= max) - return [ - errors.map(error => { - if (typeof error === "string" || !error.details) return error; - filtered++; - return { ...error, details: "" }; - }), - filtered - ]; - let fullLength = errors.length; - let result = errors; - - let i = 0; - for (; i < errors.length; i++) { - const error = errors[i]; - if (typeof error !== "string" && error.details) { - const splitted = error.details.split("\n"); - const len = splitted.length; - fullLength += len; - if (fullLength > max) { - result = i > 0 ? errors.slice(0, i) : []; - const overLimit = fullLength - max + 1; - const error = errors[i++]; - result.push({ - ...error, - details: error.details.split("\n").slice(0, -overLimit).join("\n"), - filteredDetails: overLimit - }); - filtered = errors.length - i; - for (; i < errors.length; i++) { - const error = errors[i]; - if (typeof error === "string" || !error.details) result.push(error); - result.push({ ...error, details: "" }); - } - break; - } else if (fullLength === max) { - result = errors.slice(0, ++i); - filtered = errors.length - i; - for (; i < errors.length; i++) { - const error = errors[i]; - if (typeof error === "string" || !error.details) result.push(error); - result.push({ ...error, details: "" }); - } - break; - } - } - } - - return [result, filtered]; -}; - -/** - * @template {{ size: number }} T - * @template {{ size: number }} R - * @param {(R | T)[]} children children - * @param {T[]} assets assets - * @returns {{ size: number }} asset size - */ -const assetGroup = (children, assets) => { - let size = 0; - for (const asset of children) { - size += asset.size; - } - return { size }; -}; - -/** - * @template {{ size: number, sizes: Record }} T - * @param {Children[]} children children - * @param {KnownStatsModule[]} modules modules - * @returns {{ size: number, sizes: Record}} size and sizes - */ -const moduleGroup = (children, modules) => { - let size = 0; - /** @type {Record} */ - const sizes = {}; - for (const module of children) { - size += module.size; - for (const key of Object.keys(module.sizes)) { - sizes[key] = (sizes[key] || 0) + module.sizes[key]; - } - } - return { - size, - sizes - }; -}; - -/** - * @template {{ active: boolean }} T - * @param {Children[]} children children - * @param {KnownStatsModuleReason[]} reasons reasons - * @returns {{ active: boolean }} reason group - */ -const reasonGroup = (children, reasons) => { - let active = false; - for (const reason of children) { - active = active || reason.active; - } - return { - active - }; -}; - -const GROUP_EXTENSION_REGEXP = /(\.[^.]+?)(?:\?|(?: \+ \d+ modules?)?$)/; -const GROUP_PATH_REGEXP = /(.+)[/\\][^/\\]+?(?:\?|(?: \+ \d+ modules?)?$)/; - -/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} AssetsGroupers */ - -/** @type {AssetsGroupers} */ -const ASSETS_GROUPERS = { - _: (groupConfigs, context, options) => { - /** - * @param {keyof KnownStatsAsset} name name - * @param {boolean=} exclude need exclude? - */ - const groupByFlag = (name, exclude) => { - groupConfigs.push({ - getKeys: asset => (asset[name] ? ["1"] : undefined), - getOptions: () => ({ - groupChildren: !exclude, - force: exclude - }), - createGroup: (key, children, assets) => - exclude - ? { - type: "assets by status", - [name]: Boolean(key), - filteredChildren: assets.length, - ...assetGroup(children, assets) - } - : { - type: "assets by status", - [name]: Boolean(key), - children, - ...assetGroup(children, assets) - } - }); - }; - const { - groupAssetsByEmitStatus, - groupAssetsByPath, - groupAssetsByExtension - } = options; - if (groupAssetsByEmitStatus) { - groupByFlag("emitted"); - groupByFlag("comparedForEmit"); - groupByFlag("isOverSizeLimit"); - } - if (groupAssetsByEmitStatus || !options.cachedAssets) { - groupByFlag("cached", !options.cachedAssets); - } - if (groupAssetsByPath || groupAssetsByExtension) { - groupConfigs.push({ - getKeys: asset => { - const extensionMatch = - groupAssetsByExtension && GROUP_EXTENSION_REGEXP.exec(asset.name); - const extension = extensionMatch ? extensionMatch[1] : ""; - const pathMatch = - groupAssetsByPath && GROUP_PATH_REGEXP.exec(asset.name); - const path = pathMatch ? pathMatch[1].split(/[/\\]/) : []; - const keys = []; - if (groupAssetsByPath) { - keys.push("."); - if (extension) - keys.push( - path.length - ? `${path.join("/")}/*${extension}` - : `*${extension}` - ); - while (path.length > 0) { - keys.push(`${path.join("/")}/`); - path.pop(); - } - } else if (extension) { - keys.push(`*${extension}`); - } - return keys; - }, - createGroup: (key, children, assets) => ({ - type: groupAssetsByPath ? "assets by path" : "assets by extension", - name: key, - children, - ...assetGroup(children, assets) - }) - }); - } - }, - groupAssetsByInfo: (groupConfigs, context, options) => { - /** - * @param {string} name name - */ - const groupByAssetInfoFlag = name => { - groupConfigs.push({ - getKeys: asset => (asset.info && asset.info[name] ? ["1"] : undefined), - createGroup: (key, children, assets) => ({ - type: "assets by info", - info: { - [name]: Boolean(key) - }, - children, - ...assetGroup(children, assets) - }) - }); - }; - groupByAssetInfoFlag("immutable"); - groupByAssetInfoFlag("development"); - groupByAssetInfoFlag("hotModuleReplacement"); - }, - groupAssetsByChunk: (groupConfigs, context, options) => { - /** - * @param {keyof KnownStatsAsset} name name - */ - const groupByNames = name => { - groupConfigs.push({ - getKeys: asset => /** @type {string[]} */ (asset[name]), - createGroup: (key, children, assets) => ({ - type: "assets by chunk", - [name]: [key], - children, - ...assetGroup(children, assets) - }) - }); - }; - groupByNames("chunkNames"); - groupByNames("auxiliaryChunkNames"); - groupByNames("chunkIdHints"); - groupByNames("auxiliaryChunkIdHints"); - }, - excludeAssets: (groupConfigs, context, { excludeAssets }) => { - groupConfigs.push({ - getKeys: asset => { - const ident = asset.name; - const excluded = excludeAssets.some(fn => fn(ident, asset)); - if (excluded) return ["excluded"]; - }, - getOptions: () => ({ - groupChildren: false, - force: true - }), - createGroup: (key, children, assets) => ({ - type: "hidden assets", - filteredChildren: assets.length, - ...assetGroup(children, assets) - }) - }); - } -}; - -/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} ModulesGroupers */ - -/** @type {function("module" | "chunk" | "root-of-chunk" | "nested"): ModulesGroupers} */ -const MODULES_GROUPERS = type => ({ - _: (groupConfigs, context, options) => { - /** - * @param {keyof KnownStatsModule} name name - * @param {string} type type - * @param {boolean=} exclude need exclude? - */ - const groupByFlag = (name, type, exclude) => { - groupConfigs.push({ - getKeys: module => (module[name] ? ["1"] : undefined), - getOptions: () => ({ - groupChildren: !exclude, - force: exclude - }), - createGroup: (key, children, modules) => ({ - type, - [name]: Boolean(key), - ...(exclude ? { filteredChildren: modules.length } : { children }), - ...moduleGroup(children, modules) - }) - }); - }; - const { - groupModulesByCacheStatus, - groupModulesByLayer, - groupModulesByAttributes, - groupModulesByType, - groupModulesByPath, - groupModulesByExtension - } = options; - if (groupModulesByAttributes) { - groupByFlag("errors", "modules with errors"); - groupByFlag("warnings", "modules with warnings"); - groupByFlag("assets", "modules with assets"); - groupByFlag("optional", "optional modules"); - } - if (groupModulesByCacheStatus) { - groupByFlag("cacheable", "cacheable modules"); - groupByFlag("built", "built modules"); - groupByFlag("codeGenerated", "code generated modules"); - } - if (groupModulesByCacheStatus || !options.cachedModules) { - groupByFlag("cached", "cached modules", !options.cachedModules); - } - if (groupModulesByAttributes || !options.orphanModules) { - groupByFlag("orphan", "orphan modules", !options.orphanModules); - } - if (groupModulesByAttributes || !options.dependentModules) { - groupByFlag("dependent", "dependent modules", !options.dependentModules); - } - if (groupModulesByType || !options.runtimeModules) { - groupConfigs.push({ - getKeys: module => { - if (!module.moduleType) return; - if (groupModulesByType) { - return [module.moduleType.split("/", 1)[0]]; - } else if (module.moduleType === WEBPACK_MODULE_TYPE_RUNTIME) { - return [WEBPACK_MODULE_TYPE_RUNTIME]; - } - }, - getOptions: key => { - const exclude = - key === WEBPACK_MODULE_TYPE_RUNTIME && !options.runtimeModules; - return { - groupChildren: !exclude, - force: exclude - }; - }, - createGroup: (key, children, modules) => { - const exclude = - key === WEBPACK_MODULE_TYPE_RUNTIME && !options.runtimeModules; - return { - type: `${key} modules`, - moduleType: key, - ...(exclude ? { filteredChildren: modules.length } : { children }), - ...moduleGroup(children, modules) - }; - } - }); - } - if (groupModulesByLayer) { - groupConfigs.push({ - getKeys: module => /** @type {string[]} */ ([module.layer]), - createGroup: (key, children, modules) => ({ - type: "modules by layer", - layer: key, - children, - ...moduleGroup(children, modules) - }) - }); - } - if (groupModulesByPath || groupModulesByExtension) { - groupConfigs.push({ - getKeys: module => { - if (!module.name) return; - const resource = parseResource( - /** @type {string} */ (module.name.split("!").pop()) - ).path; - const dataUrl = /^data:[^,;]+/.exec(resource); - if (dataUrl) return [dataUrl[0]]; - const extensionMatch = - groupModulesByExtension && GROUP_EXTENSION_REGEXP.exec(resource); - const extension = extensionMatch ? extensionMatch[1] : ""; - const pathMatch = - groupModulesByPath && GROUP_PATH_REGEXP.exec(resource); - const path = pathMatch ? pathMatch[1].split(/[/\\]/) : []; - const keys = []; - if (groupModulesByPath) { - if (extension) - keys.push( - path.length - ? `${path.join("/")}/*${extension}` - : `*${extension}` - ); - while (path.length > 0) { - keys.push(`${path.join("/")}/`); - path.pop(); - } - } else if (extension) { - keys.push(`*${extension}`); - } - return keys; - }, - createGroup: (key, children, modules) => { - const isDataUrl = key.startsWith("data:"); - return { - type: isDataUrl - ? "modules by mime type" - : groupModulesByPath - ? "modules by path" - : "modules by extension", - name: isDataUrl ? key.slice(/* 'data:'.length */ 5) : key, - children, - ...moduleGroup(children, modules) - }; - } - }); - } - }, - excludeModules: (groupConfigs, context, { excludeModules }) => { - groupConfigs.push({ - getKeys: module => { - const name = module.name; - if (name) { - const excluded = excludeModules.some(fn => fn(name, module, type)); - if (excluded) return ["1"]; - } - }, - getOptions: () => ({ - groupChildren: false, - force: true - }), - createGroup: (key, children, modules) => ({ - type: "hidden modules", - filteredChildren: children.length, - ...moduleGroup(children, modules) - }) - }); - } -}); - -/** @typedef {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} ModuleReasonsGroupers */ - -/** @type {ModuleReasonsGroupers} */ -const MODULE_REASONS_GROUPERS = { - groupReasonsByOrigin: groupConfigs => { - groupConfigs.push({ - getKeys: reason => /** @type {string[]} */ ([reason.module]), - createGroup: (key, children, reasons) => ({ - type: "from origin", - module: key, - children, - ...reasonGroup(children, reasons) - }) - }); - } -}; - -/** @type {Record} */ -const RESULT_GROUPERS = { - "compilation.assets": ASSETS_GROUPERS, - "asset.related": ASSETS_GROUPERS, - "compilation.modules": MODULES_GROUPERS("module"), - "chunk.modules": MODULES_GROUPERS("chunk"), - "chunk.rootModules": MODULES_GROUPERS("root-of-chunk"), - "module.modules": MODULES_GROUPERS("nested"), - "module.reasons": MODULE_REASONS_GROUPERS -}; - -// remove a prefixed "!" that can be specified to reverse sort order -/** - * @param {string} field a field name - * @returns {field} normalized field - */ -const normalizeFieldKey = field => { - if (field[0] === "!") { - return field.slice(1); - } - return field; -}; - -// if a field is prefixed by a "!" reverse sort order -/** - * @param {string} field a field name - * @returns {boolean} result - */ -const sortOrderRegular = field => { - if (field[0] === "!") { - return false; - } - return true; -}; - -/** - * @template T - * @param {string} field field name - * @returns {function(T, T): 0 | 1 | -1} comparators - */ -const sortByField = field => { - if (!field) { - /** - * @param {any} a first - * @param {any} b second - * @returns {-1|0|1} zero - */ - const noSort = (a, b) => 0; - return noSort; - } - - const fieldKey = normalizeFieldKey(field); - - let sortFn = compareSelect(m => m[fieldKey], compareIds); - - // if a field is prefixed with a "!" the sort is reversed! - const sortIsRegular = sortOrderRegular(field); - - if (!sortIsRegular) { - const oldSortFn = sortFn; - sortFn = (a, b) => oldSortFn(b, a); - } - - return sortFn; -}; - -/** @type {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>} */ -const ASSET_SORTERS = { - assetsSort: (comparators, context, { assetsSort }) => { - comparators.push(sortByField(assetsSort)); - }, - _: comparators => { - comparators.push(compareSelect(a => a.name, compareIds)); - } -}; - -/** @type {Record[], context: StatsFactoryContext, options: NormalizedStatsOptions) => void>>} */ -const RESULT_SORTERS = { - "compilation.chunks": { - chunksSort: (comparators, context, { chunksSort }) => { - comparators.push(sortByField(chunksSort)); - } - }, - "compilation.modules": { - modulesSort: (comparators, context, { modulesSort }) => { - comparators.push(sortByField(modulesSort)); - } - }, - "chunk.modules": { - chunkModulesSort: (comparators, context, { chunkModulesSort }) => { - comparators.push(sortByField(chunkModulesSort)); - } - }, - "module.modules": { - nestedModulesSort: (comparators, context, { nestedModulesSort }) => { - comparators.push(sortByField(nestedModulesSort)); - } - }, - "compilation.assets": ASSET_SORTERS, - "asset.related": ASSET_SORTERS -}; - -/** - * @param {Record>} config the config see above - * @param {NormalizedStatsOptions} options stats options - * @param {function(string, Function): void} fn handler function called for every active line in config - * @returns {void} - */ -const iterateConfig = (config, options, fn) => { - for (const hookFor of Object.keys(config)) { - const subConfig = config[hookFor]; - for (const option of Object.keys(subConfig)) { - if (option !== "_") { - if (option.startsWith("!")) { - if (options[option.slice(1)]) continue; - } else { - const value = options[option]; - if ( - value === false || - value === undefined || - (Array.isArray(value) && value.length === 0) - ) - continue; - } - } - fn(hookFor, subConfig[option]); - } - } -}; - -/** @type {Record} */ -const ITEM_NAMES = { - "compilation.children[]": "compilation", - "compilation.modules[]": "module", - "compilation.entrypoints[]": "chunkGroup", - "compilation.namedChunkGroups[]": "chunkGroup", - "compilation.errors[]": "error", - "compilation.warnings[]": "warning", - "chunk.modules[]": "module", - "chunk.rootModules[]": "module", - "chunk.origins[]": "chunkOrigin", - "compilation.chunks[]": "chunk", - "compilation.assets[]": "asset", - "asset.related[]": "asset", - "module.issuerPath[]": "moduleIssuer", - "module.reasons[]": "moduleReason", - "module.modules[]": "module", - "module.children[]": "module", - "moduleTrace[]": "moduleTraceItem", - "moduleTraceItem.dependencies[]": "moduleTraceDependency" -}; - -/** - * @template T - * @typedef {{ name: T }} NamedObject - */ - -/** - * @template {{ name: string }} T - * @param {T[]} items items to be merged - * @returns {NamedObject} an object - */ -const mergeToObject = items => { - const obj = Object.create(null); - for (const item of items) { - obj[item.name] = item; - } - return obj; -}; - -/** - * @template {{ name: string }} T - * @type {Record NamedObject>} - */ -const MERGER = { - "compilation.entrypoints": mergeToObject, - "compilation.namedChunkGroups": mergeToObject -}; - -class DefaultStatsFactoryPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => { - compilation.hooks.statsFactory.tap( - "DefaultStatsFactoryPlugin", - /** - * @param {StatsFactory} stats stats factory - * @param {NormalizedStatsOptions} options stats options - */ - (stats, options) => { - iterateConfig(SIMPLE_EXTRACTORS, options, (hookFor, fn) => { - stats.hooks.extract - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (obj, data, ctx) => - fn(obj, data, ctx, options, stats) - ); - }); - iterateConfig(FILTER, options, (hookFor, fn) => { - stats.hooks.filter - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) => - fn(item, ctx, options, idx, i) - ); - }); - iterateConfig(FILTER_RESULTS, options, (hookFor, fn) => { - stats.hooks.filterResults - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (item, ctx, idx, i) => - fn(item, ctx, options, idx, i) - ); - }); - iterateConfig(SORTERS, options, (hookFor, fn) => { - stats.hooks.sort - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (comparators, ctx) => - fn(comparators, ctx, options) - ); - }); - iterateConfig(RESULT_SORTERS, options, (hookFor, fn) => { - stats.hooks.sortResults - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (comparators, ctx) => - fn(comparators, ctx, options) - ); - }); - iterateConfig(RESULT_GROUPERS, options, (hookFor, fn) => { - stats.hooks.groupResults - .for(hookFor) - .tap("DefaultStatsFactoryPlugin", (groupConfigs, ctx) => - fn(groupConfigs, ctx, options) - ); - }); - for (const key of Object.keys(ITEM_NAMES)) { - const itemName = ITEM_NAMES[key]; - stats.hooks.getItemName - .for(key) - .tap("DefaultStatsFactoryPlugin", () => itemName); - } - for (const key of Object.keys(MERGER)) { - const merger = MERGER[key]; - stats.hooks.merge.for(key).tap("DefaultStatsFactoryPlugin", merger); - } - if (options.children) { - if (Array.isArray(options.children)) { - stats.hooks.getItemFactory - .for("compilation.children[].compilation") - .tap( - "DefaultStatsFactoryPlugin", - /** - * @param {Compilation} comp compilation - * @param {StatsFactoryContext} options options - * @returns {StatsFactory | undefined} stats factory - */ - (comp, { _index: idx }) => { - const children = - /** @type {TODO} */ - (options.children); - if (idx < children.length) { - return compilation.createStatsFactory( - compilation.createStatsOptions(children[idx]) - ); - } - } - ); - } else if (options.children !== true) { - const childFactory = compilation.createStatsFactory( - compilation.createStatsOptions(options.children) - ); - stats.hooks.getItemFactory - .for("compilation.children[].compilation") - .tap("DefaultStatsFactoryPlugin", () => childFactory); - } - } - } - ); - }); - } -} -module.exports = DefaultStatsFactoryPlugin; diff --git a/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js b/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js deleted file mode 100644 index 70e56b8cb3e..00000000000 --- a/webpack-lib/lib/stats/DefaultStatsPresetPlugin.js +++ /dev/null @@ -1,386 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RequestShortener = require("../RequestShortener"); - -/** @typedef {import("../../declarations/WebpackOptions").StatsOptions} StatsOptions */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").CreateStatsOptionsContext} CreateStatsOptionsContext */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */ - -/** - * @param {StatsOptions} options options - * @param {StatsOptions} defaults default options - */ -const applyDefaults = (options, defaults) => { - for (const _k of Object.keys(defaults)) { - const key = /** @type {keyof StatsOptions} */ (_k); - if (typeof options[key] === "undefined") { - /** @type {TODO} */ - (options)[key] = defaults[key]; - } - } -}; - -/** @typedef {Record} NamedPresets */ -/** @type {NamedPresets} */ -const NAMED_PRESETS = { - verbose: { - hash: true, - builtAt: true, - relatedAssets: true, - entrypoints: true, - chunkGroups: true, - ids: true, - modules: false, - chunks: true, - chunkRelations: true, - chunkModules: true, - dependentModules: true, - chunkOrigins: true, - depth: true, - env: true, - reasons: true, - usedExports: true, - providedExports: true, - optimizationBailout: true, - errorDetails: true, - errorStack: true, - publicPath: true, - logging: "verbose", - orphanModules: true, - runtimeModules: true, - exclude: false, - errorsSpace: Infinity, - warningsSpace: Infinity, - modulesSpace: Infinity, - chunkModulesSpace: Infinity, - assetsSpace: Infinity, - reasonsSpace: Infinity, - children: true - }, - detailed: { - hash: true, - builtAt: true, - relatedAssets: true, - entrypoints: true, - chunkGroups: true, - ids: true, - chunks: true, - chunkRelations: true, - chunkModules: false, - chunkOrigins: true, - depth: true, - usedExports: true, - providedExports: true, - optimizationBailout: true, - errorDetails: true, - publicPath: true, - logging: true, - runtimeModules: true, - exclude: false, - errorsSpace: 1000, - warningsSpace: 1000, - modulesSpace: 1000, - assetsSpace: 1000, - reasonsSpace: 1000 - }, - minimal: { - all: false, - version: true, - timings: true, - modules: true, - errorsSpace: 0, - warningsSpace: 0, - modulesSpace: 0, - assets: true, - assetsSpace: 0, - errors: true, - errorsCount: true, - warnings: true, - warningsCount: true, - logging: "warn" - }, - "errors-only": { - all: false, - errors: true, - errorsCount: true, - errorsSpace: Infinity, - moduleTrace: true, - logging: "error" - }, - "errors-warnings": { - all: false, - errors: true, - errorsCount: true, - errorsSpace: Infinity, - warnings: true, - warningsCount: true, - warningsSpace: Infinity, - logging: "warn" - }, - summary: { - all: false, - version: true, - errorsCount: true, - warningsCount: true - }, - none: { - all: false - } -}; - -/** - * @param {StatsOptions} all stats option - * @returns {boolean} true when enabled, otherwise false - */ -const NORMAL_ON = ({ all }) => all !== false; -/** - * @param {StatsOptions} all stats option - * @returns {boolean} true when enabled, otherwise false - */ -const NORMAL_OFF = ({ all }) => all === true; -/** - * @param {StatsOptions} all stats option - * @param {CreateStatsOptionsContext} forToString stats options context - * @returns {boolean} true when enabled, otherwise false - */ -const ON_FOR_TO_STRING = ({ all }, { forToString }) => - forToString ? all !== false : all === true; -/** - * @param {StatsOptions} all stats option - * @param {CreateStatsOptionsContext} forToString stats options context - * @returns {boolean} true when enabled, otherwise false - */ -const OFF_FOR_TO_STRING = ({ all }, { forToString }) => - forToString ? all === true : all !== false; -/** - * @param {StatsOptions} all stats option - * @param {CreateStatsOptionsContext} forToString stats options context - * @returns {boolean | "auto"} true when enabled, otherwise false - */ -const AUTO_FOR_TO_STRING = ({ all }, { forToString }) => { - if (all === false) return false; - if (all === true) return true; - if (forToString) return "auto"; - return true; -}; - -/** @typedef {Record StatsOptions[keyof StatsOptions] | RequestShortener>} Defaults */ - -/** @type {Defaults} */ -const DEFAULTS = { - context: (options, context, compilation) => compilation.compiler.context, - requestShortener: (options, context, compilation) => - compilation.compiler.context === options.context - ? compilation.requestShortener - : new RequestShortener( - /** @type {string} */ - (options.context), - compilation.compiler.root - ), - performance: NORMAL_ON, - hash: OFF_FOR_TO_STRING, - env: NORMAL_OFF, - version: NORMAL_ON, - timings: NORMAL_ON, - builtAt: OFF_FOR_TO_STRING, - assets: NORMAL_ON, - entrypoints: AUTO_FOR_TO_STRING, - chunkGroups: OFF_FOR_TO_STRING, - chunkGroupAuxiliary: OFF_FOR_TO_STRING, - chunkGroupChildren: OFF_FOR_TO_STRING, - chunkGroupMaxAssets: (o, { forToString }) => (forToString ? 5 : Infinity), - chunks: OFF_FOR_TO_STRING, - chunkRelations: OFF_FOR_TO_STRING, - chunkModules: ({ all, modules }) => { - if (all === false) return false; - if (all === true) return true; - if (modules) return false; - return true; - }, - dependentModules: OFF_FOR_TO_STRING, - chunkOrigins: OFF_FOR_TO_STRING, - ids: OFF_FOR_TO_STRING, - modules: ({ all, chunks, chunkModules }, { forToString }) => { - if (all === false) return false; - if (all === true) return true; - if (forToString && chunks && chunkModules) return false; - return true; - }, - nestedModules: OFF_FOR_TO_STRING, - groupModulesByType: ON_FOR_TO_STRING, - groupModulesByCacheStatus: ON_FOR_TO_STRING, - groupModulesByLayer: ON_FOR_TO_STRING, - groupModulesByAttributes: ON_FOR_TO_STRING, - groupModulesByPath: ON_FOR_TO_STRING, - groupModulesByExtension: ON_FOR_TO_STRING, - modulesSpace: (o, { forToString }) => (forToString ? 15 : Infinity), - chunkModulesSpace: (o, { forToString }) => (forToString ? 10 : Infinity), - nestedModulesSpace: (o, { forToString }) => (forToString ? 10 : Infinity), - relatedAssets: OFF_FOR_TO_STRING, - groupAssetsByEmitStatus: ON_FOR_TO_STRING, - groupAssetsByInfo: ON_FOR_TO_STRING, - groupAssetsByPath: ON_FOR_TO_STRING, - groupAssetsByExtension: ON_FOR_TO_STRING, - groupAssetsByChunk: ON_FOR_TO_STRING, - assetsSpace: (o, { forToString }) => (forToString ? 15 : Infinity), - orphanModules: OFF_FOR_TO_STRING, - runtimeModules: ({ all, runtime }, { forToString }) => - runtime !== undefined - ? runtime - : forToString - ? all === true - : all !== false, - cachedModules: ({ all, cached }, { forToString }) => - cached !== undefined ? cached : forToString ? all === true : all !== false, - moduleAssets: OFF_FOR_TO_STRING, - depth: OFF_FOR_TO_STRING, - cachedAssets: OFF_FOR_TO_STRING, - reasons: OFF_FOR_TO_STRING, - reasonsSpace: (o, { forToString }) => (forToString ? 15 : Infinity), - groupReasonsByOrigin: ON_FOR_TO_STRING, - usedExports: OFF_FOR_TO_STRING, - providedExports: OFF_FOR_TO_STRING, - optimizationBailout: OFF_FOR_TO_STRING, - children: OFF_FOR_TO_STRING, - source: NORMAL_OFF, - moduleTrace: NORMAL_ON, - errors: NORMAL_ON, - errorsCount: NORMAL_ON, - errorDetails: AUTO_FOR_TO_STRING, - errorStack: OFF_FOR_TO_STRING, - warnings: NORMAL_ON, - warningsCount: NORMAL_ON, - publicPath: OFF_FOR_TO_STRING, - logging: ({ all }, { forToString }) => - forToString && all !== false ? "info" : false, - loggingDebug: () => [], - loggingTrace: OFF_FOR_TO_STRING, - excludeModules: () => [], - excludeAssets: () => [], - modulesSort: () => "depth", - chunkModulesSort: () => "name", - nestedModulesSort: () => false, - chunksSort: () => false, - assetsSort: () => "!size", - outputPath: OFF_FOR_TO_STRING, - colors: () => false -}; - -/** - * @param {string | ({ test: function(string): boolean }) | (function(string): boolean) | boolean} item item to normalize - * @returns {(function(string): boolean) | undefined} normalize fn - */ -const normalizeFilter = item => { - if (typeof item === "string") { - const regExp = new RegExp( - `[\\\\/]${item.replace(/[-[\]{}()*+?.\\^$|]/g, "\\$&")}([\\\\/]|$|!|\\?)` - ); - 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; - } -}; - -/** @type {Record} */ -const NORMALIZER = { - excludeModules: value => { - if (!Array.isArray(value)) { - value = value ? [value] : []; - } - return value.map(normalizeFilter); - }, - excludeAssets: value => { - if (!Array.isArray(value)) { - value = value ? [value] : []; - } - return value.map(normalizeFilter); - }, - warningsFilter: value => { - if (!Array.isArray(value)) { - value = value ? [value] : []; - } - /** - * @callback WarningFilterFn - * @param {StatsError} warning warning - * @param {string} warningString warning string - * @returns {boolean} result - */ - return value.map( - /** - * @param {StatsOptions["warningsFilter"]} filter a warning filter - * @returns {WarningFilterFn} result - */ - filter => { - if (typeof filter === "string") { - return (warning, warningString) => warningString.includes(filter); - } - if (filter instanceof RegExp) { - return (warning, warningString) => filter.test(warningString); - } - if (typeof filter === "function") { - return filter; - } - throw new Error( - `Can only filter warnings with Strings or RegExps. (Given: ${filter})` - ); - } - ); - }, - logging: value => { - if (value === true) value = "log"; - return value; - }, - loggingDebug: value => { - if (!Array.isArray(value)) { - value = value ? [value] : []; - } - return value.map(normalizeFilter); - } -}; - -class DefaultStatsPresetPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("DefaultStatsPresetPlugin", compilation => { - for (const key of Object.keys(NAMED_PRESETS)) { - const defaults = NAMED_PRESETS[/** @type {keyof NamedPresets} */ (key)]; - compilation.hooks.statsPreset - .for(key) - .tap("DefaultStatsPresetPlugin", (options, context) => { - applyDefaults(options, defaults); - }); - } - compilation.hooks.statsNormalize.tap( - "DefaultStatsPresetPlugin", - (options, context) => { - for (const key of Object.keys(DEFAULTS)) { - if (options[key] === undefined) - options[key] = DEFAULTS[key](options, context, compilation); - } - for (const key of Object.keys(NORMALIZER)) { - options[key] = NORMALIZER[key](options[key]); - } - } - ); - }); - } -} -module.exports = DefaultStatsPresetPlugin; diff --git a/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js b/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js deleted file mode 100644 index 419311a72a5..00000000000 --- a/webpack-lib/lib/stats/DefaultStatsPrinterPlugin.js +++ /dev/null @@ -1,1695 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("./DefaultStatsFactoryPlugin").KnownStatsChunkGroup} KnownStatsChunkGroup */ -/** @typedef {import("./StatsPrinter")} StatsPrinter */ -/** @typedef {import("./StatsPrinter").KnownStatsPrinterColorFn} KnownStatsPrinterColorFn */ -/** @typedef {import("./StatsPrinter").KnownStatsPrinterFormaters} KnownStatsPrinterFormaters */ -/** @typedef {import("./StatsPrinter").StatsPrinterContext} StatsPrinterContext */ - -const DATA_URI_CONTENT_LENGTH = 16; -const MAX_MODULE_IDENTIFIER_LENGTH = 80; - -/** - * @param {number} n a number - * @param {string} singular singular - * @param {string} plural plural - * @returns {string} if n is 1, singular, else plural - */ -const plural = (n, singular, plural) => (n === 1 ? singular : plural); - -/** - * @param {Record} sizes sizes by source type - * @param {StatsPrinterContext} options options - * @returns {string | undefined} text - */ -const printSizes = (sizes, { formatSize = n => `${n}` }) => { - const keys = Object.keys(sizes); - if (keys.length > 1) { - return keys.map(key => `${formatSize(sizes[key])} (${key})`).join(" "); - } else if (keys.length === 1) { - return formatSize(sizes[keys[0]]); - } -}; - -/** - * @param {string} resource resource - * @returns {string} resource name for display - */ -const getResourceName = resource => { - const dataUrl = /^data:[^,]+,/.exec(resource); - if (!dataUrl) return resource; - - const len = dataUrl[0].length + DATA_URI_CONTENT_LENGTH; - if (resource.length < len) return resource; - return `${resource.slice( - 0, - Math.min(resource.length - /* '..'.length */ 2, len) - )}..`; -}; - -/** - * @param {string} name module name - * @returns {[string,string]} prefix and module name - */ -const getModuleName = name => { - const [, prefix, resource] = - /** @type {[any, string, string]} */ - (/** @type {unknown} */ (/^(.*!)?([^!]*)$/.exec(name))); - - if (resource.length > MAX_MODULE_IDENTIFIER_LENGTH) { - const truncatedResource = `${resource.slice( - 0, - Math.min( - resource.length - /* '...(truncated)'.length */ 14, - MAX_MODULE_IDENTIFIER_LENGTH - ) - )}...(truncated)`; - - return [prefix, getResourceName(truncatedResource)]; - } - - return [prefix, getResourceName(resource)]; -}; - -/** - * @param {string} str string - * @param {function(string): string} fn function to apply to each line - * @returns {string} joined string - */ -const mapLines = (str, fn) => str.split("\n").map(fn).join("\n"); - -/** - * @param {number} n a number - * @returns {string} number as two digit string, leading 0 - */ -const twoDigit = n => (n >= 10 ? `${n}` : `0${n}`); - -/** - * @param {string | number} id an id - * @returns {boolean | string} is i - */ -const isValidId = id => typeof id === "number" || id; - -/** - * @template T - * @param {Array | undefined} list of items - * @param {number} count number of items to show - * @returns {string} string representation of list - */ -const moreCount = (list, count) => - list && list.length > 0 ? `+ ${count}` : `${count}`; - -/** - * @template T - * @template {keyof T} K - * @typedef {{ [P in K]-?: T[P] }} WithRequired - */ - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const COMPILATION_SIMPLE_PRINTERS = { - "compilation.summary!": ( - _, - { - type, - bold, - green, - red, - yellow, - formatDateTime, - formatTime, - compilation: { - name, - hash, - version, - time, - builtAt, - errorsCount, - warningsCount - } - } - ) => { - const root = type === "compilation.summary!"; - const warningsMessage = - /** @type {number} */ (warningsCount) > 0 - ? yellow( - `${warningsCount} ${plural(/** @type {number} */ (warningsCount), "warning", "warnings")}` - ) - : ""; - const errorsMessage = - /** @type {number} */ (errorsCount) > 0 - ? red( - `${errorsCount} ${plural(/** @type {number} */ (errorsCount), "error", "errors")}` - ) - : ""; - const timeMessage = root && time ? ` in ${formatTime(time)}` : ""; - const hashMessage = hash ? ` (${hash})` : ""; - const builtAtMessage = - root && builtAt ? `${formatDateTime(builtAt)}: ` : ""; - const versionMessage = root && version ? `webpack ${version}` : ""; - const nameMessage = - root && name - ? bold(name) - : name - ? `Child ${bold(name)}` - : root - ? "" - : "Child"; - const subjectMessage = - nameMessage && versionMessage - ? `${nameMessage} (${versionMessage})` - : versionMessage || nameMessage || "webpack"; - let statusMessage; - if (errorsMessage && warningsMessage) { - statusMessage = `compiled with ${errorsMessage} and ${warningsMessage}`; - } else if (errorsMessage) { - statusMessage = `compiled with ${errorsMessage}`; - } else if (warningsMessage) { - statusMessage = `compiled with ${warningsMessage}`; - } else if (errorsCount === 0 && warningsCount === 0) { - statusMessage = `compiled ${green("successfully")}`; - } else { - statusMessage = "compiled"; - } - if ( - builtAtMessage || - versionMessage || - errorsMessage || - warningsMessage || - (errorsCount === 0 && warningsCount === 0) || - timeMessage || - hashMessage - ) - return `${builtAtMessage}${subjectMessage} ${statusMessage}${timeMessage}${hashMessage}`; - }, - "compilation.filteredWarningDetailsCount": count => - count - ? `${count} ${plural( - count, - "warning has", - "warnings have" - )} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.` - : undefined, - "compilation.filteredErrorDetailsCount": (count, { yellow }) => - count - ? yellow( - `${count} ${plural( - count, - "error has", - "errors have" - )} detailed information that is not shown.\nUse 'stats.errorDetails: true' resp. '--stats-error-details' to show it.` - ) - : undefined, - "compilation.env": (env, { bold }) => - env - ? `Environment (--env): ${bold(JSON.stringify(env, null, 2))}` - : undefined, - "compilation.publicPath": (publicPath, { bold }) => - `PublicPath: ${bold(publicPath || "(none)")}`, - "compilation.entrypoints": (entrypoints, context, printer) => - Array.isArray(entrypoints) - ? undefined - : printer.print(context.type, Object.values(entrypoints), { - ...context, - chunkGroupKind: "Entrypoint" - }), - "compilation.namedChunkGroups": (namedChunkGroups, context, printer) => { - if (!Array.isArray(namedChunkGroups)) { - const { - compilation: { entrypoints } - } = context; - let chunkGroups = Object.values(namedChunkGroups); - if (entrypoints) { - chunkGroups = chunkGroups.filter( - group => - !Object.prototype.hasOwnProperty.call(entrypoints, group.name) - ); - } - return printer.print(context.type, chunkGroups, { - ...context, - chunkGroupKind: "Chunk Group" - }); - } - }, - "compilation.assetsByChunkName": () => "", - - "compilation.filteredModules": ( - filteredModules, - { compilation: { modules } } - ) => - filteredModules > 0 - ? `${moreCount(modules, filteredModules)} ${plural( - filteredModules, - "module", - "modules" - )}` - : undefined, - "compilation.filteredAssets": ( - filteredAssets, - { compilation: { assets } } - ) => - filteredAssets > 0 - ? `${moreCount(assets, filteredAssets)} ${plural( - filteredAssets, - "asset", - "assets" - )}` - : undefined, - "compilation.logging": (logging, context, printer) => - Array.isArray(logging) - ? undefined - : printer.print( - context.type, - Object.entries(logging).map(([name, value]) => ({ ...value, name })), - context - ), - "compilation.warningsInChildren!": (_, { yellow, compilation }) => { - if ( - !compilation.children && - /** @type {number} */ (compilation.warningsCount) > 0 && - compilation.warnings - ) { - const childWarnings = - /** @type {number} */ (compilation.warningsCount) - - compilation.warnings.length; - if (childWarnings > 0) { - return yellow( - `${childWarnings} ${plural( - childWarnings, - "WARNING", - "WARNINGS" - )} in child compilations${ - compilation.children - ? "" - : " (Use 'stats.children: true' resp. '--stats-children' for more details)" - }` - ); - } - } - }, - "compilation.errorsInChildren!": (_, { red, compilation }) => { - if ( - !compilation.children && - /** @type {number} */ (compilation.errorsCount) > 0 && - compilation.errors - ) { - const childErrors = - /** @type {number} */ (compilation.errorsCount) - - compilation.errors.length; - if (childErrors > 0) { - return red( - `${childErrors} ${plural( - childErrors, - "ERROR", - "ERRORS" - )} in child compilations${ - compilation.children - ? "" - : " (Use 'stats.children: true' resp. '--stats-children' for more details)" - }` - ); - } - } - } -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const ASSET_SIMPLE_PRINTERS = { - "asset.type": type => type, - "asset.name": (name, { formatFilename, asset: { isOverSizeLimit } }) => - formatFilename(name, isOverSizeLimit), - "asset.size": (size, { asset: { isOverSizeLimit }, yellow, formatSize }) => - isOverSizeLimit ? yellow(formatSize(size)) : formatSize(size), - "asset.emitted": (emitted, { green, formatFlag }) => - emitted ? green(formatFlag("emitted")) : undefined, - "asset.comparedForEmit": (comparedForEmit, { yellow, formatFlag }) => - comparedForEmit ? yellow(formatFlag("compared for emit")) : undefined, - "asset.cached": (cached, { green, formatFlag }) => - cached ? green(formatFlag("cached")) : undefined, - "asset.isOverSizeLimit": (isOverSizeLimit, { yellow, formatFlag }) => - isOverSizeLimit ? yellow(formatFlag("big")) : undefined, - - "asset.info.immutable": (immutable, { green, formatFlag }) => - immutable ? green(formatFlag("immutable")) : undefined, - "asset.info.javascriptModule": (javascriptModule, { formatFlag }) => - javascriptModule ? formatFlag("javascript module") : undefined, - "asset.info.sourceFilename": (sourceFilename, { formatFlag }) => - sourceFilename - ? formatFlag( - sourceFilename === true - ? "from source file" - : `from: ${sourceFilename}` - ) - : undefined, - "asset.info.development": (development, { green, formatFlag }) => - development ? green(formatFlag("dev")) : undefined, - "asset.info.hotModuleReplacement": ( - hotModuleReplacement, - { green, formatFlag } - ) => (hotModuleReplacement ? green(formatFlag("hmr")) : undefined), - "asset.separator!": () => "\n", - "asset.filteredRelated": (filteredRelated, { asset: { related } }) => - filteredRelated > 0 - ? `${moreCount(related, filteredRelated)} related ${plural( - filteredRelated, - "asset", - "assets" - )}` - : undefined, - "asset.filteredChildren": (filteredChildren, { asset: { children } }) => - filteredChildren > 0 - ? `${moreCount(children, filteredChildren)} ${plural( - filteredChildren, - "asset", - "assets" - )}` - : undefined, - - assetChunk: (id, { formatChunkId }) => formatChunkId(id), - - assetChunkName: name => name, - assetChunkIdHint: name => name -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_SIMPLE_PRINTERS = { - "module.type": type => (type !== "module" ? type : undefined), - "module.id": (id, { formatModuleId }) => - isValidId(id) ? formatModuleId(id) : undefined, - "module.name": (name, { bold }) => { - const [prefix, resource] = getModuleName(name); - return `${prefix || ""}${bold(resource || "")}`; - }, - "module.identifier": identifier => undefined, - "module.layer": (layer, { formatLayer }) => - layer ? formatLayer(layer) : undefined, - "module.sizes": printSizes, - "module.chunks[]": (id, { formatChunkId }) => formatChunkId(id), - "module.depth": (depth, { formatFlag }) => - depth !== null ? formatFlag(`depth ${depth}`) : undefined, - "module.cacheable": (cacheable, { formatFlag, red }) => - cacheable === false ? red(formatFlag("not cacheable")) : undefined, - "module.orphan": (orphan, { formatFlag, yellow }) => - orphan ? yellow(formatFlag("orphan")) : undefined, - "module.runtime": (runtime, { formatFlag, yellow }) => - runtime ? yellow(formatFlag("runtime")) : undefined, - "module.optional": (optional, { formatFlag, yellow }) => - optional ? yellow(formatFlag("optional")) : undefined, - "module.dependent": (dependent, { formatFlag, cyan }) => - dependent ? cyan(formatFlag("dependent")) : undefined, - "module.built": (built, { formatFlag, yellow }) => - built ? yellow(formatFlag("built")) : undefined, - "module.codeGenerated": (codeGenerated, { formatFlag, yellow }) => - codeGenerated ? yellow(formatFlag("code generated")) : undefined, - "module.buildTimeExecuted": (buildTimeExecuted, { formatFlag, green }) => - buildTimeExecuted ? green(formatFlag("build time executed")) : undefined, - "module.cached": (cached, { formatFlag, green }) => - cached ? green(formatFlag("cached")) : undefined, - "module.assets": (assets, { formatFlag, magenta }) => - assets && assets.length - ? magenta( - formatFlag( - `${assets.length} ${plural(assets.length, "asset", "assets")}` - ) - ) - : undefined, - "module.warnings": (warnings, { formatFlag, yellow }) => - warnings === true - ? yellow(formatFlag("warnings")) - : warnings - ? yellow( - formatFlag(`${warnings} ${plural(warnings, "warning", "warnings")}`) - ) - : undefined, - "module.errors": (errors, { formatFlag, red }) => - errors === true - ? red(formatFlag("errors")) - : errors - ? red(formatFlag(`${errors} ${plural(errors, "error", "errors")}`)) - : undefined, - "module.providedExports": (providedExports, { formatFlag, cyan }) => { - if (Array.isArray(providedExports)) { - if (providedExports.length === 0) return cyan(formatFlag("no exports")); - return cyan(formatFlag(`exports: ${providedExports.join(", ")}`)); - } - }, - "module.usedExports": (usedExports, { formatFlag, cyan, module }) => { - if (usedExports !== true) { - if (usedExports === null) return cyan(formatFlag("used exports unknown")); - if (usedExports === false) return cyan(formatFlag("module unused")); - if (Array.isArray(usedExports)) { - if (usedExports.length === 0) - return cyan(formatFlag("no exports used")); - const providedExportsCount = Array.isArray(module.providedExports) - ? module.providedExports.length - : null; - if ( - providedExportsCount !== null && - providedExportsCount === usedExports.length - ) { - return cyan(formatFlag("all exports used")); - } - - return cyan( - formatFlag(`only some exports used: ${usedExports.join(", ")}`) - ); - } - } - }, - "module.optimizationBailout[]": (optimizationBailout, { yellow }) => - yellow(optimizationBailout), - "module.issuerPath": (issuerPath, { module }) => - module.profile ? undefined : "", - "module.profile": profile => undefined, - "module.filteredModules": (filteredModules, { module: { modules } }) => - filteredModules > 0 - ? `${moreCount(modules, filteredModules)} nested ${plural( - filteredModules, - "module", - "modules" - )}` - : undefined, - "module.filteredReasons": (filteredReasons, { module: { reasons } }) => - filteredReasons > 0 - ? `${moreCount(reasons, filteredReasons)} ${plural( - filteredReasons, - "reason", - "reasons" - )}` - : undefined, - "module.filteredChildren": (filteredChildren, { module: { children } }) => - filteredChildren > 0 - ? `${moreCount(children, filteredChildren)} ${plural( - filteredChildren, - "module", - "modules" - )}` - : undefined, - "module.separator!": () => "\n" -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_ISSUER_PRINTERS = { - "moduleIssuer.id": (id, { formatModuleId }) => formatModuleId(id), - "moduleIssuer.profile.total": (value, { formatTime }) => formatTime(value) -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_REASON_PRINTERS = { - "moduleReason.type": type => type, - "moduleReason.userRequest": (userRequest, { cyan }) => - cyan(getResourceName(userRequest)), - "moduleReason.moduleId": (moduleId, { formatModuleId }) => - isValidId(moduleId) ? formatModuleId(moduleId) : undefined, - "moduleReason.module": (module, { magenta }) => magenta(module), - "moduleReason.loc": loc => loc, - "moduleReason.explanation": (explanation, { cyan }) => cyan(explanation), - "moduleReason.active": (active, { formatFlag }) => - active ? undefined : formatFlag("inactive"), - "moduleReason.resolvedModule": (module, { magenta }) => magenta(module), - "moduleReason.filteredChildren": ( - filteredChildren, - { moduleReason: { children } } - ) => - filteredChildren > 0 - ? `${moreCount(children, filteredChildren)} ${plural( - filteredChildren, - "reason", - "reasons" - )}` - : undefined -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_PROFILE_PRINTERS = { - "module.profile.total": (value, { formatTime }) => formatTime(value), - "module.profile.resolving": (value, { formatTime }) => - `resolving: ${formatTime(value)}`, - "module.profile.restoring": (value, { formatTime }) => - `restoring: ${formatTime(value)}`, - "module.profile.integration": (value, { formatTime }) => - `integration: ${formatTime(value)}`, - "module.profile.building": (value, { formatTime }) => - `building: ${formatTime(value)}`, - "module.profile.storing": (value, { formatTime }) => - `storing: ${formatTime(value)}`, - "module.profile.additionalResolving": (value, { formatTime }) => - value ? `additional resolving: ${formatTime(value)}` : undefined, - "module.profile.additionalIntegration": (value, { formatTime }) => - value ? `additional integration: ${formatTime(value)}` : undefined -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const CHUNK_GROUP_PRINTERS = { - "chunkGroup.kind!": (_, { chunkGroupKind }) => chunkGroupKind, - "chunkGroup.separator!": () => "\n", - "chunkGroup.name": (name, { bold }) => bold(name), - "chunkGroup.isOverSizeLimit": (isOverSizeLimit, { formatFlag, yellow }) => - isOverSizeLimit ? yellow(formatFlag("big")) : undefined, - "chunkGroup.assetsSize": (size, { formatSize }) => - size ? formatSize(size) : undefined, - "chunkGroup.auxiliaryAssetsSize": (size, { formatSize }) => - size ? `(${formatSize(size)})` : undefined, - "chunkGroup.filteredAssets": (n, { chunkGroup: { assets } }) => - n > 0 - ? `${moreCount(assets, n)} ${plural(n, "asset", "assets")}` - : undefined, - "chunkGroup.filteredAuxiliaryAssets": ( - n, - { chunkGroup: { auxiliaryAssets } } - ) => - n > 0 - ? `${moreCount(auxiliaryAssets, n)} auxiliary ${plural( - n, - "asset", - "assets" - )}` - : undefined, - "chunkGroup.is!": () => "=", - "chunkGroupAsset.name": (asset, { green }) => green(asset), - "chunkGroupAsset.size": (size, { formatSize, chunkGroup }) => - chunkGroup.assets && - (chunkGroup.assets.length > 1 || - (chunkGroup.auxiliaryAssets && chunkGroup.auxiliaryAssets.length > 0) - ? formatSize(size) - : undefined), - "chunkGroup.children": (children, context, printer) => - Array.isArray(children) - ? undefined - : printer.print( - context.type, - Object.keys(children).map(key => ({ - type: key, - children: children[key] - })), - context - ), - "chunkGroupChildGroup.type": type => `${type}:`, - "chunkGroupChild.assets[]": (file, { formatFilename }) => - formatFilename(file), - "chunkGroupChild.chunks[]": (id, { formatChunkId }) => formatChunkId(id), - "chunkGroupChild.name": name => (name ? `(name: ${name})` : undefined) -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const CHUNK_PRINTERS = { - "chunk.id": (id, { formatChunkId }) => formatChunkId(id), - "chunk.files[]": (file, { formatFilename }) => formatFilename(file), - "chunk.names[]": name => name, - "chunk.idHints[]": name => name, - "chunk.runtime[]": name => name, - "chunk.sizes": (sizes, context) => printSizes(sizes, context), - "chunk.parents[]": (parents, context) => - context.formatChunkId(parents, "parent"), - "chunk.siblings[]": (siblings, context) => - context.formatChunkId(siblings, "sibling"), - "chunk.children[]": (children, context) => - context.formatChunkId(children, "child"), - "chunk.childrenByOrder": (childrenByOrder, context, printer) => - Array.isArray(childrenByOrder) - ? undefined - : printer.print( - context.type, - Object.keys(childrenByOrder).map(key => ({ - type: key, - children: childrenByOrder[key] - })), - context - ), - "chunk.childrenByOrder[].type": type => `${type}:`, - "chunk.childrenByOrder[].children[]": (id, { formatChunkId }) => - isValidId(id) ? formatChunkId(id) : undefined, - "chunk.entry": (entry, { formatFlag, yellow }) => - entry ? yellow(formatFlag("entry")) : undefined, - "chunk.initial": (initial, { formatFlag, yellow }) => - initial ? yellow(formatFlag("initial")) : undefined, - "chunk.rendered": (rendered, { formatFlag, green }) => - rendered ? green(formatFlag("rendered")) : undefined, - "chunk.recorded": (recorded, { formatFlag, green }) => - recorded ? green(formatFlag("recorded")) : undefined, - "chunk.reason": (reason, { yellow }) => (reason ? yellow(reason) : undefined), - "chunk.filteredModules": (filteredModules, { chunk: { modules } }) => - filteredModules > 0 - ? `${moreCount(modules, filteredModules)} chunk ${plural( - filteredModules, - "module", - "modules" - )}` - : undefined, - "chunk.separator!": () => "\n", - - "chunkOrigin.request": request => request, - "chunkOrigin.moduleId": (moduleId, { formatModuleId }) => - isValidId(moduleId) ? formatModuleId(moduleId) : undefined, - "chunkOrigin.moduleName": (moduleName, { bold }) => bold(moduleName), - "chunkOrigin.loc": loc => loc -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const ERROR_PRINTERS = { - "error.compilerPath": (compilerPath, { bold }) => - compilerPath ? bold(`(${compilerPath})`) : undefined, - "error.chunkId": (chunkId, { formatChunkId }) => - isValidId(chunkId) ? formatChunkId(chunkId) : undefined, - "error.chunkEntry": (chunkEntry, { formatFlag }) => - chunkEntry ? formatFlag("entry") : undefined, - "error.chunkInitial": (chunkInitial, { formatFlag }) => - chunkInitial ? formatFlag("initial") : undefined, - "error.file": (file, { bold }) => bold(file), - "error.moduleName": (moduleName, { bold }) => - moduleName.includes("!") - ? `${bold(moduleName.replace(/^(\s|\S)*!/, ""))} (${moduleName})` - : `${bold(moduleName)}`, - "error.loc": (loc, { green }) => green(loc), - "error.message": (message, { bold, formatError }) => - message.includes("\u001B[") ? message : bold(formatError(message)), - "error.details": (details, { formatError }) => formatError(details), - "error.filteredDetails": filteredDetails => - filteredDetails ? `+ ${filteredDetails} hidden lines` : undefined, - "error.stack": stack => stack, - "error.moduleTrace": moduleTrace => undefined, - "error.separator!": () => "\n" -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const LOG_ENTRY_PRINTERS = { - "loggingEntry(error).loggingEntry.message": (message, { red }) => - mapLines(message, x => ` ${red(x)}`), - "loggingEntry(warn).loggingEntry.message": (message, { yellow }) => - mapLines(message, x => ` ${yellow(x)}`), - "loggingEntry(info).loggingEntry.message": (message, { green }) => - mapLines(message, x => ` ${green(x)}`), - "loggingEntry(log).loggingEntry.message": (message, { bold }) => - mapLines(message, x => ` ${bold(x)}`), - "loggingEntry(debug).loggingEntry.message": message => - mapLines(message, x => ` ${x}`), - "loggingEntry(trace).loggingEntry.message": message => - mapLines(message, x => ` ${x}`), - "loggingEntry(status).loggingEntry.message": (message, { magenta }) => - mapLines(message, x => ` ${magenta(x)}`), - "loggingEntry(profile).loggingEntry.message": (message, { magenta }) => - mapLines(message, x => `

${magenta(x)}`), - "loggingEntry(profileEnd).loggingEntry.message": (message, { magenta }) => - mapLines(message, x => `

${magenta(x)}`), - "loggingEntry(time).loggingEntry.message": (message, { magenta }) => - mapLines(message, x => ` ${magenta(x)}`), - "loggingEntry(group).loggingEntry.message": (message, { cyan }) => - mapLines(message, x => `<-> ${cyan(x)}`), - "loggingEntry(groupCollapsed).loggingEntry.message": (message, { cyan }) => - mapLines(message, x => `<+> ${cyan(x)}`), - "loggingEntry(clear).loggingEntry": () => " -------", - "loggingEntry(groupCollapsed).loggingEntry.children": () => "", - "loggingEntry.trace[]": trace => - trace ? mapLines(trace, x => `| ${x}`) : undefined, - - loggingGroup: loggingGroup => - loggingGroup.entries.length === 0 ? "" : undefined, - "loggingGroup.debug": (flag, { red }) => (flag ? red("DEBUG") : undefined), - "loggingGroup.name": (name, { bold }) => bold(`LOG from ${name}`), - "loggingGroup.separator!": () => "\n", - "loggingGroup.filteredEntries": filteredEntries => - filteredEntries > 0 ? `+ ${filteredEntries} hidden lines` : undefined -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_TRACE_ITEM_PRINTERS = { - "moduleTraceItem.originName": originName => originName -}; - -/** @type {Record & Required & WithRequired, printer: StatsPrinter) => string | undefined>} */ -const MODULE_TRACE_DEPENDENCY_PRINTERS = { - "moduleTraceDependency.loc": loc => loc -}; - -/** @type {Record} */ -const ITEM_NAMES = { - "compilation.assets[]": "asset", - "compilation.modules[]": "module", - "compilation.chunks[]": "chunk", - "compilation.entrypoints[]": "chunkGroup", - "compilation.namedChunkGroups[]": "chunkGroup", - "compilation.errors[]": "error", - "compilation.warnings[]": "error", - "compilation.logging[]": "loggingGroup", - "compilation.children[]": "compilation", - "asset.related[]": "asset", - "asset.children[]": "asset", - "asset.chunks[]": "assetChunk", - "asset.auxiliaryChunks[]": "assetChunk", - "asset.chunkNames[]": "assetChunkName", - "asset.chunkIdHints[]": "assetChunkIdHint", - "asset.auxiliaryChunkNames[]": "assetChunkName", - "asset.auxiliaryChunkIdHints[]": "assetChunkIdHint", - "chunkGroup.assets[]": "chunkGroupAsset", - "chunkGroup.auxiliaryAssets[]": "chunkGroupAsset", - "chunkGroupChild.assets[]": "chunkGroupAsset", - "chunkGroupChild.auxiliaryAssets[]": "chunkGroupAsset", - "chunkGroup.children[]": "chunkGroupChildGroup", - "chunkGroupChildGroup.children[]": "chunkGroupChild", - "module.modules[]": "module", - "module.children[]": "module", - "module.reasons[]": "moduleReason", - "moduleReason.children[]": "moduleReason", - "module.issuerPath[]": "moduleIssuer", - "chunk.origins[]": "chunkOrigin", - "chunk.modules[]": "module", - "loggingGroup.entries[]": logEntry => - `loggingEntry(${logEntry.type}).loggingEntry`, - "loggingEntry.children[]": logEntry => - `loggingEntry(${logEntry.type}).loggingEntry`, - "error.moduleTrace[]": "moduleTraceItem", - "moduleTraceItem.dependencies[]": "moduleTraceDependency" -}; - -const ERROR_PREFERRED_ORDER = [ - "compilerPath", - "chunkId", - "chunkEntry", - "chunkInitial", - "file", - "separator!", - "moduleName", - "loc", - "separator!", - "message", - "separator!", - "details", - "separator!", - "filteredDetails", - "separator!", - "stack", - "separator!", - "missing", - "separator!", - "moduleTrace" -]; - -/** @type {Record} */ -const PREFERRED_ORDERS = { - compilation: [ - "name", - "hash", - "version", - "time", - "builtAt", - "env", - "publicPath", - "assets", - "filteredAssets", - "entrypoints", - "namedChunkGroups", - "chunks", - "modules", - "filteredModules", - "children", - "logging", - "warnings", - "warningsInChildren!", - "filteredWarningDetailsCount", - "errors", - "errorsInChildren!", - "filteredErrorDetailsCount", - "summary!", - "needAdditionalPass" - ], - asset: [ - "type", - "name", - "size", - "chunks", - "auxiliaryChunks", - "emitted", - "comparedForEmit", - "cached", - "info", - "isOverSizeLimit", - "chunkNames", - "auxiliaryChunkNames", - "chunkIdHints", - "auxiliaryChunkIdHints", - "related", - "filteredRelated", - "children", - "filteredChildren" - ], - "asset.info": [ - "immutable", - "sourceFilename", - "javascriptModule", - "development", - "hotModuleReplacement" - ], - chunkGroup: [ - "kind!", - "name", - "isOverSizeLimit", - "assetsSize", - "auxiliaryAssetsSize", - "is!", - "assets", - "filteredAssets", - "auxiliaryAssets", - "filteredAuxiliaryAssets", - "separator!", - "children" - ], - chunkGroupAsset: ["name", "size"], - chunkGroupChildGroup: ["type", "children"], - chunkGroupChild: ["assets", "chunks", "name"], - module: [ - "type", - "name", - "identifier", - "id", - "layer", - "sizes", - "chunks", - "depth", - "cacheable", - "orphan", - "runtime", - "optional", - "dependent", - "built", - "codeGenerated", - "cached", - "assets", - "failed", - "warnings", - "errors", - "children", - "filteredChildren", - "providedExports", - "usedExports", - "optimizationBailout", - "reasons", - "filteredReasons", - "issuerPath", - "profile", - "modules", - "filteredModules" - ], - moduleReason: [ - "active", - "type", - "userRequest", - "moduleId", - "module", - "resolvedModule", - "loc", - "explanation", - "children", - "filteredChildren" - ], - "module.profile": [ - "total", - "separator!", - "resolving", - "restoring", - "integration", - "building", - "storing", - "additionalResolving", - "additionalIntegration" - ], - chunk: [ - "id", - "runtime", - "files", - "names", - "idHints", - "sizes", - "parents", - "siblings", - "children", - "childrenByOrder", - "entry", - "initial", - "rendered", - "recorded", - "reason", - "separator!", - "origins", - "separator!", - "modules", - "separator!", - "filteredModules" - ], - chunkOrigin: ["request", "moduleId", "moduleName", "loc"], - error: ERROR_PREFERRED_ORDER, - warning: ERROR_PREFERRED_ORDER, - "chunk.childrenByOrder[]": ["type", "children"], - loggingGroup: [ - "debug", - "name", - "separator!", - "entries", - "separator!", - "filteredEntries" - ], - loggingEntry: ["message", "trace", "children"] -}; - -/** @typedef {(items: string[]) => string | undefined} SimpleItemsJoiner */ - -/** @type {SimpleItemsJoiner} */ -const itemsJoinOneLine = items => items.filter(Boolean).join(" "); -/** @type {SimpleItemsJoiner} */ -const itemsJoinOneLineBrackets = items => - items.length > 0 ? `(${items.filter(Boolean).join(" ")})` : undefined; -/** @type {SimpleItemsJoiner} */ -const itemsJoinMoreSpacing = items => items.filter(Boolean).join("\n\n"); -/** @type {SimpleItemsJoiner} */ -const itemsJoinComma = items => items.filter(Boolean).join(", "); -/** @type {SimpleItemsJoiner} */ -const itemsJoinCommaBrackets = items => - items.length > 0 ? `(${items.filter(Boolean).join(", ")})` : undefined; -/** @type {function(string): SimpleItemsJoiner} */ -const itemsJoinCommaBracketsWithName = name => items => - items.length > 0 - ? `(${name}: ${items.filter(Boolean).join(", ")})` - : undefined; - -/** @type {Record} */ -const SIMPLE_ITEMS_JOINER = { - "chunk.parents": itemsJoinOneLine, - "chunk.siblings": itemsJoinOneLine, - "chunk.children": itemsJoinOneLine, - "chunk.names": itemsJoinCommaBrackets, - "chunk.idHints": itemsJoinCommaBracketsWithName("id hint"), - "chunk.runtime": itemsJoinCommaBracketsWithName("runtime"), - "chunk.files": itemsJoinComma, - "chunk.childrenByOrder": itemsJoinOneLine, - "chunk.childrenByOrder[].children": itemsJoinOneLine, - "chunkGroup.assets": itemsJoinOneLine, - "chunkGroup.auxiliaryAssets": itemsJoinOneLineBrackets, - "chunkGroupChildGroup.children": itemsJoinComma, - "chunkGroupChild.assets": itemsJoinOneLine, - "chunkGroupChild.auxiliaryAssets": itemsJoinOneLineBrackets, - "asset.chunks": itemsJoinComma, - "asset.auxiliaryChunks": itemsJoinCommaBrackets, - "asset.chunkNames": itemsJoinCommaBracketsWithName("name"), - "asset.auxiliaryChunkNames": itemsJoinCommaBracketsWithName("auxiliary name"), - "asset.chunkIdHints": itemsJoinCommaBracketsWithName("id hint"), - "asset.auxiliaryChunkIdHints": - itemsJoinCommaBracketsWithName("auxiliary id hint"), - "module.chunks": itemsJoinOneLine, - "module.issuerPath": items => - items - .filter(Boolean) - .map(item => `${item} ->`) - .join(" "), - "compilation.errors": itemsJoinMoreSpacing, - "compilation.warnings": itemsJoinMoreSpacing, - "compilation.logging": itemsJoinMoreSpacing, - "compilation.children": items => - indent(/** @type {string} */ (itemsJoinMoreSpacing(items)), " "), - "moduleTraceItem.dependencies": itemsJoinOneLine, - "loggingEntry.children": items => - indent(items.filter(Boolean).join("\n"), " ", false) -}; - -/** - * @param {Item[]} items items - * @returns {string} result - */ -const joinOneLine = items => - items - .map(item => item.content) - .filter(Boolean) - .join(" "); - -/** - * @param {Item[]} items items - * @returns {string} result - */ -const joinInBrackets = items => { - const res = []; - let mode = 0; - for (const item of items) { - if (item.element === "separator!") { - switch (mode) { - case 0: - case 1: - mode += 2; - break; - case 4: - res.push(")"); - mode = 3; - break; - } - } - if (!item.content) continue; - switch (mode) { - case 0: - mode = 1; - break; - case 1: - res.push(" "); - break; - case 2: - res.push("("); - mode = 4; - break; - case 3: - res.push(" ("); - mode = 4; - break; - case 4: - res.push(", "); - break; - } - res.push(item.content); - } - if (mode === 4) res.push(")"); - return res.join(""); -}; - -/** - * @param {string} str a string - * @param {string} prefix prefix - * @param {boolean=} noPrefixInFirstLine need prefix in the first line? - * @returns {string} result - */ -const indent = (str, prefix, noPrefixInFirstLine) => { - const rem = str.replace(/\n([^\n])/g, `\n${prefix}$1`); - if (noPrefixInFirstLine) return rem; - const ind = str[0] === "\n" ? "" : prefix; - return ind + rem; -}; - -/** - * @param {(false | Item)[]} items items - * @param {string} indenter indenter - * @returns {string} result - */ -const joinExplicitNewLine = (items, indenter) => { - let firstInLine = true; - let first = true; - return items - .map(item => { - if (!item || !item.content) return; - let content = indent(item.content, first ? "" : indenter, !firstInLine); - if (firstInLine) { - content = content.replace(/^\n+/, ""); - } - if (!content) return; - first = false; - const noJoiner = firstInLine || content.startsWith("\n"); - firstInLine = content.endsWith("\n"); - return noJoiner ? content : ` ${content}`; - }) - .filter(Boolean) - .join("") - .trim(); -}; - -/** - * @param {boolean} error is an error - * @returns {SimpleElementJoiner} joiner - */ -const joinError = - error => - /** - * @param {Item[]} items items - * @param {Required} ctx context - * @returns {string} result - */ - (items, { red, yellow }) => - `${error ? red("ERROR") : yellow("WARNING")} in ${joinExplicitNewLine( - items, - "" - )}`; - -/** @typedef {{ element: string, content: string }} Item */ -/** @typedef {(items: Item[], context: Required) => string} SimpleElementJoiner */ - -/** @type {Record} */ -const SIMPLE_ELEMENT_JOINERS = { - compilation: items => { - const result = []; - let lastNeedMore = false; - for (const item of items) { - if (!item.content) continue; - const needMoreSpace = - item.element === "warnings" || - item.element === "filteredWarningDetailsCount" || - item.element === "errors" || - item.element === "filteredErrorDetailsCount" || - item.element === "logging"; - if (result.length !== 0) { - result.push(needMoreSpace || lastNeedMore ? "\n\n" : "\n"); - } - result.push(item.content); - lastNeedMore = needMoreSpace; - } - if (lastNeedMore) result.push("\n"); - return result.join(""); - }, - asset: items => - joinExplicitNewLine( - items.map(item => { - if ( - (item.element === "related" || item.element === "children") && - item.content - ) { - return { - ...item, - content: `\n${item.content}\n` - }; - } - return item; - }), - " " - ), - "asset.info": joinOneLine, - module: (items, { module }) => { - let hasName = false; - return joinExplicitNewLine( - items.map(item => { - switch (item.element) { - case "id": - if (module.id === module.name) { - if (hasName) return false; - if (item.content) hasName = true; - } - break; - case "name": - if (hasName) return false; - if (item.content) hasName = true; - break; - case "providedExports": - case "usedExports": - case "optimizationBailout": - case "reasons": - case "issuerPath": - case "profile": - case "children": - case "modules": - if (item.content) { - return { - ...item, - content: `\n${item.content}\n` - }; - } - break; - } - return item; - }), - " " - ); - }, - chunk: items => { - let hasEntry = false; - return `chunk ${joinExplicitNewLine( - items.filter(item => { - switch (item.element) { - case "entry": - if (item.content) hasEntry = true; - break; - case "initial": - if (hasEntry) return false; - break; - } - return true; - }), - " " - )}`; - }, - "chunk.childrenByOrder[]": items => `(${joinOneLine(items)})`, - chunkGroup: items => joinExplicitNewLine(items, " "), - chunkGroupAsset: joinOneLine, - chunkGroupChildGroup: joinOneLine, - chunkGroupChild: joinOneLine, - // moduleReason: (items, { moduleReason }) => { - // let hasName = false; - // return joinOneLine( - // items.filter(item => { - // switch (item.element) { - // case "moduleId": - // if (moduleReason.moduleId === moduleReason.module && item.content) - // hasName = true; - // break; - // case "module": - // if (hasName) return false; - // break; - // case "resolvedModule": - // return ( - // moduleReason.module !== moduleReason.resolvedModule && - // item.content - // ); - // } - // return true; - // }) - // ); - // }, - moduleReason: (items, { moduleReason }) => { - let hasName = false; - return joinExplicitNewLine( - items.map(item => { - switch (item.element) { - case "moduleId": - if (moduleReason.moduleId === moduleReason.module && item.content) - hasName = true; - break; - case "module": - if (hasName) return false; - break; - case "resolvedModule": - if (moduleReason.module === moduleReason.resolvedModule) - return false; - break; - case "children": - if (item.content) { - return { - ...item, - content: `\n${item.content}\n` - }; - } - break; - } - return item; - }), - " " - ); - }, - "module.profile": joinInBrackets, - moduleIssuer: joinOneLine, - chunkOrigin: items => `> ${joinOneLine(items)}`, - "errors[].error": joinError(true), - "warnings[].error": joinError(false), - loggingGroup: items => joinExplicitNewLine(items, "").trimEnd(), - moduleTraceItem: items => ` @ ${joinOneLine(items)}`, - moduleTraceDependency: joinOneLine -}; - -/** @typedef {"bold" | "yellow" | "red" | "green" | "cyan" | "magenta"} ColorNames */ - -/** @type {Record} */ -const AVAILABLE_COLORS = { - 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" -}; - -/** @type {Record & StatsPrinterContext, ...any): string>} */ -const AVAILABLE_FORMATS = { - formatChunkId: (id, { yellow }, direction) => { - switch (direction) { - case "parent": - return `<{${yellow(id)}}>`; - case "sibling": - return `={${yellow(id)}}=`; - case "child": - return `>{${yellow(id)}}<`; - default: - return `{${yellow(id)}}`; - } - }, - formatModuleId: id => `[${id}]`, - formatFilename: (filename, { green, yellow }, oversize) => - (oversize ? yellow : green)(filename), - formatFlag: flag => `[${flag}]`, - formatLayer: layer => `(in ${layer})`, - formatSize: require("../SizeFormatHelpers").formatSize, - formatDateTime: (dateTime, { bold }) => { - const d = new Date(dateTime); - const x = twoDigit; - const date = `${d.getFullYear()}-${x(d.getMonth() + 1)}-${x(d.getDate())}`; - const time = `${x(d.getHours())}:${x(d.getMinutes())}:${x(d.getSeconds())}`; - return `${date} ${bold(time)}`; - }, - formatTime: ( - time, - { timeReference, bold, green, yellow, red }, - boldQuantity - ) => { - const unit = " ms"; - if (timeReference && time !== timeReference) { - const times = [ - timeReference / 2, - timeReference / 4, - timeReference / 8, - timeReference / 16 - ]; - if (time < times[3]) return `${time}${unit}`; - else if (time < times[2]) return bold(`${time}${unit}`); - else if (time < times[1]) return green(`${time}${unit}`); - else if (time < times[0]) return yellow(`${time}${unit}`); - return red(`${time}${unit}`); - } - return `${boldQuantity ? bold(time) : time}${unit}`; - }, - formatError: (message, { green, yellow, red }) => { - if (message.includes("\u001B[")) return message; - const highlights = [ - { regExp: /(Did you mean .+)/g, format: green }, - { - regExp: /(Set 'mode' option to 'development' or 'production')/g, - format: green - }, - { regExp: /(\(module has no exports\))/g, format: red }, - { regExp: /\(possible exports: (.+)\)/g, format: green }, - { regExp: /(?:^|\n)(.* doesn't exist)/g, format: red }, - { regExp: /('\w+' option has not been set)/g, format: red }, - { - regExp: /(Emitted value instead of an instance of Error)/g, - format: yellow - }, - { regExp: /(Used? .+ instead)/gi, format: yellow }, - { regExp: /\b(deprecated|must|required)\b/g, format: yellow }, - { - regExp: /\b(BREAKING CHANGE)\b/gi, - format: red - }, - { - regExp: - /\b(error|failed|unexpected|invalid|not found|not supported|not available|not possible|not implemented|doesn't support|conflict|conflicting|not existing|duplicate)\b/gi, - format: red - } - ]; - for (const { regExp, format } of highlights) { - message = message.replace( - regExp, - /** - * @param {string} match match - * @param {string} content content - * @returns {string} result - */ - (match, content) => match.replace(content, format(content)) - ); - } - return message; - } -}; - -/** @typedef {function(string): string} ResultModifierFn */ -/** @type {Record} */ -const RESULT_MODIFIER = { - "module.modules": result => indent(result, "| ") -}; - -/** - * @param {string[]} array array - * @param {string[]} preferredOrder preferred order - * @returns {string[]} result - */ -const createOrder = (array, preferredOrder) => { - const originalArray = array.slice(); - /** @type {Set} */ - const set = new Set(array); - /** @type {Set} */ - const usedSet = new Set(); - array.length = 0; - for (const element of preferredOrder) { - if (element.endsWith("!") || set.has(element)) { - array.push(element); - usedSet.add(element); - } - } - for (const element of originalArray) { - if (!usedSet.has(element)) { - array.push(element); - } - } - return array; -}; - -class DefaultStatsPrinterPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("DefaultStatsPrinterPlugin", compilation => { - compilation.hooks.statsPrinter.tap( - "DefaultStatsPrinterPlugin", - (stats, options) => { - // Put colors into context - stats.hooks.print - .for("compilation") - .tap("DefaultStatsPrinterPlugin", (compilation, context) => { - for (const color of Object.keys(AVAILABLE_COLORS)) { - const name = /** @type {ColorNames} */ (color); - /** @type {string | undefined} */ - let start; - if (options.colors) { - if ( - typeof options.colors === "object" && - typeof options.colors[name] === "string" - ) { - start = options.colors[name]; - } else { - start = AVAILABLE_COLORS[name]; - } - } - if (start) { - /** - * @param {string} str string - * @returns {string} string with color - */ - context[color] = str => - `${start}${ - typeof str === "string" - ? str.replace( - /((\u001B\[39m|\u001B\[22m|\u001B\[0m)+)/g, - `$1${start}` - ) - : str - }\u001B[39m\u001B[22m`; - } else { - /** - * @param {string} str string - * @returns {string} str string - */ - context[color] = str => str; - } - } - for (const format of Object.keys(AVAILABLE_FORMATS)) { - context[format] = - /** - * @param {string | number} content content - * @param {...TODO} args args - * @returns {string} result - */ - (content, ...args) => - AVAILABLE_FORMATS[format]( - content, - /** @type {Required & StatsPrinterContext} */ - (context), - ...args - ); - } - context.timeReference = compilation.time; - }); - - for (const key of Object.keys(COMPILATION_SIMPLE_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - COMPILATION_SIMPLE_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(ASSET_SIMPLE_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - ASSET_SIMPLE_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_SIMPLE_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_SIMPLE_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_ISSUER_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_ISSUER_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_REASON_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_REASON_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_PROFILE_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_PROFILE_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(CHUNK_GROUP_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - CHUNK_GROUP_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(CHUNK_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - CHUNK_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(ERROR_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - ERROR_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(LOG_ENTRY_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - LOG_ENTRY_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_TRACE_DEPENDENCY_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_TRACE_DEPENDENCY_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(MODULE_TRACE_ITEM_PRINTERS)) { - stats.hooks.print - .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - MODULE_TRACE_ITEM_PRINTERS[key]( - obj, - /** @type {Required & Required & WithRequired} */ - (ctx), - stats - ) - ); - } - - for (const key of Object.keys(PREFERRED_ORDERS)) { - const preferredOrder = PREFERRED_ORDERS[key]; - stats.hooks.sortElements - .for(key) - .tap("DefaultStatsPrinterPlugin", (elements, context) => { - createOrder(elements, preferredOrder); - }); - } - - for (const key of Object.keys(ITEM_NAMES)) { - const itemName = ITEM_NAMES[key]; - stats.hooks.getItemName - .for(key) - .tap( - "DefaultStatsPrinterPlugin", - typeof itemName === "string" ? () => itemName : itemName - ); - } - - for (const key of Object.keys(SIMPLE_ITEMS_JOINER)) { - const joiner = SIMPLE_ITEMS_JOINER[key]; - stats.hooks.printItems - .for(key) - .tap("DefaultStatsPrinterPlugin", joiner); - } - - for (const key of Object.keys(SIMPLE_ELEMENT_JOINERS)) { - const joiner = SIMPLE_ELEMENT_JOINERS[key]; - stats.hooks.printElements - .for(key) - .tap("DefaultStatsPrinterPlugin", /** @type {TODO} */ (joiner)); - } - - for (const key of Object.keys(RESULT_MODIFIER)) { - const modifier = RESULT_MODIFIER[key]; - stats.hooks.result - .for(key) - .tap("DefaultStatsPrinterPlugin", modifier); - } - } - ); - }); - } -} -module.exports = DefaultStatsPrinterPlugin; diff --git a/webpack-lib/lib/stats/StatsFactory.js b/webpack-lib/lib/stats/StatsFactory.js deleted file mode 100644 index b668369ea1d..00000000000 --- a/webpack-lib/lib/stats/StatsFactory.js +++ /dev/null @@ -1,363 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable"); -const { concatComparators, keepOriginalOrder } = require("../util/comparators"); -const smartGrouping = require("../util/smartGrouping"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compilation").NormalizedStatsOptions} NormalizedStatsOptions */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../WebpackError")} WebpackError */ -/** @typedef {import("../util/comparators").Comparator} Comparator */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("../util/smartGrouping").GroupConfig} GroupConfig */ - -/** - * @typedef {object} KnownStatsFactoryContext - * @property {string} type - * @property {function(string): string} makePathsRelative - * @property {Compilation} compilation - * @property {Set} rootModules - * @property {Map} compilationFileToChunks - * @property {Map} compilationAuxiliaryFileToChunks - * @property {RuntimeSpec} runtime - * @property {function(Compilation): WebpackError[]} cachedGetErrors - * @property {function(Compilation): WebpackError[]} cachedGetWarnings - */ - -/** @typedef {Record & KnownStatsFactoryContext} StatsFactoryContext */ - -/** @typedef {any} CreatedObject */ -/** @typedef {any} FactoryData */ -/** @typedef {any} FactoryDataItem */ -/** @typedef {any} Result */ -/** @typedef {Record} ObjectForExtract */ - -/** - * @typedef {object} StatsFactoryHooks - * @property {HookMap>} extract - * @property {HookMap>} filter - * @property {HookMap>} sort - * @property {HookMap>} filterSorted - * @property {HookMap>} groupResults - * @property {HookMap>} sortResults - * @property {HookMap>} filterResults - * @property {HookMap>} merge - * @property {HookMap>} result - * @property {HookMap>} getItemName - * @property {HookMap>} getItemFactory - */ - -/** - * @template T - * @typedef {Map} Caches - */ - -class StatsFactory { - constructor() { - /** @type {StatsFactoryHooks} */ - this.hooks = Object.freeze({ - extract: new HookMap( - () => new SyncBailHook(["object", "data", "context"]) - ), - filter: new HookMap( - () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) - ), - sort: new HookMap(() => new SyncBailHook(["comparators", "context"])), - filterSorted: new HookMap( - () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) - ), - groupResults: new HookMap( - () => new SyncBailHook(["groupConfigs", "context"]) - ), - sortResults: new HookMap( - () => new SyncBailHook(["comparators", "context"]) - ), - filterResults: new HookMap( - () => new SyncBailHook(["item", "context", "index", "unfilteredIndex"]) - ), - merge: new HookMap(() => new SyncBailHook(["items", "context"])), - result: new HookMap(() => new SyncWaterfallHook(["result", "context"])), - getItemName: new HookMap(() => new SyncBailHook(["item", "context"])), - getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"])) - }); - const hooks = this.hooks; - this._caches = /** @type {TODO} */ ({}); - for (const key of Object.keys(hooks)) { - this._caches[/** @type {keyof StatsFactoryHooks} */ (key)] = new Map(); - } - this._inCreate = false; - } - - /** - * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @param {HM} hookMap hook map - * @param {Caches} cache cache - * @param {string} type type - * @returns {H[]} hooks - * @private - */ - _getAllLevelHooks(hookMap, cache, type) { - const cacheEntry = cache.get(type); - if (cacheEntry !== undefined) { - return cacheEntry; - } - const hooks = /** @type {H[]} */ ([]); - const typeParts = type.split("."); - for (let i = 0; i < typeParts.length; i++) { - const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join("."))); - if (hook) { - hooks.push(hook); - } - } - cache.set(type, hooks); - return hooks; - } - - /** - * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @template {H extends import("tapable").Hook ? R : never} R - * @param {HM} hookMap hook map - * @param {Caches} cache cache - * @param {string} type type - * @param {function(H): R | void} fn fn - * @returns {R | void} hook - * @private - */ - _forEachLevel(hookMap, cache, type, fn) { - for (const hook of this._getAllLevelHooks(hookMap, cache, type)) { - const result = fn(/** @type {H} */ (hook)); - if (result !== undefined) return result; - } - } - - /** - * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @param {HM} hookMap hook map - * @param {Caches} cache cache - * @param {string} type type - * @param {FactoryData} data data - * @param {function(H, FactoryData): FactoryData} fn fn - * @returns {FactoryData} data - * @private - */ - _forEachLevelWaterfall(hookMap, cache, type, data, fn) { - for (const hook of this._getAllLevelHooks(hookMap, cache, type)) { - data = fn(/** @type {H} */ (hook), data); - } - return data; - } - - /** - * @template {StatsFactoryHooks[keyof StatsFactoryHooks]} T - * @template {T extends HookMap ? H : never} H - * @template {H extends import("tapable").Hook ? R : never} R - * @param {T} hookMap hook map - * @param {Caches} cache cache - * @param {string} type type - * @param {Array} items items - * @param {function(H, R, number, number): R | undefined} fn fn - * @param {boolean} forceClone force clone - * @returns {R[]} result for each level - * @private - */ - _forEachLevelFilter(hookMap, cache, type, items, fn, forceClone) { - const hooks = this._getAllLevelHooks(hookMap, cache, type); - if (hooks.length === 0) return forceClone ? items.slice() : items; - let i = 0; - return items.filter((item, idx) => { - for (const hook of hooks) { - const r = fn(/** @type {H} */ (hook), item, idx, i); - if (r !== undefined) { - if (r) i++; - return r; - } - } - i++; - return true; - }); - } - - /** - * @param {string} type type - * @param {FactoryData} data factory data - * @param {Omit} baseContext context used as base - * @returns {CreatedObject} created object - */ - create(type, data, baseContext) { - if (this._inCreate) { - return this._create(type, data, baseContext); - } - try { - this._inCreate = true; - return this._create(type, data, baseContext); - } finally { - for (const key of Object.keys(this._caches)) - this._caches[/** @type {keyof StatsFactoryHooks} */ (key)].clear(); - this._inCreate = false; - } - } - - /** - * @param {string} type type - * @param {FactoryData} data factory data - * @param {Omit} baseContext context used as base - * @returns {CreatedObject} created object - * @private - */ - _create(type, data, baseContext) { - const context = /** @type {StatsFactoryContext} */ ({ - ...baseContext, - type, - [type]: data - }); - if (Array.isArray(data)) { - // run filter on unsorted items - const items = this._forEachLevelFilter( - this.hooks.filter, - this._caches.filter, - type, - data, - (h, r, idx, i) => h.call(r, context, idx, i), - true - ); - - // sort items - /** @type {Comparator[]} */ - const comparators = []; - this._forEachLevel(this.hooks.sort, this._caches.sort, type, h => - h.call(comparators, context) - ); - if (comparators.length > 0) { - items.sort( - // @ts-expect-error number of arguments is correct - concatComparators(...comparators, keepOriginalOrder(items)) - ); - } - - // run filter on sorted items - const items2 = this._forEachLevelFilter( - this.hooks.filterSorted, - this._caches.filterSorted, - type, - items, - (h, r, idx, i) => h.call(r, context, idx, i), - false - ); - - // for each item - let resultItems = items2.map((item, i) => { - /** @type {StatsFactoryContext} */ - const itemContext = { - ...context, - _index: i - }; - - // run getItemName - const itemName = this._forEachLevel( - this.hooks.getItemName, - this._caches.getItemName, - `${type}[]`, - h => h.call(item, itemContext) - ); - if (itemName) itemContext[itemName] = item; - const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`; - - // run getItemFactory - const itemFactory = - this._forEachLevel( - this.hooks.getItemFactory, - this._caches.getItemFactory, - innerType, - h => h.call(item, itemContext) - ) || this; - - // run item factory - return itemFactory.create(innerType, item, itemContext); - }); - - // sort result items - /** @type {Comparator[]} */ - const comparators2 = []; - this._forEachLevel( - this.hooks.sortResults, - this._caches.sortResults, - type, - h => h.call(comparators2, context) - ); - if (comparators2.length > 0) { - resultItems.sort( - // @ts-expect-error number of arguments is correct - concatComparators(...comparators2, keepOriginalOrder(resultItems)) - ); - } - - // group result items - /** @type {GroupConfig[]} */ - const groupConfigs = []; - this._forEachLevel( - this.hooks.groupResults, - this._caches.groupResults, - type, - h => h.call(groupConfigs, context) - ); - if (groupConfigs.length > 0) { - resultItems = smartGrouping(resultItems, groupConfigs); - } - - // run filter on sorted result items - const finalResultItems = this._forEachLevelFilter( - this.hooks.filterResults, - this._caches.filterResults, - type, - resultItems, - (h, r, idx, i) => h.call(r, context, idx, i), - false - ); - - // run merge on mapped items - let result = this._forEachLevel( - this.hooks.merge, - this._caches.merge, - type, - h => h.call(finalResultItems, context) - ); - if (result === undefined) result = finalResultItems; - - // run result on merged items - return this._forEachLevelWaterfall( - this.hooks.result, - this._caches.result, - type, - result, - (h, r) => h.call(r, context) - ); - } - /** @type {ObjectForExtract} */ - const object = {}; - - // run extract on value - this._forEachLevel(this.hooks.extract, this._caches.extract, type, h => - h.call(object, data, context) - ); - - // run result on extracted object - return this._forEachLevelWaterfall( - this.hooks.result, - this._caches.result, - type, - object, - (h, r) => h.call(r, context) - ); - } -} -module.exports = StatsFactory; diff --git a/webpack-lib/lib/stats/StatsPrinter.js b/webpack-lib/lib/stats/StatsPrinter.js deleted file mode 100644 index 99270618389..00000000000 --- a/webpack-lib/lib/stats/StatsPrinter.js +++ /dev/null @@ -1,280 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { HookMap, SyncWaterfallHook, SyncBailHook } = require("tapable"); - -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsAsset} StatsAsset */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunk} StatsChunk */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsChunkGroup} StatsChunkGroup */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsCompilation} StatsCompilation */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsError} StatsError */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsLogging} StatsLogging */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModule} StatsModule */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleIssuer} StatsModuleIssuer */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleReason} StatsModuleReason */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceDependency} StatsModuleTraceDependency */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsModuleTraceItem} StatsModuleTraceItem */ -/** @typedef {import("./DefaultStatsFactoryPlugin").StatsProfile} StatsProfile */ - -/** - * @typedef {object} PrintedElement - * @property {string} element - * @property {string} content - */ - -/** - * @typedef {object} KnownStatsPrinterContext - * @property {string=} type - * @property {StatsCompilation=} compilation - * @property {StatsChunkGroup=} chunkGroup - * @property {string=} chunkGroupKind - * @property {StatsAsset=} asset - * @property {StatsModule=} module - * @property {StatsChunk=} chunk - * @property {StatsModuleReason=} moduleReason - * @property {StatsModuleIssuer=} moduleIssuer - * @property {StatsError=} error - * @property {StatsProfile=} profile - * @property {StatsLogging=} logging - * @property {StatsModuleTraceItem=} moduleTraceItem - * @property {StatsModuleTraceDependency=} moduleTraceDependency - */ - -/** - * @typedef {object} KnownStatsPrinterColorFn - * @property {(str: string) => string=} bold - * @property {(str: string) => string=} yellow - * @property {(str: string) => string=} red - * @property {(str: string) => string=} green - * @property {(str: string) => string=} magenta - * @property {(str: string) => string=} cyan - */ - -/** - * @typedef {object} KnownStatsPrinterFormaters - * @property {(file: string, oversize?: boolean) => string=} formatFilename - * @property {(id: string) => string=} formatModuleId - * @property {(id: string, direction?: "parent"|"child"|"sibling") => string=} formatChunkId - * @property {(size: number) => string=} formatSize - * @property {(size: string) => string=} formatLayer - * @property {(dateTime: number) => string=} formatDateTime - * @property {(flag: string) => string=} formatFlag - * @property {(time: number, boldQuantity?: boolean) => string=} formatTime - * @property {(message: string) => string=} formatError - */ - -/** @typedef {Record & KnownStatsPrinterColorFn & KnownStatsPrinterFormaters & KnownStatsPrinterContext} StatsPrinterContext */ -/** @typedef {any} PrintObject */ - -/** - * @typedef {object} StatsPrintHooks - * @property {HookMap>} sortElements - * @property {HookMap>} printElements - * @property {HookMap>} sortItems - * @property {HookMap>} getItemName - * @property {HookMap>} printItems - * @property {HookMap>} print - * @property {HookMap>} result - */ - -class StatsPrinter { - constructor() { - /** @type {StatsPrintHooks} */ - this.hooks = Object.freeze({ - sortElements: new HookMap( - () => new SyncBailHook(["elements", "context"]) - ), - printElements: new HookMap( - () => new SyncBailHook(["printedElements", "context"]) - ), - sortItems: new HookMap(() => new SyncBailHook(["items", "context"])), - getItemName: new HookMap(() => new SyncBailHook(["item", "context"])), - printItems: new HookMap( - () => new SyncBailHook(["printedItems", "context"]) - ), - print: new HookMap(() => new SyncBailHook(["object", "context"])), - /** @type {HookMap>} */ - result: new HookMap(() => new SyncWaterfallHook(["result", "context"])) - }); - /** - * @type {TODO} - */ - this._levelHookCache = new Map(); - this._inPrint = false; - } - - /** - * get all level hooks - * @private - * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @param {HM} hookMap hook map - * @param {string} type type - * @returns {H[]} hooks - */ - _getAllLevelHooks(hookMap, type) { - let cache = this._levelHookCache.get(hookMap); - if (cache === undefined) { - cache = new Map(); - this._levelHookCache.set(hookMap, cache); - } - const cacheEntry = cache.get(type); - if (cacheEntry !== undefined) { - return cacheEntry; - } - /** @type {H[]} */ - const hooks = []; - const typeParts = type.split("."); - for (let i = 0; i < typeParts.length; i++) { - const hook = /** @type {H} */ (hookMap.get(typeParts.slice(i).join("."))); - if (hook) { - hooks.push(hook); - } - } - cache.set(type, hooks); - return hooks; - } - - /** - * Run `fn` for each level - * @private - * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @template {H extends import("tapable").Hook ? R : never} R - * @param {HM} hookMap hook map - * @param {string} type type - * @param {function(H): R | void} fn fn - * @returns {R | void} hook - */ - _forEachLevel(hookMap, type, fn) { - for (const hook of this._getAllLevelHooks(hookMap, type)) { - const result = fn(/** @type {H} */ (hook)); - if (result !== undefined) return result; - } - } - - /** - * Run `fn` for each level - * @private - * @template {StatsPrintHooks[keyof StatsPrintHooks]} HM - * @template {HM extends HookMap ? H : never} H - * @param {HM} hookMap hook map - * @param {string} type type - * @param {string} data data - * @param {function(H, string): string} fn fn - * @returns {string} result of `fn` - */ - _forEachLevelWaterfall(hookMap, type, data, fn) { - for (const hook of this._getAllLevelHooks(hookMap, type)) { - data = fn(/** @type {H} */ (hook), data); - } - return data; - } - - /** - * @param {string} type The type - * @param {PrintObject} object Object to print - * @param {StatsPrinterContext=} baseContext The base context - * @returns {string} printed result - */ - print(type, object, baseContext) { - if (this._inPrint) { - return this._print(type, object, baseContext); - } - try { - this._inPrint = true; - return this._print(type, object, baseContext); - } finally { - this._levelHookCache.clear(); - this._inPrint = false; - } - } - - /** - * @private - * @param {string} type type - * @param {PrintObject} object object - * @param {StatsPrinterContext=} baseContext context - * @returns {string} printed result - */ - _print(type, object, baseContext) { - /** @type {StatsPrinterContext} */ - const context = { - ...baseContext, - type, - [type]: object - }; - - let printResult = this._forEachLevel(this.hooks.print, type, hook => - hook.call(object, context) - ); - if (printResult === undefined) { - if (Array.isArray(object)) { - const sortedItems = object.slice(); - this._forEachLevel(this.hooks.sortItems, type, h => - h.call(sortedItems, context) - ); - const printedItems = sortedItems.map((item, i) => { - /** @type {StatsPrinterContext} */ - const itemContext = { - ...context, - _index: i - }; - const itemName = this._forEachLevel( - this.hooks.getItemName, - `${type}[]`, - h => h.call(item, itemContext) - ); - if (itemName) itemContext[itemName] = item; - return this.print( - itemName ? `${type}[].${itemName}` : `${type}[]`, - item, - itemContext - ); - }); - printResult = this._forEachLevel(this.hooks.printItems, type, h => - h.call(printedItems, context) - ); - if (printResult === undefined) { - const result = printedItems.filter(Boolean); - if (result.length > 0) printResult = result.join("\n"); - } - } else if (object !== null && typeof object === "object") { - const elements = Object.keys(object).filter( - key => object[key] !== undefined - ); - this._forEachLevel(this.hooks.sortElements, type, h => - h.call(elements, context) - ); - const printedElements = elements.map(element => { - const content = this.print(`${type}.${element}`, object[element], { - ...context, - _parent: object, - _element: element, - [element]: object[element] - }); - return { element, content }; - }); - printResult = this._forEachLevel(this.hooks.printElements, type, h => - h.call(printedElements, context) - ); - if (printResult === undefined) { - const result = printedElements.map(e => e.content).filter(Boolean); - if (result.length > 0) printResult = result.join("\n"); - } - } - } - - return this._forEachLevelWaterfall( - this.hooks.result, - type, - /** @type {string} */ (printResult), - (h, r) => h.call(r, context) - ); - } -} -module.exports = StatsPrinter; diff --git a/webpack-lib/lib/util/ArrayHelpers.js b/webpack-lib/lib/util/ArrayHelpers.js deleted file mode 100644 index ac32ce9f7a3..00000000000 --- a/webpack-lib/lib/util/ArrayHelpers.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * Compare two arrays or strings by performing strict equality check for each value. - * @template T [T=any] - * @param {ArrayLike} a Array of values to be compared - * @param {ArrayLike} b Array of values to be compared - * @returns {boolean} returns true if all the elements of passed arrays are strictly equal. - */ - -module.exports.equals = (a, b) => { - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; -}; - -/** - * Partition an array by calling a predicate function on each value. - * @template T [T=any] - * @param {Array} arr Array of values to be partitioned - * @param {(value: T) => boolean} fn Partition function which partitions based on truthiness of result. - * @returns {[Array, Array]} returns the values of `arr` partitioned into two new arrays based on fn predicate. - */ - -module.exports.groupBy = ( - // eslint-disable-next-line default-param-last - arr = [], - fn -) => - arr.reduce( - /** - * @param {[Array, Array]} groups An accumulator storing already partitioned values returned from previous call. - * @param {T} value The value of the current element - * @returns {[Array, Array]} returns an array of partitioned groups accumulator resulting from calling a predicate on the current value. - */ - (groups, value) => { - groups[fn(value) ? 0 : 1].push(value); - return groups; - }, - [[], []] - ); diff --git a/webpack-lib/lib/util/ArrayQueue.js b/webpack-lib/lib/util/ArrayQueue.js deleted file mode 100644 index 522abf93de2..00000000000 --- a/webpack-lib/lib/util/ArrayQueue.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template T - */ -class ArrayQueue { - /** - * @param {Iterable=} items The initial elements. - */ - constructor(items) { - /** - * @private - * @type {T[]} - */ - this._list = items ? Array.from(items) : []; - /** - * @private - * @type {T[]} - */ - this._listReversed = []; - } - - /** - * Returns the number of elements in this queue. - * @returns {number} The number of elements in this queue. - */ - get length() { - return this._list.length + this._listReversed.length; - } - - /** - * Empties the queue. - */ - clear() { - this._list.length = 0; - this._listReversed.length = 0; - } - - /** - * Appends the specified element to this queue. - * @param {T} item The element to add. - * @returns {void} - */ - enqueue(item) { - this._list.push(item); - } - - /** - * Retrieves and removes the head of this queue. - * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. - */ - dequeue() { - if (this._listReversed.length === 0) { - if (this._list.length === 0) return; - if (this._list.length === 1) return this._list.pop(); - if (this._list.length < 16) return this._list.shift(); - const temp = this._listReversed; - this._listReversed = this._list; - this._listReversed.reverse(); - this._list = temp; - } - return this._listReversed.pop(); - } - - /** - * Finds and removes an item - * @param {T} item the item - * @returns {void} - */ - delete(item) { - const i = this._list.indexOf(item); - if (i >= 0) { - this._list.splice(i, 1); - } else { - const i = this._listReversed.indexOf(item); - if (i >= 0) this._listReversed.splice(i, 1); - } - } - - [Symbol.iterator]() { - return { - next: () => { - const item = this.dequeue(); - if (item) { - return { - done: false, - value: item - }; - } - return { - done: true, - value: undefined - }; - } - }; - } -} - -module.exports = ArrayQueue; diff --git a/webpack-lib/lib/util/AsyncQueue.js b/webpack-lib/lib/util/AsyncQueue.js deleted file mode 100644 index fb01d49e91d..00000000000 --- a/webpack-lib/lib/util/AsyncQueue.js +++ /dev/null @@ -1,410 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncHook, AsyncSeriesHook } = require("tapable"); -const { makeWebpackError } = require("../HookWebpackError"); -const WebpackError = require("../WebpackError"); -const ArrayQueue = require("./ArrayQueue"); - -const QUEUED_STATE = 0; -const PROCESSING_STATE = 1; -const DONE_STATE = 2; - -let inHandleResult = 0; - -/** - * @template T - * @callback Callback - * @param {(WebpackError | null)=} err - * @param {(T | null)=} result - */ - -/** - * @template T - * @template K - * @template R - */ -class AsyncQueueEntry { - /** - * @param {T} item the item - * @param {Callback} callback the callback - */ - constructor(item, callback) { - this.item = item; - /** @type {typeof QUEUED_STATE | typeof PROCESSING_STATE | typeof DONE_STATE} */ - this.state = QUEUED_STATE; - /** @type {Callback | undefined} */ - this.callback = callback; - /** @type {Callback[] | undefined} */ - this.callbacks = undefined; - /** @type {R | null | undefined} */ - this.result = undefined; - /** @type {WebpackError | null | undefined} */ - this.error = undefined; - } -} - -/** - * @template T, K - * @typedef {function(T): K} getKey - */ - -/** - * @template T, R - * @typedef {function(T, Callback): void} Processor - */ - -/** - * @template T - * @template K - * @template R - */ -class AsyncQueue { - /** - * @param {object} options options object - * @param {string=} options.name name of the queue - * @param {number=} options.parallelism how many items should be processed at once - * @param {string=} options.context context of execution - * @param {AsyncQueue=} options.parent parent queue, which will have priority over this queue and with shared parallelism - * @param {getKey=} options.getKey extract key from item - * @param {Processor} options.processor async function to process items - */ - constructor({ name, context, parallelism, parent, processor, getKey }) { - this._name = name; - this._context = context || "normal"; - this._parallelism = parallelism || 1; - this._processor = processor; - this._getKey = - getKey || - /** @type {getKey} */ (item => /** @type {T & K} */ (item)); - /** @type {Map>} */ - this._entries = new Map(); - /** @type {ArrayQueue>} */ - this._queued = new ArrayQueue(); - /** @type {AsyncQueue[] | undefined} */ - this._children = undefined; - this._activeTasks = 0; - this._willEnsureProcessing = false; - this._needProcessing = false; - this._stopped = false; - /** @type {AsyncQueue} */ - this._root = parent ? parent._root : this; - if (parent) { - if (this._root._children === undefined) { - this._root._children = [this]; - } else { - this._root._children.push(this); - } - } - - this.hooks = { - /** @type {AsyncSeriesHook<[T]>} */ - beforeAdd: new AsyncSeriesHook(["item"]), - /** @type {SyncHook<[T]>} */ - added: new SyncHook(["item"]), - /** @type {AsyncSeriesHook<[T]>} */ - beforeStart: new AsyncSeriesHook(["item"]), - /** @type {SyncHook<[T]>} */ - started: new SyncHook(["item"]), - /** @type {SyncHook<[T, WebpackError | null | undefined, R | null | undefined]>} */ - result: new SyncHook(["item", "error", "result"]) - }; - - this._ensureProcessing = this._ensureProcessing.bind(this); - } - - /** - * @returns {string} context of execution - */ - getContext() { - return this._context; - } - - /** - * @param {string} value context of execution - */ - setContext(value) { - this._context = value; - } - - /** - * @param {T} item an item - * @param {Callback} callback callback function - * @returns {void} - */ - add(item, callback) { - if (this._stopped) return callback(new WebpackError("Queue was stopped")); - this.hooks.beforeAdd.callAsync(item, err => { - if (err) { - callback( - makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeAdd`) - ); - return; - } - const key = this._getKey(item); - const entry = this._entries.get(key); - if (entry !== undefined) { - if (entry.state === DONE_STATE) { - if (inHandleResult++ > 3) { - process.nextTick(() => callback(entry.error, entry.result)); - } else { - callback(entry.error, entry.result); - } - inHandleResult--; - } else if (entry.callbacks === undefined) { - entry.callbacks = [callback]; - } else { - entry.callbacks.push(callback); - } - return; - } - const newEntry = new AsyncQueueEntry(item, callback); - if (this._stopped) { - this.hooks.added.call(item); - this._root._activeTasks++; - process.nextTick(() => - this._handleResult(newEntry, new WebpackError("Queue was stopped")) - ); - } else { - this._entries.set(key, newEntry); - this._queued.enqueue(newEntry); - const root = this._root; - root._needProcessing = true; - if (root._willEnsureProcessing === false) { - root._willEnsureProcessing = true; - setImmediate(root._ensureProcessing); - } - this.hooks.added.call(item); - } - }); - } - - /** - * @param {T} item an item - * @returns {void} - */ - invalidate(item) { - const key = this._getKey(item); - const entry = - /** @type {AsyncQueueEntry} */ - (this._entries.get(key)); - this._entries.delete(key); - if (entry.state === QUEUED_STATE) { - this._queued.delete(entry); - } - } - - /** - * Waits for an already started item - * @param {T} item an item - * @param {Callback} callback callback function - * @returns {void} - */ - waitFor(item, callback) { - const key = this._getKey(item); - const entry = this._entries.get(key); - if (entry === undefined) { - return callback( - new WebpackError( - "waitFor can only be called for an already started item" - ) - ); - } - if (entry.state === DONE_STATE) { - process.nextTick(() => callback(entry.error, entry.result)); - } else if (entry.callbacks === undefined) { - entry.callbacks = [callback]; - } else { - entry.callbacks.push(callback); - } - } - - /** - * @returns {void} - */ - stop() { - this._stopped = true; - const queue = this._queued; - this._queued = new ArrayQueue(); - const root = this._root; - for (const entry of queue) { - this._entries.delete( - this._getKey(/** @type {AsyncQueueEntry} */ (entry).item) - ); - root._activeTasks++; - this._handleResult( - /** @type {AsyncQueueEntry} */ (entry), - new WebpackError("Queue was stopped") - ); - } - } - - /** - * @returns {void} - */ - increaseParallelism() { - const root = this._root; - root._parallelism++; - /* istanbul ignore next */ - if (root._willEnsureProcessing === false && root._needProcessing) { - root._willEnsureProcessing = true; - setImmediate(root._ensureProcessing); - } - } - - /** - * @returns {void} - */ - decreaseParallelism() { - const root = this._root; - root._parallelism--; - } - - /** - * @param {T} item an item - * @returns {boolean} true, if the item is currently being processed - */ - isProcessing(item) { - const key = this._getKey(item); - const entry = this._entries.get(key); - return entry !== undefined && entry.state === PROCESSING_STATE; - } - - /** - * @param {T} item an item - * @returns {boolean} true, if the item is currently queued - */ - isQueued(item) { - const key = this._getKey(item); - const entry = this._entries.get(key); - return entry !== undefined && entry.state === QUEUED_STATE; - } - - /** - * @param {T} item an item - * @returns {boolean} true, if the item is currently queued - */ - isDone(item) { - const key = this._getKey(item); - const entry = this._entries.get(key); - return entry !== undefined && entry.state === DONE_STATE; - } - - /** - * @returns {void} - */ - _ensureProcessing() { - while (this._activeTasks < this._parallelism) { - const entry = this._queued.dequeue(); - if (entry === undefined) break; - this._activeTasks++; - entry.state = PROCESSING_STATE; - this._startProcessing(entry); - } - this._willEnsureProcessing = false; - if (this._queued.length > 0) return; - if (this._children !== undefined) { - for (const child of this._children) { - while (this._activeTasks < this._parallelism) { - const entry = child._queued.dequeue(); - if (entry === undefined) break; - this._activeTasks++; - entry.state = PROCESSING_STATE; - child._startProcessing(entry); - } - if (child._queued.length > 0) return; - } - } - if (!this._willEnsureProcessing) this._needProcessing = false; - } - - /** - * @param {AsyncQueueEntry} entry the entry - * @returns {void} - */ - _startProcessing(entry) { - this.hooks.beforeStart.callAsync(entry.item, err => { - if (err) { - this._handleResult( - entry, - makeWebpackError(err, `AsyncQueue(${this._name}).hooks.beforeStart`) - ); - return; - } - let inCallback = false; - try { - this._processor(entry.item, (e, r) => { - inCallback = true; - this._handleResult(entry, e, r); - }); - } catch (err) { - if (inCallback) throw err; - this._handleResult(entry, /** @type {WebpackError} */ (err), null); - } - this.hooks.started.call(entry.item); - }); - } - - /** - * @param {AsyncQueueEntry} entry the entry - * @param {(WebpackError | null)=} err error, if any - * @param {(R | null)=} result result, if any - * @returns {void} - */ - _handleResult(entry, err, result) { - this.hooks.result.callAsync(entry.item, err, result, hookError => { - const error = hookError - ? makeWebpackError(hookError, `AsyncQueue(${this._name}).hooks.result`) - : err; - - const callback = /** @type {Callback} */ (entry.callback); - const callbacks = entry.callbacks; - entry.state = DONE_STATE; - entry.callback = undefined; - entry.callbacks = undefined; - entry.result = result; - entry.error = error; - - const root = this._root; - root._activeTasks--; - if (root._willEnsureProcessing === false && root._needProcessing) { - root._willEnsureProcessing = true; - setImmediate(root._ensureProcessing); - } - - if (inHandleResult++ > 3) { - process.nextTick(() => { - callback(error, result); - if (callbacks !== undefined) { - for (const callback of callbacks) { - callback(error, result); - } - } - }); - } else { - callback(error, result); - if (callbacks !== undefined) { - for (const callback of callbacks) { - callback(error, result); - } - } - } - inHandleResult--; - }); - } - - clear() { - this._entries.clear(); - this._queued.clear(); - this._activeTasks = 0; - this._willEnsureProcessing = false; - this._needProcessing = false; - this._stopped = false; - } -} - -module.exports = AsyncQueue; diff --git a/webpack-lib/lib/util/Hash.js b/webpack-lib/lib/util/Hash.js deleted file mode 100644 index a0078275327..00000000000 --- a/webpack-lib/lib/util/Hash.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -class Hash { - /* istanbul ignore next */ - /** - * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} - * @abstract - * @param {string|Buffer} data data - * @param {string=} inputEncoding data encoding - * @returns {this} updated hash - */ - update(data, inputEncoding) { - const AbstractMethodError = require("../AbstractMethodError"); - throw new AbstractMethodError(); - } - - /* istanbul ignore next */ - /** - * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} - * @abstract - * @param {string=} encoding encoding of the return value - * @returns {string|Buffer} digest - */ - digest(encoding) { - const AbstractMethodError = require("../AbstractMethodError"); - throw new AbstractMethodError(); - } -} - -module.exports = Hash; diff --git a/webpack-lib/lib/util/IterableHelpers.js b/webpack-lib/lib/util/IterableHelpers.js deleted file mode 100644 index 73d02c66727..00000000000 --- a/webpack-lib/lib/util/IterableHelpers.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template T - * @param {Iterable} set a set - * @returns {T | undefined} last item - */ -const last = set => { - let last; - for (const item of set) last = item; - return last; -}; - -/** - * @template T - * @param {Iterable} iterable iterable - * @param {function(T): boolean | null | undefined} filter predicate - * @returns {boolean} true, if some items match the filter predicate - */ -const someInIterable = (iterable, filter) => { - for (const item of iterable) { - if (filter(item)) return true; - } - return false; -}; - -/** - * @template T - * @param {Iterable} iterable an iterable - * @returns {number} count of items - */ -const countIterable = iterable => { - let i = 0; - for (const _ of iterable) i++; - return i; -}; - -module.exports.last = last; -module.exports.someInIterable = someInIterable; -module.exports.countIterable = countIterable; diff --git a/webpack-lib/lib/util/LazyBucketSortedSet.js b/webpack-lib/lib/util/LazyBucketSortedSet.js deleted file mode 100644 index 5469010893d..00000000000 --- a/webpack-lib/lib/util/LazyBucketSortedSet.js +++ /dev/null @@ -1,252 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { first } = require("./SetHelpers"); -const SortableSet = require("./SortableSet"); - -/** - * @template T - * @typedef {LazyBucketSortedSet | SortableSet} Entry - */ - -/** - * @template T - * @typedef {(function(T): any) | (function(any, any): number)} Arg - */ - -/** - * Multi layer bucket sorted set: - * Supports adding non-existing items (DO NOT ADD ITEM TWICE), - * Supports removing exiting items (DO NOT REMOVE ITEM NOT IN SET), - * Supports popping the first items according to defined order, - * Supports iterating all items without order, - * Supports updating an item in an efficient way, - * Supports size property, which is the number of items, - * Items are lazy partially sorted when needed - * @template T - * @template K - */ -class LazyBucketSortedSet { - /** - * @param {function(T): K} getKey function to get key from item - * @param {function(K, K): number} comparator comparator to sort keys - * @param {...Arg} args more pairs of getKey and comparator plus optional final comparator for the last layer - */ - constructor(getKey, comparator, ...args) { - this._getKey = getKey; - /** @type {Arg[]} */ - this._innerArgs = args; - this._leaf = args.length <= 1; - this._keys = new SortableSet(undefined, comparator); - /** @type {Map>} */ - this._map = new Map(); - this._unsortedItems = new Set(); - this.size = 0; - } - - /** - * @param {T} item an item - * @returns {void} - */ - add(item) { - this.size++; - this._unsortedItems.add(item); - } - - /** - * @param {K} key key of item - * @param {T} item the item - * @returns {void} - */ - _addInternal(key, item) { - let entry = this._map.get(key); - if (entry === undefined) { - entry = - /** @type {Entry} */ - ( - this._leaf - ? new SortableSet(undefined, this._innerArgs[0]) - : new /** @type {TODO} */ (LazyBucketSortedSet)(...this._innerArgs) - ); - this._keys.add(key); - this._map.set(key, entry); - } - /** @type {Entry} */ - (entry).add(item); - } - - /** - * @param {T} item an item - * @returns {void} - */ - delete(item) { - this.size--; - if (this._unsortedItems.has(item)) { - this._unsortedItems.delete(item); - return; - } - const key = this._getKey(item); - const entry = /** @type {Entry} */ (this._map.get(key)); - entry.delete(item); - if (entry.size === 0) { - this._deleteKey(key); - } - } - - /** - * @param {K} key key to be removed - * @returns {void} - */ - _deleteKey(key) { - this._keys.delete(key); - this._map.delete(key); - } - - /** - * @returns {T | undefined} an item - */ - popFirst() { - if (this.size === 0) return; - this.size--; - if (this._unsortedItems.size > 0) { - for (const item of this._unsortedItems) { - const key = this._getKey(item); - this._addInternal(key, item); - } - this._unsortedItems.clear(); - } - this._keys.sort(); - const key = /** @type {K} */ (first(this._keys)); - const entry = this._map.get(key); - if (this._leaf) { - const leafEntry = /** @type {SortableSet} */ (entry); - leafEntry.sort(); - const item = /** @type {T} */ (first(leafEntry)); - leafEntry.delete(item); - if (leafEntry.size === 0) { - this._deleteKey(key); - } - return item; - } - const nodeEntry = /** @type {LazyBucketSortedSet} */ (entry); - const item = nodeEntry.popFirst(); - if (nodeEntry.size === 0) { - this._deleteKey(key); - } - return item; - } - - /** - * @param {T} item to be updated item - * @returns {function(true=): void} finish update - */ - startUpdate(item) { - if (this._unsortedItems.has(item)) { - return remove => { - if (remove) { - this._unsortedItems.delete(item); - this.size--; - } - }; - } - const key = this._getKey(item); - if (this._leaf) { - const oldEntry = /** @type {SortableSet} */ (this._map.get(key)); - return remove => { - if (remove) { - this.size--; - oldEntry.delete(item); - if (oldEntry.size === 0) { - this._deleteKey(key); - } - return; - } - const newKey = this._getKey(item); - if (key === newKey) { - // This flags the sortable set as unordered - oldEntry.add(item); - } else { - oldEntry.delete(item); - if (oldEntry.size === 0) { - this._deleteKey(key); - } - this._addInternal(newKey, item); - } - }; - } - const oldEntry = /** @type {LazyBucketSortedSet} */ ( - this._map.get(key) - ); - const finishUpdate = oldEntry.startUpdate(item); - return remove => { - if (remove) { - this.size--; - finishUpdate(true); - if (oldEntry.size === 0) { - this._deleteKey(key); - } - return; - } - const newKey = this._getKey(item); - if (key === newKey) { - finishUpdate(); - } else { - finishUpdate(true); - if (oldEntry.size === 0) { - this._deleteKey(key); - } - this._addInternal(newKey, item); - } - }; - } - - /** - * @param {Iterator[]} iterators list of iterators to append to - * @returns {void} - */ - _appendIterators(iterators) { - if (this._unsortedItems.size > 0) - iterators.push(this._unsortedItems[Symbol.iterator]()); - for (const key of this._keys) { - const entry = this._map.get(key); - if (this._leaf) { - const leafEntry = /** @type {SortableSet} */ (entry); - const iterator = leafEntry[Symbol.iterator](); - iterators.push(iterator); - } else { - const nodeEntry = /** @type {LazyBucketSortedSet} */ (entry); - nodeEntry._appendIterators(iterators); - } - } - } - - /** - * @returns {Iterator} the iterator - */ - [Symbol.iterator]() { - /** @type {Iterator[]} */ - const iterators = []; - this._appendIterators(iterators); - iterators.reverse(); - let currentIterator = - /** @type {Iterator} */ - (iterators.pop()); - return { - next: () => { - const res = currentIterator.next(); - if (res.done) { - if (iterators.length === 0) return res; - currentIterator = /** @type {Iterator} */ (iterators.pop()); - return currentIterator.next(); - } - return res; - } - }; - } -} - -module.exports = LazyBucketSortedSet; diff --git a/webpack-lib/lib/util/LazySet.js b/webpack-lib/lib/util/LazySet.js deleted file mode 100644 index 623c1c5a329..00000000000 --- a/webpack-lib/lib/util/LazySet.js +++ /dev/null @@ -1,229 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const makeSerializable = require("./makeSerializable.js"); - -/** - * @template T - * @param {Set} targetSet set where items should be added - * @param {Set>} toMerge iterables to be merged - * @returns {void} - */ -const merge = (targetSet, toMerge) => { - for (const set of toMerge) { - for (const item of set) { - targetSet.add(item); - } - } -}; - -/** - * @template T - * @param {Set>} targetSet set where iterables should be added - * @param {Array>} toDeepMerge lazy sets to be flattened - * @returns {void} - */ -const flatten = (targetSet, toDeepMerge) => { - for (const set of toDeepMerge) { - if (set._set.size > 0) targetSet.add(set._set); - if (set._needMerge) { - for (const mergedSet of set._toMerge) { - targetSet.add(mergedSet); - } - flatten(targetSet, set._toDeepMerge); - } - } -}; - -/** - * Like Set but with an addAll method to eventually add items from another iterable. - * Access methods make sure that all delayed operations are executed. - * Iteration methods deopts to normal Set performance until clear is called again (because of the chance of modifications during iteration). - * @template T - */ -class LazySet { - /** - * @param {Iterable=} iterable init iterable - */ - constructor(iterable) { - /** @type {Set} */ - this._set = new Set(iterable); - /** @type {Set>} */ - this._toMerge = new Set(); - /** @type {Array>} */ - this._toDeepMerge = []; - this._needMerge = false; - this._deopt = false; - } - - _flatten() { - flatten(this._toMerge, this._toDeepMerge); - this._toDeepMerge.length = 0; - } - - _merge() { - this._flatten(); - merge(this._set, this._toMerge); - this._toMerge.clear(); - this._needMerge = false; - } - - _isEmpty() { - return ( - this._set.size === 0 && - this._toMerge.size === 0 && - this._toDeepMerge.length === 0 - ); - } - - get size() { - if (this._needMerge) this._merge(); - return this._set.size; - } - - /** - * @param {T} item an item - * @returns {LazySet} itself - */ - add(item) { - this._set.add(item); - return this; - } - - /** - * @param {Iterable | LazySet} iterable a immutable iterable or another immutable LazySet which will eventually be merged into the Set - * @returns {LazySet} itself - */ - addAll(iterable) { - if (this._deopt) { - const _set = this._set; - for (const item of iterable) { - _set.add(item); - } - } else { - if (iterable instanceof LazySet) { - if (iterable._isEmpty()) return this; - this._toDeepMerge.push(iterable); - this._needMerge = true; - if (this._toDeepMerge.length > 100000) { - this._flatten(); - } - } else { - this._toMerge.add(iterable); - this._needMerge = true; - } - if (this._toMerge.size > 100000) this._merge(); - } - return this; - } - - clear() { - this._set.clear(); - this._toMerge.clear(); - this._toDeepMerge.length = 0; - this._needMerge = false; - this._deopt = false; - } - - /** - * @param {T} value an item - * @returns {boolean} true, if the value was in the Set before - */ - delete(value) { - if (this._needMerge) this._merge(); - return this._set.delete(value); - } - - /** - * @returns {IterableIterator<[T, T]>} entries - */ - entries() { - this._deopt = true; - if (this._needMerge) this._merge(); - return this._set.entries(); - } - - /** - * @param {function(T, T, Set): void} callbackFn function called for each entry - * @param {any} thisArg this argument for the callbackFn - * @returns {void} - */ - forEach(callbackFn, thisArg) { - this._deopt = true; - if (this._needMerge) this._merge(); - // eslint-disable-next-line unicorn/no-array-for-each - this._set.forEach(callbackFn, thisArg); - } - - /** - * @param {T} item an item - * @returns {boolean} true, when the item is in the Set - */ - has(item) { - if (this._needMerge) this._merge(); - return this._set.has(item); - } - - /** - * @returns {IterableIterator} keys - */ - keys() { - this._deopt = true; - if (this._needMerge) this._merge(); - return this._set.keys(); - } - - /** - * @returns {IterableIterator} values - */ - values() { - this._deopt = true; - if (this._needMerge) this._merge(); - return this._set.values(); - } - - /** - * @returns {IterableIterator} iterable iterator - */ - [Symbol.iterator]() { - this._deopt = true; - if (this._needMerge) this._merge(); - return this._set[Symbol.iterator](); - } - - /* istanbul ignore next */ - get [Symbol.toStringTag]() { - return "LazySet"; - } - - /** - * @param {import("../serialization/ObjectMiddleware").ObjectSerializerContext} context context - */ - serialize({ write }) { - if (this._needMerge) this._merge(); - write(this._set.size); - for (const item of this._set) write(item); - } - - /** - * @template T - * @param {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} context context - * @returns {LazySet} lazy set - */ - static deserialize({ read }) { - const count = read(); - const items = []; - for (let i = 0; i < count; i++) { - items.push(read()); - } - return new LazySet(items); - } -} - -makeSerializable(LazySet, "webpack/lib/util/LazySet"); - -module.exports = LazySet; diff --git a/webpack-lib/lib/util/MapHelpers.js b/webpack-lib/lib/util/MapHelpers.js deleted file mode 100644 index 259f621d8e0..00000000000 --- a/webpack-lib/lib/util/MapHelpers.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * getOrInsert is a helper function for maps that allows you to get a value - * from a map if it exists, or insert a new value if it doesn't. If it value doesn't - * exist, it will be computed by the provided function. - * @template K - * @template V - * @param {Map} map The map object to check - * @param {K} key The key to check - * @param {function(): V} computer function which will compute the value if it doesn't exist - * @returns {V} The value from the map, or the computed value - * @example - * ```js - * const map = new Map(); - * const value = getOrInsert(map, "key", () => "value"); - * console.log(value); // "value" - * ``` - */ -module.exports.getOrInsert = (map, key, computer) => { - // Grab key from map - const value = map.get(key); - // If the value already exists, return it - if (value !== undefined) return value; - // Otherwise compute the value, set it in the map, and return it - const newValue = computer(); - map.set(key, newValue); - return newValue; -}; diff --git a/webpack-lib/lib/util/ParallelismFactorCalculator.js b/webpack-lib/lib/util/ParallelismFactorCalculator.js deleted file mode 100644 index d7725b7bfd4..00000000000 --- a/webpack-lib/lib/util/ParallelismFactorCalculator.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const binarySearchBounds = require("./binarySearchBounds"); - -/** @typedef {function(number): void} Callback */ - -class ParallelismFactorCalculator { - constructor() { - /** @type {number[]} */ - this._rangePoints = []; - /** @type {Callback[]} */ - this._rangeCallbacks = []; - } - - /** - * @param {number} start range start - * @param {number} end range end - * @param {Callback} callback callback - * @returns {void} - */ - range(start, end, callback) { - if (start === end) return callback(1); - this._rangePoints.push(start); - this._rangePoints.push(end); - this._rangeCallbacks.push(callback); - } - - calculate() { - const segments = Array.from(new Set(this._rangePoints)).sort((a, b) => - a < b ? -1 : 1 - ); - const parallelism = segments.map(() => 0); - const rangeStartIndices = []; - for (let i = 0; i < this._rangePoints.length; i += 2) { - const start = this._rangePoints[i]; - const end = this._rangePoints[i + 1]; - let idx = binarySearchBounds.eq(segments, start); - rangeStartIndices.push(idx); - do { - parallelism[idx]++; - idx++; - } while (segments[idx] < end); - } - for (let i = 0; i < this._rangeCallbacks.length; i++) { - const start = this._rangePoints[i * 2]; - const end = this._rangePoints[i * 2 + 1]; - let idx = rangeStartIndices[i]; - let sum = 0; - let totalDuration = 0; - let current = start; - do { - const p = parallelism[idx]; - idx++; - const duration = segments[idx] - current; - totalDuration += duration; - current = segments[idx]; - sum += p * duration; - } while (current < end); - this._rangeCallbacks[i](sum / totalDuration); - } - } -} - -module.exports = ParallelismFactorCalculator; diff --git a/webpack-lib/lib/util/Queue.js b/webpack-lib/lib/util/Queue.js deleted file mode 100644 index 3820770655a..00000000000 --- a/webpack-lib/lib/util/Queue.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template T - */ -class Queue { - /** - * @param {Iterable=} items The initial elements. - */ - constructor(items) { - /** - * @private - * @type {Set} - */ - this._set = new Set(items); - } - - /** - * Returns the number of elements in this queue. - * @returns {number} The number of elements in this queue. - */ - get length() { - return this._set.size; - } - - /** - * Appends the specified element to this queue. - * @param {T} item The element to add. - * @returns {void} - */ - enqueue(item) { - this._set.add(item); - } - - /** - * Retrieves and removes the head of this queue. - * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. - */ - dequeue() { - const result = this._set[Symbol.iterator]().next(); - if (result.done) return; - this._set.delete(result.value); - return result.value; - } -} - -module.exports = Queue; diff --git a/webpack-lib/lib/util/Semaphore.js b/webpack-lib/lib/util/Semaphore.js deleted file mode 100644 index 5277fedb6c6..00000000000 --- a/webpack-lib/lib/util/Semaphore.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -class Semaphore { - /** - * Creates an instance of Semaphore. - * @param {number} available the amount available number of "tasks" - * in the Semaphore - */ - constructor(available) { - this.available = available; - /** @type {(function(): void)[]} */ - this.waiters = []; - /** @private */ - this._continue = this._continue.bind(this); - } - - /** - * @param {function(): void} callback function block to capture and run - * @returns {void} - */ - acquire(callback) { - if (this.available > 0) { - this.available--; - callback(); - } else { - this.waiters.push(callback); - } - } - - release() { - this.available++; - if (this.waiters.length > 0) { - process.nextTick(this._continue); - } - } - - _continue() { - if (this.available > 0 && this.waiters.length > 0) { - this.available--; - const callback = /** @type {(function(): void)} */ (this.waiters.pop()); - callback(); - } - } -} - -module.exports = Semaphore; diff --git a/webpack-lib/lib/util/SetHelpers.js b/webpack-lib/lib/util/SetHelpers.js deleted file mode 100644 index 5908cce9339..00000000000 --- a/webpack-lib/lib/util/SetHelpers.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * intersect creates Set containing the intersection of elements between all sets - * @template T - * @param {Set[]} sets an array of sets being checked for shared elements - * @returns {Set} returns a new Set containing the intersecting items - */ -const intersect = sets => { - if (sets.length === 0) return new Set(); - if (sets.length === 1) return new Set(sets[0]); - let minSize = Infinity; - let minIndex = -1; - for (let i = 0; i < sets.length; i++) { - const size = sets[i].size; - if (size < minSize) { - minIndex = i; - minSize = size; - } - } - const current = new Set(sets[minIndex]); - for (let i = 0; i < sets.length; i++) { - if (i === minIndex) continue; - const set = sets[i]; - for (const item of current) { - if (!set.has(item)) { - current.delete(item); - } - } - } - return current; -}; - -/** - * Checks if a set is the subset of another set - * @template T - * @param {Set} bigSet a Set which contains the original elements to compare against - * @param {Set} smallSet the set whose elements might be contained inside of bigSet - * @returns {boolean} returns true if smallSet contains all elements inside of the bigSet - */ -const isSubset = (bigSet, smallSet) => { - if (bigSet.size < smallSet.size) return false; - for (const item of smallSet) { - if (!bigSet.has(item)) return false; - } - return true; -}; - -/** - * @template T - * @param {Set} set a set - * @param {function(T): boolean} fn selector function - * @returns {T | undefined} found item - */ -const find = (set, fn) => { - for (const item of set) { - if (fn(item)) return item; - } -}; - -/** - * @template T - * @param {Set | ReadonlySet} set a set - * @returns {T | undefined} first item - */ -const first = set => { - const entry = set.values().next(); - return entry.done ? undefined : entry.value; -}; - -/** - * @template T - * @param {Set} a first - * @param {Set} b second - * @returns {Set} combined set, may be identical to a or b - */ -const combine = (a, b) => { - if (b.size === 0) return a; - if (a.size === 0) return b; - const set = new Set(a); - for (const item of b) set.add(item); - return set; -}; - -module.exports.intersect = intersect; -module.exports.isSubset = isSubset; -module.exports.find = find; -module.exports.first = first; -module.exports.combine = combine; diff --git a/webpack-lib/lib/util/SortableSet.js b/webpack-lib/lib/util/SortableSet.js deleted file mode 100644 index 9260c163a0f..00000000000 --- a/webpack-lib/lib/util/SortableSet.js +++ /dev/null @@ -1,173 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NONE = Symbol("not sorted"); - -/** - * A subset of Set that offers sorting functionality - * @template T item type in set - * @extends {Set} - */ -class SortableSet extends Set { - /** - * Create a new sortable set - * @template T - * @param {Iterable=} initialIterable The initial iterable value - * @typedef {function(T, T): number} SortFunction - * @param {SortFunction=} defaultSort Default sorting function - */ - constructor(initialIterable, defaultSort) { - super(initialIterable); - /** - * @private - * @type {undefined | SortFunction} - */ - this._sortFn = defaultSort; - /** - * @private - * @type {typeof NONE | undefined | function(T, T): number}} - */ - this._lastActiveSortFn = NONE; - /** - * @private - * @type {Map | undefined} - */ - this._cache = undefined; - /** - * @private - * @type {Map | undefined} - */ - this._cacheOrderIndependent = undefined; - } - - /** - * @param {T} value value to add to set - * @returns {this} returns itself - */ - add(value) { - this._lastActiveSortFn = NONE; - this._invalidateCache(); - this._invalidateOrderedCache(); - super.add(value); - return this; - } - - /** - * @param {T} value value to delete - * @returns {boolean} true if value existed in set, false otherwise - */ - delete(value) { - this._invalidateCache(); - this._invalidateOrderedCache(); - return super.delete(value); - } - - /** - * @returns {void} - */ - clear() { - this._invalidateCache(); - this._invalidateOrderedCache(); - return super.clear(); - } - - /** - * Sort with a comparer function - * @param {SortFunction | undefined} sortFn Sorting comparer function - * @returns {void} - */ - sortWith(sortFn) { - if (this.size <= 1 || sortFn === this._lastActiveSortFn) { - // already sorted - nothing to do - return; - } - - const sortedArray = Array.from(this).sort(sortFn); - super.clear(); - for (let i = 0; i < sortedArray.length; i += 1) { - super.add(sortedArray[i]); - } - this._lastActiveSortFn = sortFn; - this._invalidateCache(); - } - - sort() { - this.sortWith(this._sortFn); - return this; - } - - /** - * Get data from cache - * @template R - * @param {function(SortableSet): R} fn function to calculate value - * @returns {R} returns result of fn(this), cached until set changes - */ - getFromCache(fn) { - if (this._cache === undefined) { - this._cache = new Map(); - } else { - const result = this._cache.get(fn); - const data = /** @type {R} */ (result); - if (data !== undefined) { - return data; - } - } - const newData = fn(this); - this._cache.set(fn, newData); - return newData; - } - - /** - * Get data from cache (ignoring sorting) - * @template R - * @param {function(SortableSet): R} fn function to calculate value - * @returns {R} returns result of fn(this), cached until set changes - */ - getFromUnorderedCache(fn) { - if (this._cacheOrderIndependent === undefined) { - this._cacheOrderIndependent = new Map(); - } else { - const result = this._cacheOrderIndependent.get(fn); - const data = /** @type {R} */ (result); - if (data !== undefined) { - return data; - } - } - const newData = fn(this); - this._cacheOrderIndependent.set(fn, newData); - return newData; - } - - /** - * @private - * @returns {void} - */ - _invalidateCache() { - if (this._cache !== undefined) { - this._cache.clear(); - } - } - - /** - * @private - * @returns {void} - */ - _invalidateOrderedCache() { - if (this._cacheOrderIndependent !== undefined) { - this._cacheOrderIndependent.clear(); - } - } - - /** - * @returns {T[]} the raw array - */ - toJSON() { - return Array.from(this); - } -} - -module.exports = SortableSet; diff --git a/webpack-lib/lib/util/StackedCacheMap.js b/webpack-lib/lib/util/StackedCacheMap.js deleted file mode 100644 index 820f0d1b3d8..00000000000 --- a/webpack-lib/lib/util/StackedCacheMap.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * The StackedCacheMap is a data structure designed as an alternative to a Map - * in situations where you need to handle multiple item additions and - * frequently access the largest map. - * - * It is particularly optimized for efficiently adding multiple items - * at once, which can be achieved using the `addAll` method. - * - * It has a fallback Map that is used when the map to be added is mutable. - * - * Note: `delete` and `has` are not supported for performance reasons. - * @example - * ```js - * const map = new StackedCacheMap(); - * map.addAll(new Map([["a", 1], ["b", 2]]), true); - * map.addAll(new Map([["c", 3], ["d", 4]]), true); - * map.get("a"); // 1 - * map.get("d"); // 4 - * for (const [key, value] of map) { - * console.log(key, value); - * } - * ``` - * @template K - * @template V - */ -class StackedCacheMap { - constructor() { - /** @type {Map} */ - this.map = new Map(); - /** @type {ReadonlyMap[]} */ - this.stack = []; - } - - /** - * If `immutable` is true, the map can be referenced by the StackedCacheMap - * and should not be changed afterwards. If the map is mutable, all items - * are copied into a fallback Map. - * @param {ReadonlyMap} map map to add - * @param {boolean=} immutable if 'map' is immutable and StackedCacheMap can keep referencing it - */ - addAll(map, immutable) { - if (immutable) { - this.stack.push(map); - - // largest map should go first - for (let i = this.stack.length - 1; i > 0; i--) { - const beforeLast = this.stack[i - 1]; - if (beforeLast.size >= map.size) break; - this.stack[i] = beforeLast; - this.stack[i - 1] = map; - } - } else { - for (const [key, value] of map) { - this.map.set(key, value); - } - } - } - - /** - * @param {K} item the key of the element to add - * @param {V} value the value of the element to add - * @returns {void} - */ - set(item, value) { - this.map.set(item, value); - } - - /** - * @param {K} item the item to delete - * @returns {void} - */ - delete(item) { - throw new Error("Items can't be deleted from a StackedCacheMap"); - } - - /** - * @param {K} item the item to test - * @returns {boolean} true if the item exists in this set - */ - has(item) { - throw new Error( - "Checking StackedCacheMap.has before reading is inefficient, use StackedCacheMap.get and check for undefined" - ); - } - - /** - * @param {K} item the key of the element to return - * @returns {V | undefined} the value of the element - */ - get(item) { - for (const map of this.stack) { - const value = map.get(item); - if (value !== undefined) return value; - } - return this.map.get(item); - } - - clear() { - this.stack.length = 0; - this.map.clear(); - } - - /** - * @returns {number} size of the map - */ - get size() { - let size = this.map.size; - for (const map of this.stack) { - size += map.size; - } - return size; - } - - /** - * @returns {Iterator<[K, V]>} iterator - */ - [Symbol.iterator]() { - const iterators = this.stack.map(map => map[Symbol.iterator]()); - let current = this.map[Symbol.iterator](); - return { - next() { - let result = current.next(); - while (result.done && iterators.length > 0) { - current = /** @type {IterableIterator<[K, V]>} */ (iterators.pop()); - result = current.next(); - } - return result; - } - }; - } -} - -module.exports = StackedCacheMap; diff --git a/webpack-lib/lib/util/StackedMap.js b/webpack-lib/lib/util/StackedMap.js deleted file mode 100644 index 0f4011d0ce7..00000000000 --- a/webpack-lib/lib/util/StackedMap.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const TOMBSTONE = Symbol("tombstone"); -const UNDEFINED_MARKER = Symbol("undefined"); - -/** - * @template T - * @typedef {T | undefined} Cell - */ - -/** - * @template T - * @typedef {T | typeof TOMBSTONE | typeof UNDEFINED_MARKER} InternalCell - */ - -/** - * @template K - * @template V - * @param {[K, InternalCell]} pair the internal cell - * @returns {[K, Cell]} its “safe” representation - */ -const extractPair = pair => { - const key = pair[0]; - const val = pair[1]; - if (val === UNDEFINED_MARKER || val === TOMBSTONE) { - return [key, undefined]; - } - return /** @type {[K, Cell]} */ (pair); -}; - -/** - * @template K - * @template V - */ -class StackedMap { - /** - * @param {Map>[]=} parentStack an optional parent - */ - constructor(parentStack) { - /** @type {Map>} */ - this.map = new Map(); - /** @type {Map>[]} */ - this.stack = parentStack === undefined ? [] : parentStack.slice(); - this.stack.push(this.map); - } - - /** - * @param {K} item the key of the element to add - * @param {V} value the value of the element to add - * @returns {void} - */ - set(item, value) { - this.map.set(item, value === undefined ? UNDEFINED_MARKER : value); - } - - /** - * @param {K} item the item to delete - * @returns {void} - */ - delete(item) { - if (this.stack.length > 1) { - this.map.set(item, TOMBSTONE); - } else { - this.map.delete(item); - } - } - - /** - * @param {K} item the item to test - * @returns {boolean} true if the item exists in this set - */ - has(item) { - const topValue = this.map.get(item); - if (topValue !== undefined) { - return topValue !== TOMBSTONE; - } - if (this.stack.length > 1) { - for (let i = this.stack.length - 2; i >= 0; i--) { - const value = this.stack[i].get(item); - if (value !== undefined) { - this.map.set(item, value); - return value !== TOMBSTONE; - } - } - this.map.set(item, TOMBSTONE); - } - return false; - } - - /** - * @param {K} item the key of the element to return - * @returns {Cell} the value of the element - */ - get(item) { - const topValue = this.map.get(item); - if (topValue !== undefined) { - return topValue === TOMBSTONE || topValue === UNDEFINED_MARKER - ? undefined - : topValue; - } - if (this.stack.length > 1) { - for (let i = this.stack.length - 2; i >= 0; i--) { - const value = this.stack[i].get(item); - if (value !== undefined) { - this.map.set(item, value); - return value === TOMBSTONE || value === UNDEFINED_MARKER - ? undefined - : value; - } - } - this.map.set(item, TOMBSTONE); - } - } - - _compress() { - if (this.stack.length === 1) return; - this.map = new Map(); - for (const data of this.stack) { - for (const pair of data) { - if (pair[1] === TOMBSTONE) { - this.map.delete(pair[0]); - } else { - this.map.set(pair[0], pair[1]); - } - } - } - this.stack = [this.map]; - } - - asArray() { - this._compress(); - return Array.from(this.map.keys()); - } - - asSet() { - this._compress(); - return new Set(this.map.keys()); - } - - asPairArray() { - this._compress(); - return Array.from(this.map.entries(), extractPair); - } - - asMap() { - return new Map(this.asPairArray()); - } - - get size() { - this._compress(); - return this.map.size; - } - - createChild() { - return new StackedMap(this.stack); - } -} - -module.exports = StackedMap; diff --git a/webpack-lib/lib/util/StringXor.js b/webpack-lib/lib/util/StringXor.js deleted file mode 100644 index ea5c8f83544..00000000000 --- a/webpack-lib/lib/util/StringXor.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../util/Hash")} Hash */ - -/** - * StringXor class provides methods for performing - * [XOR operations](https://en.wikipedia.org/wiki/Exclusive_or) on strings. In this context - * we operating on the character codes of two strings, which are represented as - * [Buffer](https://nodejs.org/api/buffer.html) objects. - * - * We use [StringXor in webpack](https://github.com/webpack/webpack/commit/41a8e2ea483a544c4ccd3e6217bdfb80daffca39) - * to create a hash of the current state of the compilation. By XOR'ing the Module hashes, it - * doesn't matter if the Module hashes are sorted or not. This is useful because it allows us to avoid sorting the - * Module hashes. - * @example - * ```js - * const xor = new StringXor(); - * xor.add('hello'); - * xor.add('world'); - * console.log(xor.toString()); - * ``` - * @example - * ```js - * const xor = new StringXor(); - * xor.add('foo'); - * xor.add('bar'); - * const hash = createHash('sha256'); - * hash.update(xor.toString()); - * console.log(hash.digest('hex')); - * ``` - */ -class StringXor { - constructor() { - /** @type {Buffer|undefined} */ - this._value = undefined; - } - - /** - * Adds a string to the current StringXor object. - * @param {string} str string - * @returns {void} - */ - add(str) { - const len = str.length; - const value = this._value; - if (value === undefined) { - /** - * We are choosing to use Buffer.allocUnsafe() because it is often faster than Buffer.alloc() because - * it allocates a new buffer of the specified size without initializing the memory. - */ - const newValue = (this._value = Buffer.allocUnsafe(len)); - for (let i = 0; i < len; i++) { - newValue[i] = str.charCodeAt(i); - } - return; - } - const valueLen = value.length; - if (valueLen < len) { - const newValue = (this._value = Buffer.allocUnsafe(len)); - let i; - for (i = 0; i < valueLen; i++) { - newValue[i] = value[i] ^ str.charCodeAt(i); - } - for (; i < len; i++) { - newValue[i] = str.charCodeAt(i); - } - } else { - for (let i = 0; i < len; i++) { - value[i] = value[i] ^ str.charCodeAt(i); - } - } - } - - /** - * Returns a string that represents the current state of the StringXor object. We chose to use "latin1" encoding - * here because "latin1" encoding is a single-byte encoding that can represent all characters in the - * [ISO-8859-1 character set](https://en.wikipedia.org/wiki/ISO/IEC_8859-1). This is useful when working - * with binary data that needs to be represented as a string. - * @returns {string} Returns a string that represents the current state of the StringXor object. - */ - toString() { - const value = this._value; - return value === undefined ? "" : value.toString("latin1"); - } - - /** - * Updates the hash with the current state of the StringXor object. - * @param {Hash} hash Hash instance - */ - updateHash(hash) { - const value = this._value; - if (value !== undefined) hash.update(value); - } -} - -module.exports = StringXor; diff --git a/webpack-lib/lib/util/TupleQueue.js b/webpack-lib/lib/util/TupleQueue.js deleted file mode 100644 index 6cdd7ea9f2b..00000000000 --- a/webpack-lib/lib/util/TupleQueue.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const TupleSet = require("./TupleSet"); - -/** - * @template {any[]} T - */ -class TupleQueue { - /** - * @param {Iterable=} items The initial elements. - */ - constructor(items) { - /** - * @private - * @type {TupleSet} - */ - this._set = new TupleSet(items); - /** - * @private - * @type {Iterator} - */ - this._iterator = this._set[Symbol.iterator](); - } - - /** - * Returns the number of elements in this queue. - * @returns {number} The number of elements in this queue. - */ - get length() { - return this._set.size; - } - - /** - * Appends the specified element to this queue. - * @param {T} item The element to add. - * @returns {void} - */ - enqueue(...item) { - this._set.add(...item); - } - - /** - * Retrieves and removes the head of this queue. - * @returns {T | undefined} The head of the queue of `undefined` if this queue is empty. - */ - dequeue() { - const result = this._iterator.next(); - if (result.done) { - if (this._set.size > 0) { - this._iterator = this._set[Symbol.iterator](); - const value = this._iterator.next().value; - this._set.delete(...value); - return value; - } - return; - } - this._set.delete(...result.value); - return result.value; - } -} - -module.exports = TupleQueue; diff --git a/webpack-lib/lib/util/TupleSet.js b/webpack-lib/lib/util/TupleSet.js deleted file mode 100644 index 803ae194ec7..00000000000 --- a/webpack-lib/lib/util/TupleSet.js +++ /dev/null @@ -1,160 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template {any[]} T - */ -class TupleSet { - /** - * @param {Iterable=} init init - */ - constructor(init) { - /** @type {Map} */ - this._map = new Map(); - this.size = 0; - if (init) { - for (const tuple of init) { - this.add(...tuple); - } - } - } - - /** - * @param {T} args tuple - * @returns {void} - */ - add(...args) { - let map = this._map; - for (let i = 0; i < args.length - 2; i++) { - const arg = args[i]; - const innerMap = map.get(arg); - if (innerMap === undefined) { - map.set(arg, (map = new Map())); - } else { - map = innerMap; - } - } - - const beforeLast = args[args.length - 2]; - let set = map.get(beforeLast); - if (set === undefined) { - map.set(beforeLast, (set = new Set())); - } - - const last = args[args.length - 1]; - this.size -= set.size; - set.add(last); - this.size += set.size; - } - - /** - * @param {T} args tuple - * @returns {boolean} true, if the tuple is in the Set - */ - has(...args) { - let map = this._map; - for (let i = 0; i < args.length - 2; i++) { - const arg = args[i]; - map = map.get(arg); - if (map === undefined) { - return false; - } - } - - const beforeLast = args[args.length - 2]; - const set = map.get(beforeLast); - if (set === undefined) { - return false; - } - - const last = args[args.length - 1]; - return set.has(last); - } - - /** - * @param {T} args tuple - * @returns {void} - */ - delete(...args) { - let map = this._map; - for (let i = 0; i < args.length - 2; i++) { - const arg = args[i]; - map = map.get(arg); - if (map === undefined) { - return; - } - } - - const beforeLast = args[args.length - 2]; - const set = map.get(beforeLast); - if (set === undefined) { - return; - } - - const last = args[args.length - 1]; - this.size -= set.size; - set.delete(last); - this.size += set.size; - } - - /** - * @returns {Iterator} iterator - */ - [Symbol.iterator]() { - /** @type {TODO[]} */ - const iteratorStack = []; - /** @type {T[]} */ - const tuple = []; - /** @type {Iterator | undefined} */ - let currentSetIterator; - - /** - * @param {TODO} it iterator - * @returns {boolean} result - */ - const next = it => { - const result = it.next(); - if (result.done) { - if (iteratorStack.length === 0) return false; - tuple.pop(); - return next(iteratorStack.pop()); - } - const [key, value] = result.value; - iteratorStack.push(it); - tuple.push(key); - if (value instanceof Set) { - currentSetIterator = value[Symbol.iterator](); - return true; - } - return next(value[Symbol.iterator]()); - }; - - next(this._map[Symbol.iterator]()); - - return { - next() { - while (currentSetIterator) { - const result = currentSetIterator.next(); - if (result.done) { - tuple.pop(); - if (!next(iteratorStack.pop())) { - currentSetIterator = undefined; - } - } else { - return { - done: false, - value: /** @type {T} */ (tuple.concat(result.value)) - }; - } - } - return { done: true, value: undefined }; - } - }; - } -} - -module.exports = TupleSet; diff --git a/webpack-lib/lib/util/URLAbsoluteSpecifier.js b/webpack-lib/lib/util/URLAbsoluteSpecifier.js deleted file mode 100644 index f5cec7e4be0..00000000000 --- a/webpack-lib/lib/util/URLAbsoluteSpecifier.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -/** @typedef {import("./fs").InputFileSystem} InputFileSystem */ -/** @typedef {(error: Error|null, result?: Buffer) => void} ErrorFirstCallback */ - -const backSlashCharCode = "\\".charCodeAt(0); -const slashCharCode = "/".charCodeAt(0); -const aLowerCaseCharCode = "a".charCodeAt(0); -const zLowerCaseCharCode = "z".charCodeAt(0); -const aUpperCaseCharCode = "A".charCodeAt(0); -const zUpperCaseCharCode = "Z".charCodeAt(0); -const _0CharCode = "0".charCodeAt(0); -const _9CharCode = "9".charCodeAt(0); -const plusCharCode = "+".charCodeAt(0); -const hyphenCharCode = "-".charCodeAt(0); -const colonCharCode = ":".charCodeAt(0); -const hashCharCode = "#".charCodeAt(0); -const queryCharCode = "?".charCodeAt(0); -/** - * Get scheme if specifier is an absolute URL specifier - * e.g. Absolute specifiers like 'file:///user/webpack/index.js' - * https://tools.ietf.org/html/rfc3986#section-3.1 - * @param {string} specifier specifier - * @returns {string|undefined} scheme if absolute URL specifier provided - */ -function getScheme(specifier) { - const start = specifier.charCodeAt(0); - - // First char maybe only a letter - if ( - (start < aLowerCaseCharCode || start > zLowerCaseCharCode) && - (start < aUpperCaseCharCode || start > zUpperCaseCharCode) - ) { - return; - } - - let i = 1; - let ch = specifier.charCodeAt(i); - - while ( - (ch >= aLowerCaseCharCode && ch <= zLowerCaseCharCode) || - (ch >= aUpperCaseCharCode && ch <= zUpperCaseCharCode) || - (ch >= _0CharCode && ch <= _9CharCode) || - ch === plusCharCode || - ch === hyphenCharCode - ) { - if (++i === specifier.length) return; - ch = specifier.charCodeAt(i); - } - - // Scheme must end with colon - if (ch !== colonCharCode) return; - - // Check for Windows absolute path - // https://url.spec.whatwg.org/#url-miscellaneous - if (i === 1) { - const nextChar = i + 1 < specifier.length ? specifier.charCodeAt(i + 1) : 0; - if ( - nextChar === 0 || - nextChar === backSlashCharCode || - nextChar === slashCharCode || - nextChar === hashCharCode || - nextChar === queryCharCode - ) { - return; - } - } - - return specifier.slice(0, i).toLowerCase(); -} - -/** - * @param {string} specifier specifier - * @returns {string | null | undefined} protocol if absolute URL specifier provided - */ -function getProtocol(specifier) { - const scheme = getScheme(specifier); - return scheme === undefined ? undefined : `${scheme}:`; -} - -module.exports.getScheme = getScheme; -module.exports.getProtocol = getProtocol; diff --git a/webpack-lib/lib/util/WeakTupleMap.js b/webpack-lib/lib/util/WeakTupleMap.js deleted file mode 100644 index ac64e8695df..00000000000 --- a/webpack-lib/lib/util/WeakTupleMap.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template {any[]} T - * @template V - * @typedef {Map>} M - */ -/** - * @template {any[]} T - * @template V - * @typedef {WeakMap>} W - */ - -/** - * @param {any} thing thing - * @returns {boolean} true if is weak - */ -const isWeakKey = thing => typeof thing === "object" && thing !== null; - -/** - * @template {any[]} T - * @template V - */ -class WeakTupleMap { - constructor() { - /** @private */ - this.f = 0; - /** - * @private - * @type {any} - */ - this.v = undefined; - /** - * @private - * @type {M | undefined} - */ - this.m = undefined; - /** - * @private - * @type {W | undefined} - */ - this.w = undefined; - } - - /** - * @param {[...T, V]} args tuple - * @returns {void} - */ - set(...args) { - /** @type {WeakTupleMap} */ - let node = this; - for (let i = 0; i < args.length - 1; i++) { - node = node._get(args[i]); - } - node._setValue(args[args.length - 1]); - } - - /** - * @param {T} args tuple - * @returns {boolean} true, if the tuple is in the Set - */ - has(...args) { - /** @type {WeakTupleMap | undefined} */ - let node = this; - for (let i = 0; i < args.length; i++) { - node = node._peek(args[i]); - if (node === undefined) return false; - } - return node._hasValue(); - } - - /** - * @param {T} args tuple - * @returns {V | undefined} the value - */ - get(...args) { - /** @type {WeakTupleMap | undefined} */ - let node = this; - for (let i = 0; i < args.length; i++) { - node = node._peek(args[i]); - if (node === undefined) return; - } - return node._getValue(); - } - - /** - * @param {[...T, function(): V]} args tuple - * @returns {V} the value - */ - provide(...args) { - /** @type {WeakTupleMap} */ - let node = this; - for (let i = 0; i < args.length - 1; i++) { - node = node._get(args[i]); - } - if (node._hasValue()) return node._getValue(); - const fn = args[args.length - 1]; - const newValue = fn(...args.slice(0, -1)); - node._setValue(newValue); - return newValue; - } - - /** - * @param {T} args tuple - * @returns {void} - */ - delete(...args) { - /** @type {WeakTupleMap | undefined} */ - let node = this; - for (let i = 0; i < args.length; i++) { - node = node._peek(args[i]); - if (node === undefined) return; - } - node._deleteValue(); - } - - /** - * @returns {void} - */ - clear() { - this.f = 0; - this.v = undefined; - this.w = undefined; - this.m = undefined; - } - - _getValue() { - return this.v; - } - - _hasValue() { - return (this.f & 1) === 1; - } - - /** - * @param {any} v value - * @private - */ - _setValue(v) { - this.f |= 1; - this.v = v; - } - - _deleteValue() { - this.f &= 6; - this.v = undefined; - } - - /** - * @param {any} thing thing - * @returns {WeakTupleMap | undefined} thing - * @private - */ - _peek(thing) { - if (isWeakKey(thing)) { - if ((this.f & 4) !== 4) return; - return /** @type {W} */ (this.w).get(thing); - } - if ((this.f & 2) !== 2) return; - return /** @type {M} */ (this.m).get(thing); - } - - /** - * @private - * @param {any} thing thing - * @returns {WeakTupleMap} value - */ - _get(thing) { - if (isWeakKey(thing)) { - if ((this.f & 4) !== 4) { - const newMap = new WeakMap(); - this.f |= 4; - const newNode = new WeakTupleMap(); - (this.w = newMap).set(thing, newNode); - return newNode; - } - const entry = - /** @type {W} */ - (this.w).get(thing); - if (entry !== undefined) { - return entry; - } - const newNode = new WeakTupleMap(); - /** @type {W} */ - (this.w).set(thing, newNode); - return newNode; - } - if ((this.f & 2) !== 2) { - const newMap = new Map(); - this.f |= 2; - const newNode = new WeakTupleMap(); - (this.m = newMap).set(thing, newNode); - return newNode; - } - const entry = - /** @type {M} */ - (this.m).get(thing); - if (entry !== undefined) { - return entry; - } - const newNode = new WeakTupleMap(); - /** @type {M} */ - (this.m).set(thing, newNode); - return newNode; - } -} - -module.exports = WeakTupleMap; diff --git a/webpack-lib/lib/util/binarySearchBounds.js b/webpack-lib/lib/util/binarySearchBounds.js deleted file mode 100644 index c61623c1bf5..00000000000 --- a/webpack-lib/lib/util/binarySearchBounds.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Mikola Lysenko @mikolalysenko -*/ - -"use strict"; - -/* cspell:disable-next-line */ -// Refactor: Peter Somogyvari @petermetz - -/** @typedef {">=" | "<=" | "<" | ">" | "-" } BinarySearchPredicate */ -/** @typedef {"GE" | "GT" | "LT" | "LE" | "EQ" } SearchPredicateSuffix */ - -/** - * Helper function for compiling binary search functions. - * - * The generated code uses a while loop to repeatedly divide the search interval - * in half until the desired element is found, or the search interval is empty. - * - * The following is an example of a generated function for calling `compileSearch("P", "c(x,y)<=0", true, ["y", "c"], false)`: - * - * ```js - * function P(a,l,h,y,c){var i=l-1;while(l<=h){var m=(l+h)>>>1,x=a[m];if(c(x,y)<=0){i=m;l=m+1}else{h=m-1}}return i}; - * ``` - * @param {string} funcName The name of the function to be compiled. - * @param {string} predicate The predicate / comparison operator to be used in the binary search. - * @param {boolean} reversed Whether the search should be reversed. - * @param {string[]} extraArgs Extra arguments to be passed to the function. - * @param {boolean=} earlyOut Whether the search should return as soon as a match is found. - * @returns {string} The compiled binary search function. - */ -const compileSearch = (funcName, predicate, reversed, extraArgs, earlyOut) => { - const code = [ - "function ", - funcName, - "(a,l,h,", - extraArgs.join(","), - "){", - earlyOut ? "" : "var i=", - reversed ? "l-1" : "h+1", - ";while(l<=h){var m=(l+h)>>>1,x=a[m]" - ]; - - if (earlyOut) { - if (!predicate.includes("c")) { - code.push(";if(x===y){return m}else if(x<=y){"); - } else { - code.push(";var p=c(x,y);if(p===0){return m}else if(p<=0){"); - } - } else { - code.push(";if(", predicate, "){i=m;"); - } - if (reversed) { - code.push("l=m+1}else{h=m-1}"); - } else { - code.push("h=m-1}else{l=m+1}"); - } - code.push("}"); - if (earlyOut) { - code.push("return -1};"); - } else { - code.push("return i};"); - } - return code.join(""); -}; - -/** - * This helper functions generate code for two binary search functions: - * A(): Performs a binary search on an array using the comparison operator specified. - * P(): Performs a binary search on an array using a _custom comparison function_ - * `c(x,y)` **and** comparison operator specified by `predicate`. - * @param {BinarySearchPredicate} predicate The predicate / comparison operator to be used in the binary search. - * @param {boolean} reversed Whether the search should be reversed. - * @param {SearchPredicateSuffix} suffix The suffix to be used in the function name. - * @param {boolean=} earlyOut Whether the search should return as soon as a match is found. - * @returns {Function} The compiled binary search function. - */ -const compileBoundsSearch = (predicate, reversed, suffix, earlyOut) => { - const arg1 = compileSearch("A", `x${predicate}y`, reversed, ["y"], earlyOut); - - const arg2 = compileSearch( - "P", - `c(x,y)${predicate}0`, - reversed, - ["y", "c"], - earlyOut - ); - - const fnHeader = "function dispatchBinarySearch"; - - const fnBody = - // eslint-disable-next-line no-multi-str - "(a,y,c,l,h){\ -if(typeof(c)==='function'){\ -return P(a,(l===void 0)?0:l|0,(h===void 0)?a.length-1:h|0,y,c)\ -}else{\ -return A(a,(c===void 0)?0:c|0,(l===void 0)?a.length-1:l|0,y)\ -}}\ -return dispatchBinarySearch"; - - const fnArgList = [arg1, arg2, fnHeader, suffix, fnBody, suffix]; - const fnSource = fnArgList.join(""); - // eslint-disable-next-line no-new-func - const result = new Function(fnSource); - return result(); -}; - -/** - * These functions are used to perform binary searches on arrays. - * @example - * ```js - * const { gt, le} = require("./binarySearchBounds"); - * const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - * - * // Find the index of the first element greater than 5 - * const index1 = gt(arr, 5); // index1 === 3 - * - * // Find the index of the first element less than or equal to 5 - * const index2 = le(arr, 5); // index2 === 4 - * ``` - */ -module.exports = { - ge: compileBoundsSearch(">=", false, "GE"), - gt: compileBoundsSearch(">", false, "GT"), - lt: compileBoundsSearch("<", true, "LT"), - le: compileBoundsSearch("<=", true, "LE"), - eq: compileBoundsSearch("-", true, "EQ", true) -}; diff --git a/webpack-lib/lib/util/chainedImports.js b/webpack-lib/lib/util/chainedImports.js deleted file mode 100644 index 295233b7d1c..00000000000 --- a/webpack-lib/lib/util/chainedImports.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ - -/** - * @summary Get the subset of ids and their corresponding range in an id chain that should be re-rendered by webpack. - * Only those in the chain that are actually referring to namespaces or imports should be re-rendered. - * Deeper member accessors on the imported object should not be re-rendered. If deeper member accessors are re-rendered, - * there is a potential loss of meaning with rendering a quoted accessor as an unquoted accessor, or vice versa, - * because minifiers treat quoted accessors differently. e.g. import { a } from "./module"; a["b"] vs a.b - * @param {string[]} untrimmedIds chained ids - * @param {Range} untrimmedRange range encompassing allIds - * @param {Range[] | undefined} ranges cumulative range of ids for each of allIds - * @param {ModuleGraph} moduleGraph moduleGraph - * @param {Dependency} dependency dependency - * @returns {{trimmedIds: string[], trimmedRange: Range}} computed trimmed ids and cumulative range of those ids - */ -module.exports.getTrimmedIdsAndRange = ( - untrimmedIds, - untrimmedRange, - ranges, - moduleGraph, - dependency -) => { - let trimmedIds = trimIdsToThoseImported( - untrimmedIds, - moduleGraph, - dependency - ); - let trimmedRange = untrimmedRange; - if (trimmedIds.length !== untrimmedIds.length) { - // The array returned from dep.idRanges is right-aligned with the array returned from dep.names. - // Meaning, the two arrays may not always have the same number of elements, but the last element of - // dep.idRanges corresponds to [the expression fragment to the left of] the last element of dep.names. - // Use this to find the correct replacement range based on the number of ids that were trimmed. - const idx = - ranges === undefined - ? -1 /* trigger failure case below */ - : ranges.length + (trimmedIds.length - untrimmedIds.length); - if (idx < 0 || idx >= /** @type {Range[]} */ (ranges).length) { - // cspell:ignore minifiers - // Should not happen but we can't throw an error here because of backward compatibility with - // external plugins in wp5. Instead, we just disable trimming for now. This may break some minifiers. - trimmedIds = untrimmedIds; - // TODO webpack 6 remove the "trimmedIds = ids" above and uncomment the following line instead. - // throw new Error("Missing range starts data for id replacement trimming."); - } else { - trimmedRange = /** @type {Range[]} */ (ranges)[idx]; - } - } - - return { trimmedIds, trimmedRange }; -}; - -/** - * @summary Determine which IDs in the id chain are actually referring to namespaces or imports, - * and which are deeper member accessors on the imported object. - * @param {string[]} ids untrimmed ids - * @param {ModuleGraph} moduleGraph moduleGraph - * @param {Dependency} dependency dependency - * @returns {string[]} trimmed ids - */ -function trimIdsToThoseImported(ids, moduleGraph, dependency) { - /** @type {string[]} */ - let trimmedIds = []; - let currentExportsInfo = moduleGraph.getExportsInfo( - /** @type {Module} */ (moduleGraph.getModule(dependency)) - ); - for (let i = 0; i < ids.length; i++) { - if (i === 0 && ids[i] === "default") { - continue; // ExportInfo for the next level under default is still at the root ExportsInfo, so don't advance currentExportsInfo - } - const exportInfo = currentExportsInfo.getExportInfo(ids[i]); - if (exportInfo.provided === false) { - // json imports have nested ExportInfo for elements that things that are not actually exported, so check .provided - trimmedIds = ids.slice(0, i); - break; - } - const nestedInfo = exportInfo.getNestedExportsInfo(); - if (!nestedInfo) { - // once all nested exports are traversed, the next item is the actual import so stop there - trimmedIds = ids.slice(0, i + 1); - break; - } - currentExportsInfo = nestedInfo; - } - // Never trim to nothing. This can happen for invalid imports (e.g. import { notThere } from "./module", or import { anything } from "./missingModule") - return trimmedIds.length ? trimmedIds : ids; -} diff --git a/webpack-lib/lib/util/cleverMerge.js b/webpack-lib/lib/util/cleverMerge.js deleted file mode 100644 index 14852011b44..00000000000 --- a/webpack-lib/lib/util/cleverMerge.js +++ /dev/null @@ -1,607 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @type {WeakMap>} */ -const mergeCache = new WeakMap(); -/** @type {WeakMap>>} */ -const setPropertyCache = new WeakMap(); -const DELETE = Symbol("DELETE"); -const DYNAMIC_INFO = Symbol("cleverMerge dynamic info"); - -/** - * Merges two given objects and caches the result to avoid computation if same objects passed as arguments again. - * @template T - * @template O - * @example - * // performs cleverMerge(first, second), stores the result in WeakMap and returns result - * cachedCleverMerge({a: 1}, {a: 2}) - * {a: 2} - * // when same arguments passed, gets the result from WeakMap and returns it. - * cachedCleverMerge({a: 1}, {a: 2}) - * {a: 2} - * @param {T | null | undefined} first first object - * @param {O | null | undefined} second second object - * @returns {T & O | T | O} merged object of first and second object - */ -const cachedCleverMerge = (first, second) => { - if (second === undefined) return /** @type {T} */ (first); - if (first === undefined) return /** @type {O} */ (second); - if (typeof second !== "object" || second === null) - return /** @type {O} */ (second); - if (typeof first !== "object" || first === null) - return /** @type {T} */ (first); - - let innerCache = mergeCache.get(first); - if (innerCache === undefined) { - innerCache = new WeakMap(); - mergeCache.set(first, innerCache); - } - const prevMerge = /** @type {T & O} */ (innerCache.get(second)); - if (prevMerge !== undefined) return prevMerge; - const newMerge = _cleverMerge(first, second, true); - innerCache.set(second, newMerge); - return /** @type {T & O} */ (newMerge); -}; - -/** - * @template T - * @param {Partial} obj object - * @param {string} property property - * @param {string|number|boolean} value assignment value - * @returns {T} new object - */ -const cachedSetProperty = (obj, property, value) => { - let mapByProperty = setPropertyCache.get(obj); - - if (mapByProperty === undefined) { - mapByProperty = new Map(); - setPropertyCache.set(obj, mapByProperty); - } - - let mapByValue = mapByProperty.get(property); - - if (mapByValue === undefined) { - mapByValue = new Map(); - mapByProperty.set(property, mapByValue); - } - - let result = mapByValue.get(value); - - if (result) return /** @type {T} */ (result); - - result = { - ...obj, - [property]: value - }; - mapByValue.set(value, result); - - return /** @type {T} */ (result); -}; - -/** @typedef {Map} ByValues */ - -/** - * @typedef {object} ObjectParsedPropertyEntry - * @property {any | undefined} base base value - * @property {string | undefined} byProperty the name of the selector property - * @property {ByValues} byValues value depending on selector property, merged with base - */ - -/** - * @typedef {object} ParsedObject - * @property {Map} static static properties (key is property name) - * @property {{ byProperty: string, fn: Function } | undefined} dynamic dynamic part - */ - -/** @type {WeakMap} */ -const parseCache = new WeakMap(); - -/** - * @param {object} obj the object - * @returns {ParsedObject} parsed object - */ -const cachedParseObject = obj => { - const entry = parseCache.get(obj); - if (entry !== undefined) return entry; - const result = parseObject(obj); - parseCache.set(obj, result); - return result; -}; - -/** - * @template {object} T - * @param {T} obj the object - * @returns {ParsedObject} parsed object - */ -const parseObject = obj => { - const info = new Map(); - let dynamicInfo; - /** - * @param {string} p path - * @returns {Partial} object parsed property entry - */ - const getInfo = p => { - const entry = info.get(p); - if (entry !== undefined) return entry; - const newEntry = { - base: undefined, - byProperty: undefined, - byValues: undefined - }; - info.set(p, newEntry); - return newEntry; - }; - for (const key of Object.keys(obj)) { - if (key.startsWith("by")) { - const byProperty = /** @type {keyof T} */ (key); - const byObj = /** @type {object} */ (obj[byProperty]); - if (typeof byObj === "object") { - for (const byValue of Object.keys(byObj)) { - const obj = byObj[/** @type {keyof (keyof T)} */ (byValue)]; - for (const key of Object.keys(obj)) { - const entry = getInfo(key); - if (entry.byProperty === undefined) { - entry.byProperty = /** @type {string} */ (byProperty); - entry.byValues = new Map(); - } else if (entry.byProperty !== byProperty) { - throw new Error( - `${/** @type {string} */ (byProperty)} and ${entry.byProperty} for a single property is not supported` - ); - } - /** @type {ByValues} */ - (entry.byValues).set( - byValue, - obj[/** @type {keyof (keyof T)} */ (key)] - ); - if (byValue === "default") { - for (const otherByValue of Object.keys(byObj)) { - if ( - !(/** @type {ByValues} */ (entry.byValues).has(otherByValue)) - ) - /** @type {ByValues} */ - (entry.byValues).set(otherByValue, undefined); - } - } - } - } - } else if (typeof byObj === "function") { - if (dynamicInfo === undefined) { - dynamicInfo = { - byProperty: key, - fn: byObj - }; - } else { - throw new Error( - `${key} and ${dynamicInfo.byProperty} when both are functions is not supported` - ); - } - } else { - const entry = getInfo(key); - entry.base = obj[/** @type {keyof T} */ (key)]; - } - } else { - const entry = getInfo(key); - entry.base = obj[/** @type {keyof T} */ (key)]; - } - } - return { - static: info, - dynamic: dynamicInfo - }; -}; - -/** - * @template {object} T - * @param {Map} info static properties (key is property name) - * @param {{ byProperty: string, fn: Function } | undefined} dynamicInfo dynamic part - * @returns {T} the object - */ -const serializeObject = (info, dynamicInfo) => { - const obj = /** @type {T} */ ({}); - // Setup byProperty structure - for (const entry of info.values()) { - if (entry.byProperty !== undefined) { - const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); - for (const byValue of entry.byValues.keys()) { - byObj[byValue] = byObj[byValue] || {}; - } - } - } - for (const [key, entry] of info) { - if (entry.base !== undefined) { - obj[/** @type {keyof T} */ (key)] = entry.base; - } - // Fill byProperty structure - if (entry.byProperty !== undefined) { - const byObj = (obj[entry.byProperty] = obj[entry.byProperty] || {}); - for (const byValue of Object.keys(byObj)) { - const value = getFromByValues(entry.byValues, byValue); - if (value !== undefined) byObj[byValue][key] = value; - } - } - } - if (dynamicInfo !== undefined) { - obj[dynamicInfo.byProperty] = dynamicInfo.fn; - } - return obj; -}; - -const VALUE_TYPE_UNDEFINED = 0; -const VALUE_TYPE_ATOM = 1; -const VALUE_TYPE_ARRAY_EXTEND = 2; -const VALUE_TYPE_OBJECT = 3; -const VALUE_TYPE_DELETE = 4; - -/** - * @param {any} value a single value - * @returns {VALUE_TYPE_UNDEFINED | VALUE_TYPE_ATOM | VALUE_TYPE_ARRAY_EXTEND | VALUE_TYPE_OBJECT | VALUE_TYPE_DELETE} value type - */ -const getValueType = value => { - if (value === undefined) { - return VALUE_TYPE_UNDEFINED; - } else if (value === DELETE) { - return VALUE_TYPE_DELETE; - } else if (Array.isArray(value)) { - if (value.includes("...")) return VALUE_TYPE_ARRAY_EXTEND; - return VALUE_TYPE_ATOM; - } else if ( - typeof value === "object" && - value !== null && - (!value.constructor || value.constructor === Object) - ) { - return VALUE_TYPE_OBJECT; - } - return VALUE_TYPE_ATOM; -}; - -/** - * Merges two objects. Objects are deeply clever merged. - * Arrays might reference the old value with "...". - * Non-object values take preference over object values. - * @template T - * @template O - * @param {T} first first object - * @param {O} second second object - * @returns {T & O | T | O} merged object of first and second object - */ -const cleverMerge = (first, second) => { - if (second === undefined) return first; - if (first === undefined) return second; - if (typeof second !== "object" || second === null) return second; - if (typeof first !== "object" || first === null) return first; - - return /** @type {T & O} */ (_cleverMerge(first, second, false)); -}; - -/** - * Merges two objects. Objects are deeply clever merged. - * @param {object} first first object - * @param {object} second second object - * @param {boolean} internalCaching should parsing of objects and nested merges be cached - * @returns {object} merged object of first and second object - */ -const _cleverMerge = (first, second, internalCaching = false) => { - const firstObject = internalCaching - ? cachedParseObject(first) - : parseObject(first); - const { static: firstInfo, dynamic: firstDynamicInfo } = firstObject; - - // If the first argument has a dynamic part we modify the dynamic part to merge the second argument - if (firstDynamicInfo !== undefined) { - let { byProperty, fn } = firstDynamicInfo; - const fnInfo = fn[DYNAMIC_INFO]; - if (fnInfo) { - second = internalCaching - ? cachedCleverMerge(fnInfo[1], second) - : cleverMerge(fnInfo[1], second); - fn = fnInfo[0]; - } - const newFn = (...args) => { - const fnResult = fn(...args); - return internalCaching - ? cachedCleverMerge(fnResult, second) - : cleverMerge(fnResult, second); - }; - newFn[DYNAMIC_INFO] = [fn, second]; - return serializeObject(firstObject.static, { byProperty, fn: newFn }); - } - - // If the first part is static only, we merge the static parts and keep the dynamic part of the second argument - const secondObject = internalCaching - ? cachedParseObject(second) - : parseObject(second); - const { static: secondInfo, dynamic: secondDynamicInfo } = secondObject; - /** @type {Map} */ - const resultInfo = new Map(); - for (const [key, firstEntry] of firstInfo) { - const secondEntry = secondInfo.get(key); - const entry = - secondEntry !== undefined - ? mergeEntries(firstEntry, secondEntry, internalCaching) - : firstEntry; - resultInfo.set(key, entry); - } - for (const [key, secondEntry] of secondInfo) { - if (!firstInfo.has(key)) { - resultInfo.set(key, secondEntry); - } - } - return serializeObject(resultInfo, secondDynamicInfo); -}; - -/** - * @param {ObjectParsedPropertyEntry} firstEntry a - * @param {ObjectParsedPropertyEntry} secondEntry b - * @param {boolean} internalCaching should parsing of objects and nested merges be cached - * @returns {ObjectParsedPropertyEntry} new entry - */ -const mergeEntries = (firstEntry, secondEntry, internalCaching) => { - switch (getValueType(secondEntry.base)) { - case VALUE_TYPE_ATOM: - case VALUE_TYPE_DELETE: - // No need to consider firstEntry at all - // second value override everything - // = second.base + second.byProperty - return secondEntry; - case VALUE_TYPE_UNDEFINED: - if (!firstEntry.byProperty) { - // = first.base + second.byProperty - return { - base: firstEntry.base, - byProperty: secondEntry.byProperty, - byValues: secondEntry.byValues - }; - } else if (firstEntry.byProperty !== secondEntry.byProperty) { - throw new Error( - `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` - ); - } else { - // = first.base + (first.byProperty + second.byProperty) - // need to merge first and second byValues - const newByValues = new Map(firstEntry.byValues); - for (const [key, value] of secondEntry.byValues) { - const firstValue = getFromByValues(firstEntry.byValues, key); - newByValues.set( - key, - mergeSingleValue(firstValue, value, internalCaching) - ); - } - return { - base: firstEntry.base, - byProperty: firstEntry.byProperty, - byValues: newByValues - }; - } - default: { - if (!firstEntry.byProperty) { - // The simple case - // = (first.base + second.base) + second.byProperty - return { - base: mergeSingleValue( - firstEntry.base, - secondEntry.base, - internalCaching - ), - byProperty: secondEntry.byProperty, - byValues: secondEntry.byValues - }; - } - let newBase; - const intermediateByValues = new Map(firstEntry.byValues); - for (const [key, value] of intermediateByValues) { - intermediateByValues.set( - key, - mergeSingleValue(value, secondEntry.base, internalCaching) - ); - } - if ( - Array.from(firstEntry.byValues.values()).every(value => { - const type = getValueType(value); - return type === VALUE_TYPE_ATOM || type === VALUE_TYPE_DELETE; - }) - ) { - // = (first.base + second.base) + ((first.byProperty + second.base) + second.byProperty) - newBase = mergeSingleValue( - firstEntry.base, - secondEntry.base, - internalCaching - ); - } else { - // = first.base + ((first.byProperty (+default) + second.base) + second.byProperty) - newBase = firstEntry.base; - if (!intermediateByValues.has("default")) - intermediateByValues.set("default", secondEntry.base); - } - if (!secondEntry.byProperty) { - // = first.base + (first.byProperty + second.base) - return { - base: newBase, - byProperty: firstEntry.byProperty, - byValues: intermediateByValues - }; - } else if (firstEntry.byProperty !== secondEntry.byProperty) { - throw new Error( - `${firstEntry.byProperty} and ${secondEntry.byProperty} for a single property is not supported` - ); - } - const newByValues = new Map(intermediateByValues); - for (const [key, value] of secondEntry.byValues) { - const firstValue = getFromByValues(intermediateByValues, key); - newByValues.set( - key, - mergeSingleValue(firstValue, value, internalCaching) - ); - } - return { - base: newBase, - byProperty: firstEntry.byProperty, - byValues: newByValues - }; - } - } -}; - -/** - * @param {Map} byValues all values - * @param {string} key value of the selector - * @returns {any | undefined} value - */ -const getFromByValues = (byValues, key) => { - if (key !== "default" && byValues.has(key)) { - return byValues.get(key); - } - return byValues.get("default"); -}; - -/** - * @param {any} a value - * @param {any} b value - * @param {boolean} internalCaching should parsing of objects and nested merges be cached - * @returns {any} value - */ -const mergeSingleValue = (a, b, internalCaching) => { - const bType = getValueType(b); - const aType = getValueType(a); - switch (bType) { - case VALUE_TYPE_DELETE: - case VALUE_TYPE_ATOM: - return b; - case VALUE_TYPE_OBJECT: { - return aType !== VALUE_TYPE_OBJECT - ? b - : internalCaching - ? cachedCleverMerge(a, b) - : cleverMerge(a, b); - } - case VALUE_TYPE_UNDEFINED: - return a; - case VALUE_TYPE_ARRAY_EXTEND: - switch ( - aType !== VALUE_TYPE_ATOM - ? aType - : Array.isArray(a) - ? VALUE_TYPE_ARRAY_EXTEND - : VALUE_TYPE_OBJECT - ) { - case VALUE_TYPE_UNDEFINED: - return b; - case VALUE_TYPE_DELETE: - return /** @type {any[]} */ (b).filter(item => item !== "..."); - case VALUE_TYPE_ARRAY_EXTEND: { - const newArray = []; - for (const item of b) { - if (item === "...") { - for (const item of a) { - newArray.push(item); - } - } else { - newArray.push(item); - } - } - return newArray; - } - case VALUE_TYPE_OBJECT: - return /** @type {any[]} */ (b).map(item => - item === "..." ? a : item - ); - default: - throw new Error("Not implemented"); - } - default: - throw new Error("Not implemented"); - } -}; - -/** - * @template {object} T - * @param {T} obj the object - * @param {(keyof T)[]=} keysToKeepOriginalValue keys to keep original value - * @returns {T} the object without operations like "..." or DELETE - */ -const removeOperations = (obj, keysToKeepOriginalValue = []) => { - const newObj = /** @type {T} */ ({}); - for (const key of Object.keys(obj)) { - const value = obj[/** @type {keyof T} */ (key)]; - const type = getValueType(value); - if ( - type === VALUE_TYPE_OBJECT && - keysToKeepOriginalValue.includes(/** @type {keyof T} */ (key)) - ) { - newObj[/** @type {keyof T} */ (key)] = value; - continue; - } - switch (type) { - case VALUE_TYPE_UNDEFINED: - case VALUE_TYPE_DELETE: - break; - case VALUE_TYPE_OBJECT: - newObj[/** @type {keyof T} */ (key)] = - /** @type {T[keyof T]} */ - ( - removeOperations( - /** @type {TODO} */ (value), - keysToKeepOriginalValue - ) - ); - break; - case VALUE_TYPE_ARRAY_EXTEND: - newObj[/** @type {keyof T} */ (key)] = - /** @type {T[keyof T]} */ - ( - /** @type {any[]} */ - (value).filter(i => i !== "...") - ); - break; - default: - newObj[/** @type {keyof T} */ (key)] = value; - break; - } - } - return newObj; -}; - -/** - * @template T - * @template {string} P - * @param {T} obj the object - * @param {P} byProperty the by description - * @param {...any} values values - * @returns {Omit} object with merged byProperty - */ -const resolveByProperty = (obj, byProperty, ...values) => { - if (typeof obj !== "object" || obj === null || !(byProperty in obj)) { - return obj; - } - const { [byProperty]: _byValue, ..._remaining } = obj; - const remaining = /** @type {T} */ (_remaining); - const byValue = - /** @type {Record | function(...any[]): T} */ - (_byValue); - if (typeof byValue === "object") { - const key = values[0]; - if (key in byValue) { - return cachedCleverMerge(remaining, byValue[key]); - } else if ("default" in byValue) { - return cachedCleverMerge(remaining, byValue.default); - } - return remaining; - } else if (typeof byValue === "function") { - // eslint-disable-next-line prefer-spread - const result = byValue.apply(null, values); - return cachedCleverMerge( - remaining, - resolveByProperty(result, byProperty, ...values) - ); - } -}; - -module.exports.cachedSetProperty = cachedSetProperty; -module.exports.cachedCleverMerge = cachedCleverMerge; -module.exports.cleverMerge = cleverMerge; -module.exports.resolveByProperty = resolveByProperty; -module.exports.removeOperations = removeOperations; -module.exports.DELETE = DELETE; diff --git a/webpack-lib/lib/util/comparators.js b/webpack-lib/lib/util/comparators.js deleted file mode 100644 index 8d228026e4f..00000000000 --- a/webpack-lib/lib/util/comparators.js +++ /dev/null @@ -1,522 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { compareRuntime } = require("./runtime"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Chunk").ChunkId} ChunkId */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../ChunkGroup")} ChunkGroup */ -/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ - -/** - * @template T - * @typedef {function(T, T): -1|0|1} Comparator - */ -/** - * @template TArg - * @template T - * @typedef {function(TArg, T, T): -1|0|1} RawParameterizedComparator - */ -/** - * @template TArg - * @template T - * @typedef {function(TArg): Comparator} ParameterizedComparator - */ - -/** - * @template T - * @param {RawParameterizedComparator} fn comparator with argument - * @returns {ParameterizedComparator} comparator - */ -const createCachedParameterizedComparator = fn => { - /** @type {WeakMap>} */ - const map = new WeakMap(); - return arg => { - const cachedResult = map.get(arg); - if (cachedResult !== undefined) return cachedResult; - /** - * @param {T} a first item - * @param {T} b second item - * @returns {-1|0|1} compare result - */ - const result = fn.bind(null, arg); - map.set(arg, result); - return result; - }; -}; - -/** - * @param {Chunk} a chunk - * @param {Chunk} b chunk - * @returns {-1|0|1} compare result - */ -module.exports.compareChunksById = (a, b) => - compareIds(/** @type {ChunkId} */ (a.id), /** @type {ChunkId} */ (b.id)); - -/** - * @param {Module} a module - * @param {Module} b module - * @returns {-1|0|1} compare result - */ -module.exports.compareModulesByIdentifier = (a, b) => - compareIds(a.identifier(), b.identifier()); - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Module} a module - * @param {Module} b module - * @returns {-1|0|1} compare result - */ -const compareModulesById = (chunkGraph, a, b) => - compareIds( - /** @type {ModuleId} */ (chunkGraph.getModuleId(a)), - /** @type {ModuleId} */ (chunkGraph.getModuleId(b)) - ); -/** @type {ParameterizedComparator} */ -module.exports.compareModulesById = - createCachedParameterizedComparator(compareModulesById); - -/** - * @param {number} a number - * @param {number} b number - * @returns {-1|0|1} compare result - */ -const compareNumbers = (a, b) => { - if (typeof a !== typeof b) { - return typeof a < typeof b ? -1 : 1; - } - if (a < b) return -1; - if (a > b) return 1; - return 0; -}; -module.exports.compareNumbers = compareNumbers; - -/** - * @param {string} a string - * @param {string} b string - * @returns {-1|0|1} compare result - */ -const compareStringsNumeric = (a, b) => { - const aLength = a.length; - const bLength = b.length; - - let aChar = 0; - let bChar = 0; - - let aIsDigit = false; - let bIsDigit = false; - let i = 0; - let j = 0; - while (i < aLength && j < bLength) { - aChar = a.charCodeAt(i); - bChar = b.charCodeAt(j); - - aIsDigit = aChar >= 48 && aChar <= 57; - bIsDigit = bChar >= 48 && bChar <= 57; - - if (!aIsDigit && !bIsDigit) { - if (aChar < bChar) return -1; - if (aChar > bChar) return 1; - i++; - j++; - } else if (aIsDigit && !bIsDigit) { - // This segment of a is shorter than in b - return 1; - } else if (!aIsDigit && bIsDigit) { - // This segment of b is shorter than in a - return -1; - } else { - let aNumber = aChar - 48; - let bNumber = bChar - 48; - - while (++i < aLength) { - aChar = a.charCodeAt(i); - if (aChar < 48 || aChar > 57) break; - aNumber = aNumber * 10 + aChar - 48; - } - - while (++j < bLength) { - bChar = b.charCodeAt(j); - if (bChar < 48 || bChar > 57) break; - bNumber = bNumber * 10 + bChar - 48; - } - - if (aNumber < bNumber) return -1; - if (aNumber > bNumber) return 1; - } - } - - if (j < bLength) { - // a is shorter than b - bChar = b.charCodeAt(j); - bIsDigit = bChar >= 48 && bChar <= 57; - return bIsDigit ? -1 : 1; - } - if (i < aLength) { - // b is shorter than a - aChar = a.charCodeAt(i); - aIsDigit = aChar >= 48 && aChar <= 57; - return aIsDigit ? 1 : -1; - } - - return 0; -}; -module.exports.compareStringsNumeric = compareStringsNumeric; - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {Module} a module - * @param {Module} b module - * @returns {-1|0|1} compare result - */ -const compareModulesByPostOrderIndexOrIdentifier = (moduleGraph, a, b) => { - const cmp = compareNumbers( - /** @type {number} */ (moduleGraph.getPostOrderIndex(a)), - /** @type {number} */ (moduleGraph.getPostOrderIndex(b)) - ); - if (cmp !== 0) return cmp; - return compareIds(a.identifier(), b.identifier()); -}; -/** @type {ParameterizedComparator} */ -module.exports.compareModulesByPostOrderIndexOrIdentifier = - createCachedParameterizedComparator( - compareModulesByPostOrderIndexOrIdentifier - ); - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {Module} a module - * @param {Module} b module - * @returns {-1|0|1} compare result - */ -const compareModulesByPreOrderIndexOrIdentifier = (moduleGraph, a, b) => { - const cmp = compareNumbers( - /** @type {number} */ (moduleGraph.getPreOrderIndex(a)), - /** @type {number} */ (moduleGraph.getPreOrderIndex(b)) - ); - if (cmp !== 0) return cmp; - return compareIds(a.identifier(), b.identifier()); -}; -/** @type {ParameterizedComparator} */ -module.exports.compareModulesByPreOrderIndexOrIdentifier = - createCachedParameterizedComparator( - compareModulesByPreOrderIndexOrIdentifier - ); - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Module} a module - * @param {Module} b module - * @returns {-1|0|1} compare result - */ -const compareModulesByIdOrIdentifier = (chunkGraph, a, b) => { - const cmp = compareIds( - /** @type {ModuleId} */ (chunkGraph.getModuleId(a)), - /** @type {ModuleId} */ (chunkGraph.getModuleId(b)) - ); - if (cmp !== 0) return cmp; - return compareIds(a.identifier(), b.identifier()); -}; -/** @type {ParameterizedComparator} */ -module.exports.compareModulesByIdOrIdentifier = - createCachedParameterizedComparator(compareModulesByIdOrIdentifier); - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Chunk} a chunk - * @param {Chunk} b chunk - * @returns {-1|0|1} compare result - */ -const compareChunks = (chunkGraph, a, b) => chunkGraph.compareChunks(a, b); -/** @type {ParameterizedComparator} */ -module.exports.compareChunks = - createCachedParameterizedComparator(compareChunks); - -/** - * @param {string|number} a first id - * @param {string|number} b second id - * @returns {-1|0|1} compare result - */ -const compareIds = (a, b) => { - if (typeof a !== typeof b) { - return typeof a < typeof b ? -1 : 1; - } - if (a < b) return -1; - if (a > b) return 1; - return 0; -}; - -module.exports.compareIds = compareIds; - -/** - * @param {string} a first string - * @param {string} b second string - * @returns {-1|0|1} compare result - */ -const compareStrings = (a, b) => { - if (a < b) return -1; - if (a > b) return 1; - return 0; -}; - -module.exports.compareStrings = compareStrings; - -/** - * @param {ChunkGroup} a first chunk group - * @param {ChunkGroup} b second chunk group - * @returns {-1|0|1} compare result - */ -const compareChunkGroupsByIndex = (a, b) => - /** @type {number} */ (a.index) < /** @type {number} */ (b.index) ? -1 : 1; -module.exports.compareChunkGroupsByIndex = compareChunkGroupsByIndex; - -/** - * @template K1 {Object} - * @template K2 - * @template T - */ -class TwoKeyWeakMap { - constructor() { - /** - * @private - * @type {WeakMap>} - */ - this._map = new WeakMap(); - } - - /** - * @param {K1} key1 first key - * @param {K2} key2 second key - * @returns {T | undefined} value - */ - get(key1, key2) { - const childMap = this._map.get(key1); - if (childMap === undefined) { - return; - } - return childMap.get(key2); - } - - /** - * @param {K1} key1 first key - * @param {K2} key2 second key - * @param {T | undefined} value new value - * @returns {void} - */ - set(key1, key2, value) { - let childMap = this._map.get(key1); - if (childMap === undefined) { - childMap = new WeakMap(); - this._map.set(key1, childMap); - } - childMap.set(key2, value); - } -} - -/** @type {TwoKeyWeakMap, Comparator, Comparator>}} */ -const concatComparatorsCache = new TwoKeyWeakMap(); - -/** - * @template T - * @param {Comparator} c1 comparator - * @param {Comparator} c2 comparator - * @param {Comparator[]} cRest comparators - * @returns {Comparator} comparator - */ -const concatComparators = (c1, c2, ...cRest) => { - if (cRest.length > 0) { - const [c3, ...cRest2] = cRest; - return concatComparators(c1, concatComparators(c2, c3, ...cRest2)); - } - const cacheEntry = /** @type {Comparator} */ ( - concatComparatorsCache.get(c1, c2) - ); - if (cacheEntry !== undefined) return cacheEntry; - /** - * @param {T} a first value - * @param {T} b second value - * @returns {-1|0|1} compare result - */ - const result = (a, b) => { - const res = c1(a, b); - if (res !== 0) return res; - return c2(a, b); - }; - concatComparatorsCache.set(c1, c2, result); - return result; -}; -module.exports.concatComparators = concatComparators; - -/** - * @template A, B - * @typedef {(input: A) => B | undefined | null} Selector - */ - -/** @type {TwoKeyWeakMap, Comparator, Comparator>}} */ -const compareSelectCache = new TwoKeyWeakMap(); - -/** - * @template T - * @template R - * @param {Selector} getter getter for value - * @param {Comparator} comparator comparator - * @returns {Comparator} comparator - */ -const compareSelect = (getter, comparator) => { - const cacheEntry = compareSelectCache.get(getter, comparator); - if (cacheEntry !== undefined) return cacheEntry; - /** - * @param {T} a first value - * @param {T} b second value - * @returns {-1|0|1} compare result - */ - const result = (a, b) => { - const aValue = getter(a); - const bValue = getter(b); - if (aValue !== undefined && aValue !== null) { - if (bValue !== undefined && bValue !== null) { - return comparator(aValue, bValue); - } - return -1; - } - if (bValue !== undefined && bValue !== null) { - return 1; - } - return 0; - }; - compareSelectCache.set(getter, comparator, result); - return result; -}; -module.exports.compareSelect = compareSelect; - -/** @type {WeakMap, Comparator>>} */ -const compareIteratorsCache = new WeakMap(); - -/** - * @template T - * @param {Comparator} elementComparator comparator for elements - * @returns {Comparator>} comparator for iterables of elements - */ -const compareIterables = elementComparator => { - const cacheEntry = compareIteratorsCache.get(elementComparator); - if (cacheEntry !== undefined) return cacheEntry; - /** - * @param {Iterable} a first value - * @param {Iterable} b second value - * @returns {-1|0|1} compare result - */ - const result = (a, b) => { - const aI = a[Symbol.iterator](); - const bI = b[Symbol.iterator](); - while (true) { - const aItem = aI.next(); - const bItem = bI.next(); - if (aItem.done) { - return bItem.done ? 0 : -1; - } else if (bItem.done) { - return 1; - } - const res = elementComparator(aItem.value, bItem.value); - if (res !== 0) return res; - } - }; - compareIteratorsCache.set(elementComparator, result); - return result; -}; -module.exports.compareIterables = compareIterables; - -// TODO this is no longer needed when minimum node.js version is >= 12 -// since these versions ship with a stable sort function -/** - * @template T - * @param {Iterable} iterable original ordered list - * @returns {Comparator} comparator - */ -module.exports.keepOriginalOrder = iterable => { - /** @type {Map} */ - const map = new Map(); - let i = 0; - for (const item of iterable) { - map.set(item, i++); - } - return (a, b) => - compareNumbers( - /** @type {number} */ (map.get(a)), - /** @type {number} */ (map.get(b)) - ); -}; - -/** - * @param {ChunkGraph} chunkGraph the chunk graph - * @returns {Comparator} comparator - */ -module.exports.compareChunksNatural = chunkGraph => { - const cmpFn = module.exports.compareModulesById(chunkGraph); - const cmpIterableFn = compareIterables(cmpFn); - return concatComparators( - compareSelect( - chunk => /** @type {string|number} */ (chunk.name), - compareIds - ), - compareSelect(chunk => chunk.runtime, compareRuntime), - compareSelect( - /** - * @param {Chunk} chunk a chunk - * @returns {Iterable} modules - */ - chunk => chunkGraph.getOrderedChunkModulesIterable(chunk, cmpFn), - cmpIterableFn - ) - ); -}; - -/** - * Compare two locations - * @param {DependencyLocation} a A location node - * @param {DependencyLocation} b A location node - * @returns {-1|0|1} sorting comparator value - */ -module.exports.compareLocations = (a, b) => { - const isObjectA = typeof a === "object" && a !== null; - const isObjectB = typeof b === "object" && b !== null; - if (!isObjectA || !isObjectB) { - if (isObjectA) return 1; - if (isObjectB) return -1; - return 0; - } - if ("start" in a) { - if ("start" in b) { - const ap = a.start; - const bp = b.start; - if (ap.line < bp.line) return -1; - if (ap.line > bp.line) return 1; - if (/** @type {number} */ (ap.column) < /** @type {number} */ (bp.column)) - return -1; - if (/** @type {number} */ (ap.column) > /** @type {number} */ (bp.column)) - return 1; - } else return -1; - } else if ("start" in b) return 1; - if ("name" in a) { - if ("name" in b) { - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; - } else return -1; - } else if ("name" in b) return 1; - if ("index" in a) { - if ("index" in b) { - if (/** @type {number} */ (a.index) < /** @type {number} */ (b.index)) - return -1; - if (/** @type {number} */ (a.index) > /** @type {number} */ (b.index)) - return 1; - } else return -1; - } else if ("index" in b) return 1; - return 0; -}; diff --git a/webpack-lib/lib/util/compileBooleanMatcher.js b/webpack-lib/lib/util/compileBooleanMatcher.js deleted file mode 100644 index e388602f246..00000000000 --- a/webpack-lib/lib/util/compileBooleanMatcher.js +++ /dev/null @@ -1,234 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @param {string} str string - * @returns {string} quoted meta - */ -const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); - -/** - * @param {string} str string - * @returns {string} string - */ -const toSimpleString = str => { - if (`${Number(str)}` === str) { - return str; - } - return JSON.stringify(str); -}; - -/** - * @param {Record} map value map - * @returns {boolean|(function(string): string)} true/false, when unconditionally true/false, or a template function to determine the value at runtime - */ -const compileBooleanMatcher = map => { - const positiveItems = Object.keys(map).filter(i => map[i]); - const negativeItems = Object.keys(map).filter(i => !map[i]); - if (positiveItems.length === 0) return false; - if (negativeItems.length === 0) return true; - return compileBooleanMatcherFromLists(positiveItems, negativeItems); -}; - -/** - * @param {string[]} positiveItems positive items - * @param {string[]} negativeItems negative items - * @returns {function(string): string} a template function to determine the value at runtime - */ -const compileBooleanMatcherFromLists = (positiveItems, negativeItems) => { - if (positiveItems.length === 0) return () => "false"; - if (negativeItems.length === 0) return () => "true"; - if (positiveItems.length === 1) - return value => `${toSimpleString(positiveItems[0])} == ${value}`; - if (negativeItems.length === 1) - return value => `${toSimpleString(negativeItems[0])} != ${value}`; - const positiveRegexp = itemsToRegexp(positiveItems); - const negativeRegexp = itemsToRegexp(negativeItems); - if (positiveRegexp.length <= negativeRegexp.length) { - return value => `/^${positiveRegexp}$/.test(${value})`; - } - return value => `!/^${negativeRegexp}$/.test(${value})`; -}; - -/** - * @param {Set} itemsSet items set - * @param {(str: string) => string | false} getKey get key function - * @param {(str: Array) => boolean} condition condition - * @returns {Array>} list of common items - */ -const popCommonItems = (itemsSet, getKey, condition) => { - /** @type {Map>} */ - const map = new Map(); - for (const item of itemsSet) { - const key = getKey(item); - if (key) { - let list = map.get(key); - if (list === undefined) { - /** @type {Array} */ - list = []; - map.set(key, list); - } - list.push(item); - } - } - /** @type {Array>} */ - const result = []; - for (const list of map.values()) { - if (condition(list)) { - for (const item of list) { - itemsSet.delete(item); - } - result.push(list); - } - } - return result; -}; - -/** - * @param {Array} items items - * @returns {string} common prefix - */ -const getCommonPrefix = items => { - let prefix = items[0]; - for (let i = 1; i < items.length; i++) { - const item = items[i]; - for (let p = 0; p < prefix.length; p++) { - if (item[p] !== prefix[p]) { - prefix = prefix.slice(0, p); - break; - } - } - } - return prefix; -}; - -/** - * @param {Array} items items - * @returns {string} common suffix - */ -const getCommonSuffix = items => { - let suffix = items[0]; - for (let i = 1; i < items.length; i++) { - const item = items[i]; - for (let p = item.length - 1, s = suffix.length - 1; s >= 0; p--, s--) { - if (item[p] !== suffix[s]) { - suffix = suffix.slice(s + 1); - break; - } - } - } - return suffix; -}; - -/** - * @param {Array} itemsArr array of items - * @returns {string} regexp - */ -const itemsToRegexp = itemsArr => { - if (itemsArr.length === 1) { - return quoteMeta(itemsArr[0]); - } - /** @type {Array} */ - const finishedItems = []; - - // merge single char items: (a|b|c|d|ef) => ([abcd]|ef) - let countOfSingleCharItems = 0; - for (const item of itemsArr) { - if (item.length === 1) { - countOfSingleCharItems++; - } - } - // special case for only single char items - if (countOfSingleCharItems === itemsArr.length) { - return `[${quoteMeta(itemsArr.sort().join(""))}]`; - } - const items = new Set(itemsArr.sort()); - if (countOfSingleCharItems > 2) { - let singleCharItems = ""; - for (const item of items) { - if (item.length === 1) { - singleCharItems += item; - items.delete(item); - } - } - finishedItems.push(`[${quoteMeta(singleCharItems)}]`); - } - - // special case for 2 items with common prefix/suffix - if (finishedItems.length === 0 && items.size === 2) { - const prefix = getCommonPrefix(itemsArr); - const suffix = getCommonSuffix( - itemsArr.map(item => item.slice(prefix.length)) - ); - if (prefix.length > 0 || suffix.length > 0) { - return `${quoteMeta(prefix)}${itemsToRegexp( - itemsArr.map(i => i.slice(prefix.length, -suffix.length || undefined)) - )}${quoteMeta(suffix)}`; - } - } - - // special case for 2 items with common suffix - if (finishedItems.length === 0 && items.size === 2) { - /** @type {Iterator} */ - const it = items[Symbol.iterator](); - const a = it.next().value; - const b = it.next().value; - if (a.length > 0 && b.length > 0 && a.slice(-1) === b.slice(-1)) { - return `${itemsToRegexp([a.slice(0, -1), b.slice(0, -1)])}${quoteMeta( - a.slice(-1) - )}`; - } - } - - // find common prefix: (a1|a2|a3|a4|b5) => (a(1|2|3|4)|b5) - const prefixed = popCommonItems( - items, - item => (item.length >= 1 ? item[0] : false), - list => { - if (list.length >= 3) return true; - if (list.length <= 1) return false; - return list[0][1] === list[1][1]; - } - ); - for (const prefixedItems of prefixed) { - const prefix = getCommonPrefix(prefixedItems); - finishedItems.push( - `${quoteMeta(prefix)}${itemsToRegexp( - prefixedItems.map(i => i.slice(prefix.length)) - )}` - ); - } - - // find common suffix: (a1|b1|c1|d1|e2) => ((a|b|c|d)1|e2) - const suffixed = popCommonItems( - items, - item => (item.length >= 1 ? item.slice(-1) : false), - list => { - if (list.length >= 3) return true; - if (list.length <= 1) return false; - return list[0].slice(-2) === list[1].slice(-2); - } - ); - for (const suffixedItems of suffixed) { - const suffix = getCommonSuffix(suffixedItems); - finishedItems.push( - `${itemsToRegexp( - suffixedItems.map(i => i.slice(0, -suffix.length)) - )}${quoteMeta(suffix)}` - ); - } - - // TODO further optimize regexp, i. e. - // use ranges: (1|2|3|4|a) => [1-4a] - const conditional = finishedItems.concat(Array.from(items, quoteMeta)); - if (conditional.length === 1) return conditional[0]; - return `(${conditional.join("|")})`; -}; - -compileBooleanMatcher.fromLists = compileBooleanMatcherFromLists; -compileBooleanMatcher.itemsToRegexp = itemsToRegexp; -module.exports = compileBooleanMatcher; diff --git a/webpack-lib/lib/util/concatenate.js b/webpack-lib/lib/util/concatenate.js deleted file mode 100644 index 8d19001c9d8..00000000000 --- a/webpack-lib/lib/util/concatenate.js +++ /dev/null @@ -1,227 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Template = require("../Template"); - -/** @typedef {import("eslint-scope").Scope} Scope */ -/** @typedef {import("eslint-scope").Reference} Reference */ -/** @typedef {import("eslint-scope").Variable} Variable */ -/** @typedef {import("estree").Node} Node */ -/** @typedef {import("../javascript/JavascriptParser").Range} Range */ -/** @typedef {import("../javascript/JavascriptParser").Program} Program */ -/** @typedef {Set} UsedNames */ - -const DEFAULT_EXPORT = "__WEBPACK_DEFAULT_EXPORT__"; -const NAMESPACE_OBJECT_EXPORT = "__WEBPACK_NAMESPACE_OBJECT__"; - -/** - * @param {Variable} variable variable - * @returns {Reference[]} references - */ -const getAllReferences = variable => { - let set = variable.references; - // Look for inner scope variables too (like in class Foo { t() { Foo } }) - const identifiers = new Set(variable.identifiers); - for (const scope of variable.scope.childScopes) { - for (const innerVar of scope.variables) { - if (innerVar.identifiers.some(id => identifiers.has(id))) { - set = set.concat(innerVar.references); - break; - } - } - } - return set; -}; - -/** - * @param {Node | Node[]} ast ast - * @param {Node} node node - * @returns {undefined | Node[]} result - */ -const getPathInAst = (ast, node) => { - if (ast === node) { - return []; - } - - const nr = /** @type {Range} */ (node.range); - - /** - * @param {Node} n node - * @returns {Node[] | undefined} result - */ - const enterNode = n => { - if (!n) return; - const r = n.range; - if (r && r[0] <= nr[0] && r[1] >= nr[1]) { - const path = getPathInAst(n, node); - if (path) { - path.push(n); - return path; - } - } - }; - - if (Array.isArray(ast)) { - for (let i = 0; i < ast.length; i++) { - const enterResult = enterNode(ast[i]); - if (enterResult !== undefined) return enterResult; - } - } else if (ast && typeof ast === "object") { - const keys = - /** @type {Array} */ - (Object.keys(ast)); - for (let i = 0; i < keys.length; i++) { - // We are making the faster check in `enterNode` using `n.range` - const value = - ast[ - /** @type {Exclude} */ - (keys[i]) - ]; - if (Array.isArray(value)) { - const pathResult = getPathInAst(value, node); - if (pathResult !== undefined) return pathResult; - } else if (value && typeof value === "object") { - const enterResult = enterNode(value); - if (enterResult !== undefined) return enterResult; - } - } - } -}; - -/** - * @param {string} oldName old name - * @param {UsedNames} usedNamed1 used named 1 - * @param {UsedNames} usedNamed2 used named 2 - * @param {string} extraInfo extra info - * @returns {string} found new name - */ -function findNewName(oldName, usedNamed1, usedNamed2, extraInfo) { - let name = oldName; - - if (name === DEFAULT_EXPORT) { - name = ""; - } - if (name === NAMESPACE_OBJECT_EXPORT) { - name = "namespaceObject"; - } - - // Remove uncool stuff - extraInfo = extraInfo.replace( - /\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g, - "" - ); - - const splittedInfo = extraInfo.split("/"); - while (splittedInfo.length) { - name = splittedInfo.pop() + (name ? `_${name}` : ""); - const nameIdent = Template.toIdentifier(name); - if ( - !usedNamed1.has(nameIdent) && - (!usedNamed2 || !usedNamed2.has(nameIdent)) - ) - return nameIdent; - } - - let i = 0; - let nameWithNumber = Template.toIdentifier(`${name}_${i}`); - while ( - usedNamed1.has(nameWithNumber) || - // eslint-disable-next-line no-unmodified-loop-condition - (usedNamed2 && usedNamed2.has(nameWithNumber)) - ) { - i++; - nameWithNumber = Template.toIdentifier(`${name}_${i}`); - } - return nameWithNumber; -} - -/** - * @param {Scope | null} s scope - * @param {UsedNames} nameSet name set - * @param {TODO} scopeSet1 scope set 1 - * @param {TODO} scopeSet2 scope set 2 - */ -const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => { - let scope = s; - while (scope) { - if (scopeSet1.has(scope)) break; - if (scopeSet2.has(scope)) break; - scopeSet1.add(scope); - for (const variable of scope.variables) { - nameSet.add(variable.name); - } - scope = scope.upper; - } -}; - -const RESERVED_NAMES = new Set( - [ - // internal names (should always be renamed) - DEFAULT_EXPORT, - NAMESPACE_OBJECT_EXPORT, - - // keywords - "abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue", - "debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float", - "for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null", - "package,private,protected,public,return,short,static,super,switch,synchronized,this,throw", - "throws,transient,true,try,typeof,var,void,volatile,while,with,yield", - - // commonjs/amd - "module,__dirname,__filename,exports,require,define", - - // js globals - "Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math", - "NaN,name,Number,Object,prototype,String,Symbol,toString,undefined,valueOf", - - // browser globals - "alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout", - "clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent", - "defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape", - "event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location", - "mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering", - "open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat", - "parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll", - "secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape", - "untaint,window", - - // window events - "onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit" - ] - .join(",") - .split(",") -); - -/** - * @param {Map }>} usedNamesInScopeInfo used names in scope info - * @param {string} module module identifier - * @param {string} id export id - * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set }} info - */ -const getUsedNamesInScopeInfo = (usedNamesInScopeInfo, module, id) => { - const key = `${module}-${id}`; - let info = usedNamesInScopeInfo.get(key); - if (info === undefined) { - info = { - usedNames: new Set(), - alreadyCheckedScopes: new Set() - }; - usedNamesInScopeInfo.set(key, info); - } - return info; -}; - -module.exports = { - getUsedNamesInScopeInfo, - findNewName, - getAllReferences, - getPathInAst, - NAMESPACE_OBJECT_EXPORT, - DEFAULT_EXPORT, - RESERVED_NAMES, - addScopeSymbols -}; diff --git a/webpack-lib/lib/util/conventions.js b/webpack-lib/lib/util/conventions.js deleted file mode 100644 index 4f78df1c095..00000000000 --- a/webpack-lib/lib/util/conventions.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Gengkun He @ahabhgk -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").CssGeneratorExportsConvention} CssGeneratorExportsConvention */ - -/** - * @param {string} input input - * @param {CssGeneratorExportsConvention | undefined} convention convention - * @returns {string[]} results - */ -module.exports.cssExportConvention = (input, convention) => { - const set = new Set(); - if (typeof convention === "function") { - set.add(convention(input)); - } else { - switch (convention) { - case "camel-case": { - set.add(input); - set.add(module.exports.camelCase(input)); - break; - } - case "camel-case-only": { - set.add(module.exports.camelCase(input)); - break; - } - case "dashes": { - set.add(input); - set.add(module.exports.dashesCamelCase(input)); - break; - } - case "dashes-only": { - set.add(module.exports.dashesCamelCase(input)); - break; - } - case "as-is": { - set.add(input); - break; - } - } - } - return Array.from(set); -}; - -// Copy from css-loader -/** - * @param {string} input input - * @returns {string} result - */ -module.exports.dashesCamelCase = input => - input.replace(/-+(\w)/g, (match, firstLetter) => firstLetter.toUpperCase()); - -// Copy from css-loader -/** - * @param {string} input input - * @returns {string} result - */ -module.exports.camelCase = input => { - let result = input.trim(); - - if (result.length === 0) { - return ""; - } - - if (result.length === 1) { - return result.toLowerCase(); - } - - const hasUpperCase = result !== result.toLowerCase(); - - if (hasUpperCase) { - result = preserveCamelCase(result); - } - - return result - .replace(/^[_.\- ]+/, "") - .toLowerCase() - .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toUpperCase()) - .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, m => m.toUpperCase()); -}; - -// Copy from css-loader -/** - * @param {string} string string - * @returns {string} result - */ -const preserveCamelCase = string => { - let result = string; - let isLastCharLower = false; - let isLastCharUpper = false; - let isLastLastCharUpper = false; - - for (let i = 0; i < result.length; i++) { - const character = result[i]; - - if (isLastCharLower && /[\p{Lu}]/u.test(character)) { - result = `${result.slice(0, i)}-${result.slice(i)}`; - isLastCharLower = false; - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = true; - i += 1; - } else if ( - isLastCharUpper && - isLastLastCharUpper && - /[\p{Ll}]/u.test(character) - ) { - result = `${result.slice(0, i - 1)}-${result.slice(i - 1)}`; - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = false; - isLastCharLower = true; - } else { - isLastCharLower = - character.toLowerCase() === character && - character.toUpperCase() !== character; - isLastLastCharUpper = isLastCharUpper; - isLastCharUpper = - character.toUpperCase() === character && - character.toLowerCase() !== character; - } - } - - return result; -}; diff --git a/webpack-lib/lib/util/create-schema-validation.js b/webpack-lib/lib/util/create-schema-validation.js deleted file mode 100644 index 4f12c8e69af..00000000000 --- a/webpack-lib/lib/util/create-schema-validation.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const memoize = require("./memoize"); - -/** @typedef {import("schema-utils/declarations/validate").ValidationErrorConfiguration} ValidationErrorConfiguration */ -/** @typedef {import("./fs").JsonObject} JsonObject */ - -const getValidate = memoize(() => require("schema-utils").validate); - -/** - * @template {object | object[]} T - * @param {(function(T): boolean) | undefined} check check - * @param {() => JsonObject} getSchema get schema fn - * @param {ValidationErrorConfiguration} options options - * @returns {function(T=): void} validate - */ -const createSchemaValidation = (check, getSchema, options) => { - getSchema = memoize(getSchema); - return value => { - if (check && !check(/** @type {T} */ (value))) { - getValidate()( - getSchema(), - /** @type {object | object[]} */ - (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" - )(); - } - }; -}; - -module.exports = createSchemaValidation; diff --git a/webpack-lib/lib/util/createHash.js b/webpack-lib/lib/util/createHash.js deleted file mode 100644 index 991a1a2dbd8..00000000000 --- a/webpack-lib/lib/util/createHash.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Hash = require("./Hash"); - -const BULK_SIZE = 2000; - -// We are using an object instead of a Map as this will stay static during the runtime -// so access to it can be optimized by v8 -/** @type {{[key: string]: Map}} */ -const digestCaches = {}; - -/** @typedef {function(): Hash} HashFactory */ - -class BulkUpdateDecorator extends Hash { - /** - * @param {Hash | HashFactory} hashOrFactory function to create a hash - * @param {string=} hashKey key for caching - */ - constructor(hashOrFactory, hashKey) { - super(); - this.hashKey = hashKey; - if (typeof hashOrFactory === "function") { - this.hashFactory = hashOrFactory; - this.hash = undefined; - } else { - this.hashFactory = undefined; - this.hash = hashOrFactory; - } - this.buffer = ""; - } - - /** - * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} - * @param {string|Buffer} data data - * @param {string=} inputEncoding data encoding - * @returns {this} updated hash - */ - update(data, inputEncoding) { - if ( - inputEncoding !== undefined || - typeof data !== "string" || - data.length > BULK_SIZE - ) { - if (this.hash === undefined) - this.hash = /** @type {HashFactory} */ (this.hashFactory)(); - if (this.buffer.length > 0) { - this.hash.update(this.buffer); - this.buffer = ""; - } - this.hash.update(data, inputEncoding); - } else { - this.buffer += data; - if (this.buffer.length > BULK_SIZE) { - if (this.hash === undefined) - this.hash = /** @type {HashFactory} */ (this.hashFactory)(); - this.hash.update(this.buffer); - this.buffer = ""; - } - } - return this; - } - - /** - * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} - * @param {string=} encoding encoding of the return value - * @returns {string|Buffer} digest - */ - digest(encoding) { - let digestCache; - const buffer = this.buffer; - if (this.hash === undefined) { - // short data for hash, we can use caching - const cacheKey = `${this.hashKey}-${encoding}`; - digestCache = digestCaches[cacheKey]; - if (digestCache === undefined) { - digestCache = digestCaches[cacheKey] = new Map(); - } - const cacheEntry = digestCache.get(buffer); - if (cacheEntry !== undefined) return cacheEntry; - this.hash = /** @type {HashFactory} */ (this.hashFactory)(); - } - if (buffer.length > 0) { - this.hash.update(buffer); - } - const digestResult = this.hash.digest(encoding); - const result = - typeof digestResult === "string" ? digestResult : digestResult.toString(); - if (digestCache !== undefined) { - digestCache.set(buffer, result); - } - return result; - } -} - -/* istanbul ignore next */ -class DebugHash extends Hash { - constructor() { - super(); - this.string = ""; - } - - /** - * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} - * @param {string|Buffer} data data - * @param {string=} inputEncoding data encoding - * @returns {this} updated hash - */ - update(data, inputEncoding) { - if (typeof data !== "string") data = data.toString("utf-8"); - const prefix = Buffer.from("@webpack-debug-digest@").toString("hex"); - if (data.startsWith(prefix)) { - data = Buffer.from(data.slice(prefix.length), "hex").toString(); - } - this.string += `[${data}](${ - /** @type {string} */ (new Error().stack).split("\n", 3)[2] - })\n`; - return this; - } - - /** - * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} - * @param {string=} encoding encoding of the return value - * @returns {string|Buffer} digest - */ - digest(encoding) { - return Buffer.from(`@webpack-debug-digest@${this.string}`).toString("hex"); - } -} - -/** @type {typeof import("crypto") | undefined} */ -let crypto; -/** @type {typeof import("./hash/xxhash64") | undefined} */ -let createXXHash64; -/** @type {typeof import("./hash/md4") | undefined} */ -let createMd4; -/** @type {typeof import("./hash/BatchedHash") | undefined} */ -let BatchedHash; - -/** @typedef {string | typeof Hash} Algorithm */ - -/** - * Creates a hash by name or function - * @param {Algorithm} algorithm the algorithm name or a constructor creating a hash - * @returns {Hash} the hash - */ -module.exports = algorithm => { - if (typeof algorithm === "function") { - // eslint-disable-next-line new-cap - return new BulkUpdateDecorator(() => new algorithm()); - } - switch (algorithm) { - // TODO add non-cryptographic algorithm here - case "debug": - return new DebugHash(); - case "xxhash64": - if (createXXHash64 === undefined) { - createXXHash64 = require("./hash/xxhash64"); - if (BatchedHash === undefined) { - BatchedHash = require("./hash/BatchedHash"); - } - } - return new /** @type {typeof import("./hash/BatchedHash")} */ ( - BatchedHash - )(createXXHash64()); - case "md4": - if (createMd4 === undefined) { - createMd4 = require("./hash/md4"); - if (BatchedHash === undefined) { - BatchedHash = require("./hash/BatchedHash"); - } - } - return new /** @type {typeof import("./hash/BatchedHash")} */ ( - BatchedHash - )(createMd4()); - case "native-md4": - if (crypto === undefined) crypto = require("crypto"); - return new BulkUpdateDecorator( - () => /** @type {typeof import("crypto")} */ (crypto).createHash("md4"), - "md4" - ); - default: - if (crypto === undefined) crypto = require("crypto"); - return new BulkUpdateDecorator( - () => - /** @type {typeof import("crypto")} */ (crypto).createHash(algorithm), - algorithm - ); - } -}; diff --git a/webpack-lib/lib/util/deprecation.js b/webpack-lib/lib/util/deprecation.js deleted file mode 100644 index d59cbd78f0f..00000000000 --- a/webpack-lib/lib/util/deprecation.js +++ /dev/null @@ -1,347 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); - -/** @type {Map} */ -const deprecationCache = new Map(); - -/** - * @typedef {object} FakeHookMarker - * @property {true} _fakeHook it's a fake hook - */ - -/** - * @template T - * @typedef {T & FakeHookMarker} FakeHook - */ - -/** - * @param {string} message deprecation message - * @param {string} code deprecation code - * @returns {Function} function to trigger deprecation - */ -const createDeprecation = (message, code) => { - const cached = deprecationCache.get(message); - if (cached !== undefined) return cached; - const fn = util.deprecate( - () => {}, - message, - `DEP_WEBPACK_DEPRECATION_${code}` - ); - deprecationCache.set(message, fn); - return fn; -}; - -const COPY_METHODS = [ - "concat", - "entry", - "filter", - "find", - "findIndex", - "includes", - "indexOf", - "join", - "lastIndexOf", - "map", - "reduce", - "reduceRight", - "slice", - "some" -]; - -const DISABLED_METHODS = [ - "copyWithin", - "entries", - "fill", - "keys", - "pop", - "reverse", - "shift", - "splice", - "sort", - "unshift" -]; - -/** - * @param {any} set new set - * @param {string} name property name - * @returns {void} - */ -module.exports.arrayToSetDeprecation = (set, name) => { - for (const method of COPY_METHODS) { - if (set[method]) continue; - const d = createDeprecation( - `${name} was changed from Array to Set (using Array method '${method}' is deprecated)`, - "ARRAY_TO_SET" - ); - /** - * @deprecated - * @this {Set} - * @returns {number} count - */ - set[method] = function () { - d(); - const array = Array.from(this); - return Array.prototype[/** @type {keyof COPY_METHODS} */ (method)].apply( - array, - // eslint-disable-next-line prefer-rest-params - arguments - ); - }; - } - const dPush = createDeprecation( - `${name} was changed from Array to Set (using Array method 'push' is deprecated)`, - "ARRAY_TO_SET_PUSH" - ); - const dLength = createDeprecation( - `${name} was changed from Array to Set (using Array property 'length' is deprecated)`, - "ARRAY_TO_SET_LENGTH" - ); - const dIndexer = createDeprecation( - `${name} was changed from Array to Set (indexing Array is deprecated)`, - "ARRAY_TO_SET_INDEXER" - ); - /** - * @deprecated - * @this {Set} - * @returns {number} count - */ - set.push = function () { - dPush(); - // eslint-disable-next-line prefer-rest-params - for (const item of Array.from(arguments)) { - this.add(item); - } - return this.size; - }; - for (const method of DISABLED_METHODS) { - if (set[method]) continue; - set[method] = () => { - throw new Error( - `${name} was changed from Array to Set (using Array method '${method}' is not possible)` - ); - }; - } - /** - * @param {number} index index - * @returns {any} value - */ - const createIndexGetter = index => { - /** - * @this {Set} a Set - * @returns {any} the value at this location - */ - // eslint-disable-next-line func-style - const fn = function () { - dIndexer(); - let i = 0; - for (const item of this) { - if (i++ === index) return item; - } - }; - return fn; - }; - /** - * @param {number} index index - */ - const defineIndexGetter = index => { - Object.defineProperty(set, index, { - get: createIndexGetter(index), - set(value) { - throw new Error( - `${name} was changed from Array to Set (indexing Array with write is not possible)` - ); - } - }); - }; - defineIndexGetter(0); - let indexerDefined = 1; - Object.defineProperty(set, "length", { - get() { - dLength(); - const length = this.size; - for (indexerDefined; indexerDefined < length + 1; indexerDefined++) { - defineIndexGetter(indexerDefined); - } - return length; - }, - set(value) { - throw new Error( - `${name} was changed from Array to Set (writing to Array property 'length' is not possible)` - ); - } - }); - set[Symbol.isConcatSpreadable] = true; -}; - -/** - * @template T - * @param {string} name name - * @returns {{ new (values?: readonly T[] | null): SetDeprecatedArray }} SetDeprecatedArray - */ -module.exports.createArrayToSetDeprecationSet = name => { - let initialized = false; - - /** - * @template T - */ - class SetDeprecatedArray extends Set { - /** - * @param {readonly T[] | null=} items items - */ - constructor(items) { - super(items); - if (!initialized) { - initialized = true; - module.exports.arrayToSetDeprecation( - SetDeprecatedArray.prototype, - name - ); - } - } - } - return SetDeprecatedArray; -}; - -/** - * @template {object} T - * @param {T} obj object - * @param {string} name property name - * @param {string} code deprecation code - * @param {string} note additional note - * @returns {Proxy} frozen object with deprecation when modifying - */ -module.exports.soonFrozenObjectDeprecation = (obj, name, code, note = "") => { - const message = `${name} will be frozen in future, all modifications are deprecated.${ - note && `\n${note}` - }`; - return /** @type {Proxy} */ ( - new Proxy(/** @type {object} */ (obj), { - set: util.deprecate( - /** - * @param {T} target target - * @param {string | symbol} property property - * @param {any} value value - * @param {any} receiver receiver - * @returns {boolean} result - */ - (target, property, value, receiver) => - Reflect.set( - /** @type {object} */ (target), - property, - value, - receiver - ), - message, - code - ), - defineProperty: util.deprecate( - /** - * @param {T} target target - * @param {string | symbol} property property - * @param {PropertyDescriptor} descriptor descriptor - * @returns {boolean} result - */ - (target, property, descriptor) => - Reflect.defineProperty( - /** @type {object} */ (target), - property, - descriptor - ), - message, - code - ), - deleteProperty: util.deprecate( - /** - * @param {T} target target - * @param {string | symbol} property property - * @returns {boolean} result - */ - (target, property) => - Reflect.deleteProperty(/** @type {object} */ (target), property), - message, - code - ), - setPrototypeOf: util.deprecate( - /** - * @param {T} target target - * @param {object | null} proto proto - * @returns {boolean} result - */ - (target, proto) => - Reflect.setPrototypeOf(/** @type {object} */ (target), proto), - message, - code - ) - }) - ); -}; - -/** - * @template T - * @param {T} obj object - * @param {string} message deprecation message - * @param {string} code deprecation code - * @returns {T} object with property access deprecated - */ -const deprecateAllProperties = (obj, message, code) => { - const newObj = {}; - const descriptors = Object.getOwnPropertyDescriptors(obj); - for (const name of Object.keys(descriptors)) { - const descriptor = descriptors[name]; - if (typeof descriptor.value === "function") { - Object.defineProperty(newObj, name, { - ...descriptor, - value: util.deprecate(descriptor.value, message, code) - }); - } else if (descriptor.get || descriptor.set) { - Object.defineProperty(newObj, name, { - ...descriptor, - get: descriptor.get && util.deprecate(descriptor.get, message, code), - set: descriptor.set && util.deprecate(descriptor.set, message, code) - }); - } else { - let value = descriptor.value; - Object.defineProperty(newObj, name, { - configurable: descriptor.configurable, - enumerable: descriptor.enumerable, - get: util.deprecate(() => value, message, code), - set: descriptor.writable - ? util.deprecate( - /** - * @template T - * @param {T} v value - * @returns {T} result - */ - v => (value = v), - message, - code - ) - : undefined - }); - } - } - return /** @type {T} */ (newObj); -}; -module.exports.deprecateAllProperties = deprecateAllProperties; - -/** - * @template {object} T - * @param {T} fakeHook fake hook implementation - * @param {string=} message deprecation message (not deprecated when unset) - * @param {string=} code deprecation code (not deprecated when unset) - * @returns {FakeHook} fake hook which redirects - */ -module.exports.createFakeHook = (fakeHook, message, code) => { - if (message && code) { - fakeHook = deprecateAllProperties(fakeHook, message, code); - } - return Object.freeze( - Object.assign(fakeHook, { _fakeHook: /** @type {true} */ (true) }) - ); -}; diff --git a/webpack-lib/lib/util/deterministicGrouping.js b/webpack-lib/lib/util/deterministicGrouping.js deleted file mode 100644 index b69be028899..00000000000 --- a/webpack-lib/lib/util/deterministicGrouping.js +++ /dev/null @@ -1,540 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -// Simulations show these probabilities for a single change -// 93.1% that one group is invalidated -// 4.8% that two groups are invalidated -// 1.1% that 3 groups are invalidated -// 0.1% that 4 or more groups are invalidated -// -// And these for removing/adding 10 lexically adjacent files -// 64.5% that one group is invalidated -// 24.8% that two groups are invalidated -// 7.8% that 3 groups are invalidated -// 2.7% that 4 or more groups are invalidated -// -// And these for removing/adding 3 random files -// 0% that one group is invalidated -// 3.7% that two groups are invalidated -// 80.8% that 3 groups are invalidated -// 12.3% that 4 groups are invalidated -// 3.2% that 5 or more groups are invalidated - -/** - * @param {string} a key - * @param {string} b key - * @returns {number} the similarity as number - */ -const similarity = (a, b) => { - const l = Math.min(a.length, b.length); - let dist = 0; - for (let i = 0; i < l; i++) { - const ca = a.charCodeAt(i); - const cb = b.charCodeAt(i); - dist += Math.max(0, 10 - Math.abs(ca - cb)); - } - return dist; -}; - -/** - * @param {string} a key - * @param {string} b key - * @param {Set} usedNames set of already used names - * @returns {string} the common part and a single char for the difference - */ -const getName = (a, b, usedNames) => { - const l = Math.min(a.length, b.length); - let i = 0; - while (i < l) { - if (a.charCodeAt(i) !== b.charCodeAt(i)) { - i++; - break; - } - i++; - } - while (i < l) { - const name = a.slice(0, i); - const lowerName = name.toLowerCase(); - if (!usedNames.has(lowerName)) { - usedNames.add(lowerName); - return name; - } - i++; - } - // names always contain a hash, so this is always unique - // we don't need to check usedNames nor add it - return a; -}; - -/** - * @param {Record} total total size - * @param {Record} size single size - * @returns {void} - */ -const addSizeTo = (total, size) => { - for (const key of Object.keys(size)) { - total[key] = (total[key] || 0) + size[key]; - } -}; - -/** - * @param {Record} total total size - * @param {Record} size single size - * @returns {void} - */ -const subtractSizeFrom = (total, size) => { - for (const key of Object.keys(size)) { - total[key] -= size[key]; - } -}; - -/** - * @template T - * @param {Iterable>} nodes some nodes - * @returns {Record} total size - */ -const sumSize = nodes => { - const sum = Object.create(null); - for (const node of nodes) { - addSizeTo(sum, node.size); - } - return sum; -}; - -/** - * @param {Record} size size - * @param {Record} maxSize minimum size - * @returns {boolean} true, when size is too big - */ -const isTooBig = (size, maxSize) => { - for (const key of Object.keys(size)) { - const s = size[key]; - if (s === 0) continue; - const maxSizeValue = maxSize[key]; - if (typeof maxSizeValue === "number" && s > maxSizeValue) return true; - } - return false; -}; - -/** - * @param {Record} size size - * @param {Record} minSize minimum size - * @returns {boolean} true, when size is too small - */ -const isTooSmall = (size, minSize) => { - for (const key of Object.keys(size)) { - const s = size[key]; - if (s === 0) continue; - const minSizeValue = minSize[key]; - if (typeof minSizeValue === "number" && s < minSizeValue) return true; - } - return false; -}; - -/** - * @param {Record} size size - * @param {Record} minSize minimum size - * @returns {Set} set of types that are too small - */ -const getTooSmallTypes = (size, minSize) => { - const types = new Set(); - for (const key of Object.keys(size)) { - const s = size[key]; - if (s === 0) continue; - const minSizeValue = minSize[key]; - if (typeof minSizeValue === "number" && s < minSizeValue) types.add(key); - } - return types; -}; - -/** - * @template T - * @param {TODO} size size - * @param {Set} types types - * @returns {number} number of matching size types - */ -const getNumberOfMatchingSizeTypes = (size, types) => { - let i = 0; - for (const key of Object.keys(size)) { - if (size[key] !== 0 && types.has(key)) i++; - } - return i; -}; - -/** - * @param {Record} size size - * @param {Set} types types - * @returns {number} selective size sum - */ -const selectiveSizeSum = (size, types) => { - let sum = 0; - for (const key of Object.keys(size)) { - if (size[key] !== 0 && types.has(key)) sum += size[key]; - } - return sum; -}; - -/** - * @template T - */ -class Node { - /** - * @param {T} item item - * @param {string} key key - * @param {Record} size size - */ - constructor(item, key, size) { - this.item = item; - this.key = key; - this.size = size; - } -} - -/** - * @template T - */ -class Group { - /** - * @param {Node[]} nodes nodes - * @param {number[] | null} similarities similarities between the nodes (length = nodes.length - 1) - * @param {Record=} size size of the group - */ - constructor(nodes, similarities, size) { - this.nodes = nodes; - this.similarities = similarities; - this.size = size || sumSize(nodes); - /** @type {string | undefined} */ - this.key = undefined; - } - - /** - * @param {function(Node): boolean} filter filter function - * @returns {Node[] | undefined} removed nodes - */ - popNodes(filter) { - const newNodes = []; - const newSimilarities = []; - const resultNodes = []; - let lastNode; - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - if (filter(node)) { - resultNodes.push(node); - } else { - if (newNodes.length > 0) { - newSimilarities.push( - lastNode === this.nodes[i - 1] - ? /** @type {number[]} */ (this.similarities)[i - 1] - : similarity(/** @type {Node} */ (lastNode).key, node.key) - ); - } - newNodes.push(node); - lastNode = node; - } - } - if (resultNodes.length === this.nodes.length) return; - this.nodes = newNodes; - this.similarities = newSimilarities; - this.size = sumSize(newNodes); - return resultNodes; - } -} - -/** - * @template T - * @param {Iterable>} nodes nodes - * @returns {number[]} similarities - */ -const getSimilarities = nodes => { - // calculate similarities between lexically adjacent nodes - /** @type {number[]} */ - const similarities = []; - let last; - for (const node of nodes) { - if (last !== undefined) { - similarities.push(similarity(last.key, node.key)); - } - last = node; - } - return similarities; -}; - -/** - * @template T - * @typedef {object} GroupedItems - * @property {string} key - * @property {T[]} items - * @property {Record} size - */ - -/** - * @template T - * @typedef {object} Options - * @property {Record} maxSize maximum size of a group - * @property {Record} minSize minimum size of a group (preferred over maximum size) - * @property {Iterable} items a list of items - * @property {function(T): Record} getSize function to get size of an item - * @property {function(T): string} getKey function to get the key of an item - */ - -/** - * @template T - * @param {Options} options options object - * @returns {GroupedItems[]} grouped items - */ -module.exports = ({ maxSize, minSize, items, getSize, getKey }) => { - /** @type {Group[]} */ - const result = []; - - const nodes = Array.from( - items, - item => new Node(item, getKey(item), getSize(item)) - ); - - /** @type {Node[]} */ - const initialNodes = []; - - // lexically ordering of keys - nodes.sort((a, b) => { - if (a.key < b.key) return -1; - if (a.key > b.key) return 1; - return 0; - }); - - // return nodes bigger than maxSize directly as group - // But make sure that minSize is not violated - for (const node of nodes) { - if (isTooBig(node.size, maxSize) && !isTooSmall(node.size, minSize)) { - result.push(new Group([node], [])); - } else { - initialNodes.push(node); - } - } - - if (initialNodes.length > 0) { - const initialGroup = new Group(initialNodes, getSimilarities(initialNodes)); - - /** - * @param {Group} group group - * @param {Record} consideredSize size of the group to consider - * @returns {boolean} true, if the group was modified - */ - const removeProblematicNodes = (group, consideredSize = group.size) => { - const problemTypes = getTooSmallTypes(consideredSize, minSize); - if (problemTypes.size > 0) { - // We hit an edge case where the working set is already smaller than minSize - // We merge problematic nodes with the smallest result node to keep minSize intact - const problemNodes = group.popNodes( - n => getNumberOfMatchingSizeTypes(n.size, problemTypes) > 0 - ); - if (problemNodes === undefined) return false; - // Only merge it with result nodes that have the problematic size type - const possibleResultGroups = result.filter( - n => getNumberOfMatchingSizeTypes(n.size, problemTypes) > 0 - ); - if (possibleResultGroups.length > 0) { - const bestGroup = possibleResultGroups.reduce((min, group) => { - const minMatches = getNumberOfMatchingSizeTypes(min, problemTypes); - const groupMatches = getNumberOfMatchingSizeTypes( - group, - problemTypes - ); - if (minMatches !== groupMatches) - return minMatches < groupMatches ? group : min; - if ( - selectiveSizeSum(min.size, problemTypes) > - selectiveSizeSum(group.size, problemTypes) - ) - return group; - return min; - }); - for (const node of problemNodes) bestGroup.nodes.push(node); - bestGroup.nodes.sort((a, b) => { - if (a.key < b.key) return -1; - if (a.key > b.key) return 1; - return 0; - }); - } else { - // There are no other nodes with the same size types - // We create a new group and have to accept that it's smaller than minSize - result.push(new Group(problemNodes, null)); - } - return true; - } - return false; - }; - - if (initialGroup.nodes.length > 0) { - const queue = [initialGroup]; - - while (queue.length) { - const group = /** @type {Group} */ (queue.pop()); - // only groups bigger than maxSize need to be splitted - if (!isTooBig(group.size, maxSize)) { - result.push(group); - continue; - } - // If the group is already too small - // we try to work only with the unproblematic nodes - if (removeProblematicNodes(group)) { - // This changed something, so we try this group again - queue.push(group); - continue; - } - - // find unsplittable area from left and right - // going minSize from left and right - // at least one node need to be included otherwise we get stuck - let left = 1; - const leftSize = Object.create(null); - addSizeTo(leftSize, group.nodes[0].size); - while (left < group.nodes.length && isTooSmall(leftSize, minSize)) { - addSizeTo(leftSize, group.nodes[left].size); - left++; - } - let right = group.nodes.length - 2; - const rightSize = Object.create(null); - addSizeTo(rightSize, group.nodes[group.nodes.length - 1].size); - while (right >= 0 && isTooSmall(rightSize, minSize)) { - addSizeTo(rightSize, group.nodes[right].size); - right--; - } - - // left v v right - // [ O O O ] O O O [ O O O ] - // ^^^^^^^^^ leftSize - // rightSize ^^^^^^^^^ - // leftSize > minSize - // rightSize > minSize - - // Perfect split: [ O O O ] [ O O O ] - // right === left - 1 - - if (left - 1 > right) { - // We try to remove some problematic nodes to "fix" that - let prevSize; - if (right < group.nodes.length - left) { - subtractSizeFrom(rightSize, group.nodes[right + 1].size); - prevSize = rightSize; - } else { - subtractSizeFrom(leftSize, group.nodes[left - 1].size); - prevSize = leftSize; - } - if (removeProblematicNodes(group, prevSize)) { - // This changed something, so we try this group again - queue.push(group); - continue; - } - // can't split group while holding minSize - // because minSize is preferred of maxSize we return - // the problematic nodes as result here even while it's too big - // To avoid this make sure maxSize > minSize * 3 - result.push(group); - continue; - } - if (left <= right) { - // when there is a area between left and right - // we look for best split point - // we split at the minimum similarity - // here key space is separated the most - // But we also need to make sure to not create too small groups - let best = -1; - let bestSimilarity = Infinity; - let pos = left; - const rightSize = sumSize(group.nodes.slice(pos)); - - // pos v v right - // [ O O O ] O O O [ O O O ] - // ^^^^^^^^^ leftSize - // rightSize ^^^^^^^^^^^^^^^ - - while (pos <= right + 1) { - const similarity = /** @type {number[]} */ (group.similarities)[ - pos - 1 - ]; - if ( - similarity < bestSimilarity && - !isTooSmall(leftSize, minSize) && - !isTooSmall(rightSize, minSize) - ) { - best = pos; - bestSimilarity = similarity; - } - addSizeTo(leftSize, group.nodes[pos].size); - subtractSizeFrom(rightSize, group.nodes[pos].size); - pos++; - } - if (best < 0) { - // This can't happen - // but if that assumption is wrong - // fallback to a big group - result.push(group); - continue; - } - left = best; - right = best - 1; - } - - // create two new groups for left and right area - // and queue them up - const rightNodes = [group.nodes[right + 1]]; - /** @type {number[]} */ - const rightSimilarities = []; - for (let i = right + 2; i < group.nodes.length; i++) { - rightSimilarities.push( - /** @type {number[]} */ (group.similarities)[i - 1] - ); - rightNodes.push(group.nodes[i]); - } - queue.push(new Group(rightNodes, rightSimilarities)); - - const leftNodes = [group.nodes[0]]; - /** @type {number[]} */ - const leftSimilarities = []; - for (let i = 1; i < left; i++) { - leftSimilarities.push( - /** @type {number[]} */ (group.similarities)[i - 1] - ); - leftNodes.push(group.nodes[i]); - } - queue.push(new Group(leftNodes, leftSimilarities)); - } - } - } - - // lexically ordering - result.sort((a, b) => { - if (a.nodes[0].key < b.nodes[0].key) return -1; - if (a.nodes[0].key > b.nodes[0].key) return 1; - return 0; - }); - - // give every group a name - const usedNames = new Set(); - for (let i = 0; i < result.length; i++) { - const group = result[i]; - if (group.nodes.length === 1) { - group.key = group.nodes[0].key; - } else { - const first = group.nodes[0]; - const last = group.nodes[group.nodes.length - 1]; - const name = getName(first.key, last.key, usedNames); - group.key = name; - } - } - - // return the results - return result.map( - group => - /** @type {GroupedItems} */ - ({ - key: group.key, - items: group.nodes.map(node => node.item), - size: group.size - }) - ); -}; diff --git a/webpack-lib/lib/util/extractUrlAndGlobal.js b/webpack-lib/lib/util/extractUrlAndGlobal.js deleted file mode 100644 index ade0a7cf25c..00000000000 --- a/webpack-lib/lib/util/extractUrlAndGlobal.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Sam Chen @chenxsan -*/ - -"use strict"; - -/** - * @param {string} urlAndGlobal the script request - * @returns {string[]} script url and its global variable - */ -module.exports = function extractUrlAndGlobal(urlAndGlobal) { - const index = urlAndGlobal.indexOf("@"); - if (index <= 0 || index === urlAndGlobal.length - 1) { - throw new Error(`Invalid request "${urlAndGlobal}"`); - } - return [urlAndGlobal.substring(index + 1), urlAndGlobal.substring(0, index)]; -}; diff --git a/webpack-lib/lib/util/findGraphRoots.js b/webpack-lib/lib/util/findGraphRoots.js deleted file mode 100644 index 795f99055ff..00000000000 --- a/webpack-lib/lib/util/findGraphRoots.js +++ /dev/null @@ -1,231 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const NO_MARKER = 0; -const IN_PROGRESS_MARKER = 1; -const DONE_MARKER = 2; -const DONE_MAYBE_ROOT_CYCLE_MARKER = 3; -const DONE_AND_ROOT_MARKER = 4; - -/** - * @template T - */ -class Node { - /** - * @param {T} item the value of the node - */ - constructor(item) { - this.item = item; - /** @type {Set>} */ - this.dependencies = new Set(); - this.marker = NO_MARKER; - /** @type {Cycle | undefined} */ - this.cycle = undefined; - this.incoming = 0; - } -} - -/** - * @template T - */ -class Cycle { - constructor() { - /** @type {Set>} */ - this.nodes = new Set(); - } -} - -/** - * @template T - * @typedef {object} StackEntry - * @property {Node} node - * @property {Node[]} openEdges - */ - -/** - * @template T - * @param {Iterable} items list of items - * @param {function(T): Iterable} getDependencies function to get dependencies of an item (items that are not in list are ignored) - * @returns {Iterable} graph roots of the items - */ -module.exports = (items, getDependencies) => { - /** @type {Map>} */ - const itemToNode = new Map(); - for (const item of items) { - const node = new Node(item); - itemToNode.set(item, node); - } - - // early exit when there is only a single item - if (itemToNode.size <= 1) return items; - - // grab all the dependencies - for (const node of itemToNode.values()) { - for (const dep of getDependencies(node.item)) { - const depNode = itemToNode.get(dep); - if (depNode !== undefined) { - node.dependencies.add(depNode); - } - } - } - - // Set of current root modules - // items will be removed if a new reference to it has been found - /** @type {Set>} */ - const roots = new Set(); - - // Set of current cycles without references to it - // cycles will be removed if a new reference to it has been found - // that is not part of the cycle - /** @type {Set>} */ - const rootCycles = new Set(); - - // For all non-marked nodes - for (const selectedNode of itemToNode.values()) { - if (selectedNode.marker === NO_MARKER) { - // deep-walk all referenced modules - // in a non-recursive way - - // start by entering the selected node - selectedNode.marker = IN_PROGRESS_MARKER; - - // keep a stack to avoid recursive walk - /** @type {StackEntry[]} */ - const stack = [ - { - node: selectedNode, - openEdges: Array.from(selectedNode.dependencies) - } - ]; - - // process the top item until stack is empty - while (stack.length > 0) { - const topOfStack = stack[stack.length - 1]; - - // Are there still edges unprocessed in the current node? - if (topOfStack.openEdges.length > 0) { - // Process one dependency - const dependency = - /** @type {Node} */ - (topOfStack.openEdges.pop()); - switch (dependency.marker) { - case NO_MARKER: - // dependency has not be visited yet - // mark it as in-progress and recurse - stack.push({ - node: dependency, - openEdges: Array.from(dependency.dependencies) - }); - dependency.marker = IN_PROGRESS_MARKER; - break; - case IN_PROGRESS_MARKER: { - // It's a in-progress cycle - let cycle = dependency.cycle; - if (!cycle) { - cycle = new Cycle(); - cycle.nodes.add(dependency); - dependency.cycle = cycle; - } - // set cycle property for each node in the cycle - // if nodes are already part of a cycle - // we merge the cycles to a shared cycle - for ( - let i = stack.length - 1; - stack[i].node !== dependency; - i-- - ) { - const node = stack[i].node; - if (node.cycle) { - if (node.cycle !== cycle) { - // merge cycles - for (const cycleNode of node.cycle.nodes) { - cycleNode.cycle = cycle; - cycle.nodes.add(cycleNode); - } - } - } else { - node.cycle = cycle; - cycle.nodes.add(node); - } - } - // don't recurse into dependencies - // these are already on the stack - break; - } - case DONE_AND_ROOT_MARKER: - // This node has be visited yet and is currently a root node - // But as this is a new reference to the node - // it's not really a root - // so we have to convert it to a normal node - dependency.marker = DONE_MARKER; - roots.delete(dependency); - break; - case DONE_MAYBE_ROOT_CYCLE_MARKER: - // This node has be visited yet and - // is maybe currently part of a completed root cycle - // we found a new reference to the cycle - // so it's not really a root cycle - // remove the cycle from the root cycles - // and convert it to a normal node - rootCycles.delete(/** @type {Cycle} */ (dependency.cycle)); - dependency.marker = DONE_MARKER; - break; - // DONE_MARKER: nothing to do, don't recurse into dependencies - } - } else { - // All dependencies of the current node has been visited - // we leave the node - stack.pop(); - topOfStack.node.marker = DONE_MARKER; - } - } - const cycle = selectedNode.cycle; - if (cycle) { - for (const node of cycle.nodes) { - node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER; - } - rootCycles.add(cycle); - } else { - selectedNode.marker = DONE_AND_ROOT_MARKER; - roots.add(selectedNode); - } - } - } - - // Extract roots from root cycles - // We take the nodes with most incoming edges - // inside of the cycle - for (const cycle of rootCycles) { - let max = 0; - /** @type {Set>} */ - const cycleRoots = new Set(); - const nodes = cycle.nodes; - for (const node of nodes) { - for (const dep of node.dependencies) { - if (nodes.has(dep)) { - dep.incoming++; - if (dep.incoming < max) continue; - if (dep.incoming > max) { - cycleRoots.clear(); - max = dep.incoming; - } - cycleRoots.add(dep); - } - } - } - for (const cycleRoot of cycleRoots) { - roots.add(cycleRoot); - } - } - - // When roots were found, return them - if (roots.size > 0) { - return Array.from(roots, r => r.item); - } - - throw new Error("Implementation of findGraphRoots is broken"); -}; diff --git a/webpack-lib/lib/util/fs.js b/webpack-lib/lib/util/fs.js deleted file mode 100644 index 14a18c6dc7b..00000000000 --- a/webpack-lib/lib/util/fs.js +++ /dev/null @@ -1,651 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const path = require("path"); - -/** @typedef {import("../../declarations/WebpackOptions").WatchOptions} WatchOptions */ -/** @typedef {import("../FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */ - -/** - * @template T - * @typedef {object} IStatsBase - * @property {() => boolean} isFile - * @property {() => boolean} isDirectory - * @property {() => boolean} isBlockDevice - * @property {() => boolean} isCharacterDevice - * @property {() => boolean} isSymbolicLink - * @property {() => boolean} isFIFO - * @property {() => boolean} isSocket - * @property {T} dev - * @property {T} ino - * @property {T} mode - * @property {T} nlink - * @property {T} uid - * @property {T} gid - * @property {T} rdev - * @property {T} size - * @property {T} blksize - * @property {T} blocks - * @property {T} atimeMs - * @property {T} mtimeMs - * @property {T} ctimeMs - * @property {T} birthtimeMs - * @property {Date} atime - * @property {Date} mtime - * @property {Date} ctime - * @property {Date} birthtime - */ - -/** - * @typedef {IStatsBase} IStats - */ - -/** - * @typedef {IStatsBase & { atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint, birthtimeNs: bigint }} IBigIntStats - */ - -/** - * @typedef {object} Dirent - * @property {() => boolean} isFile - * @property {() => boolean} isDirectory - * @property {() => boolean} isBlockDevice - * @property {() => boolean} isCharacterDevice - * @property {() => boolean} isSymbolicLink - * @property {() => boolean} isFIFO - * @property {() => boolean} isSocket - * @property {string} name - * @property {string} path - */ - -/** @typedef {string | number | boolean | null} JsonPrimitive */ -/** @typedef {JsonValue[]} JsonArray */ -/** @typedef {JsonPrimitive | JsonObject | JsonArray} JsonValue */ -/** @typedef {{[Key in string]: JsonValue} & {[Key in string]?: JsonValue | undefined}} JsonObject */ - -/** @typedef {function(NodeJS.ErrnoException | null): void} NoParamCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, string=): void} StringCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, Buffer=): void} BufferCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (string | Buffer)=): void} StringOrBufferCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (string[])=): void} ReaddirStringCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (Buffer[])=): void} ReaddirBufferCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (string[] | Buffer[])=): void} ReaddirStringOrBufferCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (Dirent[])=): void} ReaddirDirentCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, IStats=): void} StatsCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, IBigIntStats=): void} BigIntStatsCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, (IStats | IBigIntStats)=): void} StatsOrBigIntStatsCallback */ -/** @typedef {function(NodeJS.ErrnoException | null, number=): void} NumberCallback */ -/** @typedef {function(NodeJS.ErrnoException | Error | null, JsonObject=): void} ReadJsonCallback */ - -/** @typedef {Map} TimeInfoEntries */ - -/** - * @typedef {object} WatcherInfo - * @property {Set | null} changes get current aggregated changes that have not yet send to callback - * @property {Set | null} removals get current aggregated removals that have not yet send to callback - * @property {TimeInfoEntries} fileTimeInfoEntries get info about files - * @property {TimeInfoEntries} contextTimeInfoEntries get info about directories - */ - -/** @typedef {Set} Changes */ -/** @typedef {Set} Removals */ - -// TODO webpack 6 deprecate missing getInfo -/** - * @typedef {object} Watcher - * @property {function(): void} close closes the watcher and all underlying file watchers - * @property {function(): void} pause closes the watcher, but keeps underlying file watchers alive until the next watch call - * @property {(function(): Changes | null)=} getAggregatedChanges get current aggregated changes that have not yet send to callback - * @property {(function(): Removals | null)=} getAggregatedRemovals get current aggregated removals that have not yet send to callback - * @property {function(): TimeInfoEntries} getFileTimeInfoEntries get info about files - * @property {function(): TimeInfoEntries} getContextTimeInfoEntries get info about directories - * @property {function(): WatcherInfo=} getInfo get info about timestamps and changes - */ - -/** - * @callback WatchMethod - * @param {Iterable} files watched files - * @param {Iterable} directories watched directories - * @param {Iterable} missing watched existence entries - * @param {number} startTime timestamp of start time - * @param {WatchOptions} options options object - * @param {function(Error | null, TimeInfoEntries=, TimeInfoEntries=, Changes=, Removals=): void} callback aggregated callback - * @param {function(string, number): void} callbackUndelayed callback when the first change was detected - * @returns {Watcher} a watcher - */ - -// TODO webpack 6 make optional methods required and avoid using non standard methods like `join`, `relative`, `dirname`, move IntermediateFileSystemExtras methods to InputFilesystem or OutputFilesystem - -/** - * @typedef {string | Buffer | URL} PathLike - */ - -/** - * @typedef {PathLike | number} PathOrFileDescriptor - */ - -/** - * @typedef {object} ObjectEncodingOptions - * @property {BufferEncoding | null | undefined} [encoding] - */ - -/** - * @typedef {{ - * (path: PathOrFileDescriptor, options: ({ encoding?: null | undefined, flag?: string | undefined } & import("events").Abortable) | undefined | null, callback: BufferCallback): void; - * (path: PathOrFileDescriptor, options: ({ encoding: BufferEncoding, flag?: string | undefined } & import("events").Abortable) | BufferEncoding, callback: StringCallback): void; - * (path: PathOrFileDescriptor, options: (ObjectEncodingOptions & { flag?: string | undefined } & import("events").Abortable) | BufferEncoding | undefined | null, callback: StringOrBufferCallback): void; - * (path: PathOrFileDescriptor, callback: BufferCallback): void; - * }} ReadFile - */ - -/** - * @typedef {{ - * (path: PathOrFileDescriptor, options?: { encoding?: null | undefined, flag?: string | undefined } | null): Buffer; - * (path: PathOrFileDescriptor, options: { encoding: BufferEncoding, flag?: string | undefined } | BufferEncoding): string; - * (path: PathOrFileDescriptor, options?: (ObjectEncodingOptions & { flag?: string | undefined }) | BufferEncoding | null): string | Buffer; - * }} ReadFileSync - */ - -/** - * @typedef {ObjectEncodingOptions | BufferEncoding | undefined | null} EncodingOption - */ - -/** - * @typedef {'buffer'| { encoding: 'buffer' }} BufferEncodingOption - */ - -/** - * @typedef {object} StatOptions - * @property {(boolean | undefined)=} bigint - */ - -/** - * @typedef {object} StatSyncOptions - * @property {(boolean | undefined)=} bigint - * @property {(boolean | undefined)=} throwIfNoEntry - */ - -/** - * @typedef {{ - * (path: PathLike, options: EncodingOption, callback: StringCallback): void; - * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void; - * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void; - * (path: PathLike, callback: StringCallback): void; - * }} Readlink - */ - -/** - * @typedef {{ - * (path: PathLike, options?: EncodingOption): string; - * (path: PathLike, options: BufferEncodingOption): Buffer; - * (path: PathLike, options?: EncodingOption): string | Buffer; - * }} ReadlinkSync - */ - -/** - * @typedef {{ - * (path: PathLike, options: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | undefined | null, callback: ReaddirStringCallback): void; - * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer', callback: ReaddirBufferCallback): void; - * (path: PathLike, callback: ReaddirStringCallback): void; - * (path: PathLike, options: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | undefined | null, callback: ReaddirStringOrBufferCallback): void; - * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }, callback: ReaddirDirentCallback): void; - * }} Readdir - */ - -/** - * @typedef {{ - * (path: PathLike, options?: { encoding: BufferEncoding | null, withFileTypes?: false | undefined, recursive?: boolean | undefined } | BufferEncoding | null): string[]; - * (path: PathLike, options: { encoding: 'buffer', withFileTypes?: false | undefined, recursive?: boolean | undefined } | 'buffer'): Buffer[]; - * (path: PathLike, options?: (ObjectEncodingOptions & { withFileTypes?: false | undefined, recursive?: boolean | undefined }) | BufferEncoding | null): string[] | Buffer[]; - * (path: PathLike, options: ObjectEncodingOptions & { withFileTypes: true, recursive?: boolean | undefined }): Dirent[]; - * }} ReaddirSync - */ - -/** - * @typedef {{ - * (path: PathLike, callback: StatsCallback): void; - * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void; - * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void; - * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void; - * }} Stat - */ - -/** - * @typedef {{ - * (path: PathLike, options?: undefined): IStats; - * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined; - * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined; - * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats; - * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats; - * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats; - * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined; - * }} StatSync - */ - -/** - * @typedef {{ - * (path: PathLike, callback: StatsCallback): void; - * (path: PathLike, options: (StatOptions & { bigint?: false | undefined }) | undefined, callback: StatsCallback): void; - * (path: PathLike, options: StatOptions & { bigint: true }, callback: BigIntStatsCallback): void; - * (path: PathLike, options: StatOptions | undefined, callback: StatsOrBigIntStatsCallback): void; - * }} LStat - */ - -/** - * @typedef {{ - * (path: PathLike, options?: undefined): IStats; - * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined, throwIfNoEntry: false }): IStats | undefined; - * (path: PathLike, options: StatSyncOptions & { bigint: true, throwIfNoEntry: false }): IBigIntStats | undefined; - * (path: PathLike, options?: StatSyncOptions & { bigint?: false | undefined }): IStats; - * (path: PathLike, options: StatSyncOptions & { bigint: true }): IBigIntStats; - * (path: PathLike, options: StatSyncOptions & { bigint: boolean, throwIfNoEntry?: false | undefined }): IStats | IBigIntStats; - * (path: PathLike, options?: StatSyncOptions): IStats | IBigIntStats | undefined; - * }} LStatSync - */ - -/** - * @typedef {{ - * (path: PathLike, options: EncodingOption, callback: StringCallback): void; - * (path: PathLike, options: BufferEncodingOption, callback: BufferCallback): void; - * (path: PathLike, options: EncodingOption, callback: StringOrBufferCallback): void; - * (path: PathLike, callback: StringCallback): void; - * }} RealPath - */ - -/** - * @typedef {{ - * (path: PathLike, options?: EncodingOption): string; - * (path: PathLike, options: BufferEncodingOption): Buffer; - * (path: PathLike, options?: EncodingOption): string | Buffer; - * }} RealPathSync - */ - -/** - * @typedef {function(PathOrFileDescriptor, ReadJsonCallback): void} ReadJson - */ - -/** - * @typedef {function(PathOrFileDescriptor): JsonObject} ReadJsonSync - */ - -/** - * @typedef {function((string | string[] | Set)=): void} Purge - */ - -/** - * @typedef {object} InputFileSystem - * @property {ReadFile} readFile - * @property {ReadFileSync=} readFileSync - * @property {Readlink} readlink - * @property {ReadlinkSync=} readlinkSync - * @property {Readdir} readdir - * @property {ReaddirSync=} readdirSync - * @property {Stat} stat - * @property {StatSync=} statSync - * @property {LStat=} lstat - * @property {LStatSync=} lstatSync - * @property {RealPath=} realpath - * @property {RealPathSync=} realpathSync - * @property {ReadJson=} readJson - * @property {ReadJsonSync=} readJsonSync - * @property {Purge=} purge - * @property {(function(string, string): string)=} join - * @property {(function(string, string): string)=} relative - * @property {(function(string): string)=} dirname - */ - -/** - * @typedef {number | string} Mode - */ - -/** - * @typedef {(ObjectEncodingOptions & import("events").Abortable & { mode?: Mode | undefined, flag?: string | undefined, flush?: boolean | undefined }) | BufferEncoding | null} WriteFileOptions - */ - -/** - * @typedef {{ - * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, options: WriteFileOptions, callback: NoParamCallback): void; - * (file: PathOrFileDescriptor, data: string | NodeJS.ArrayBufferView, callback: NoParamCallback): void; - * }} WriteFile - */ - -/** - * @typedef {{ recursive?: boolean | undefined, mode?: Mode | undefined }} MakeDirectoryOptions - */ - -/** - * @typedef {{ - * (file: PathLike, options: MakeDirectoryOptions & { recursive: true }, callback: StringCallback): void; - * (file: PathLike, options: Mode | (MakeDirectoryOptions & { recursive?: false | undefined; }) | null | undefined, callback: NoParamCallback): void; - * (file: PathLike, options: Mode | MakeDirectoryOptions | null | undefined, callback: StringCallback): void; - * (file: PathLike, callback: NoParamCallback): void; - * }} Mkdir - */ - -/** - * @typedef {{ maxRetries?: number | undefined, recursive?: boolean | undefined, retryDelay?: number | undefined }} RmDirOptions - */ - -/** - * @typedef {{ - * (file: PathLike, callback: NoParamCallback): void; - * (file: PathLike, options: RmDirOptions, callback: NoParamCallback): void; - * }} Rmdir - */ - -/** - * @typedef {function(PathLike, NoParamCallback): void} Unlink - */ - -/** - * @typedef {object} OutputFileSystem - * @property {WriteFile} writeFile - * @property {Mkdir} mkdir - * @property {Readdir=} readdir - * @property {Rmdir=} rmdir - * @property {Unlink=} unlink - * @property {Stat} stat - * @property {LStat=} lstat - * @property {ReadFile} readFile - * @property {(function(string, string): string)=} join - * @property {(function(string, string): string)=} relative - * @property {(function(string): string)=} dirname - */ - -/** - * @typedef {object} WatchFileSystem - * @property {WatchMethod} watch - */ - -/** - * @typedef {{ - * (path: PathLike, options: MakeDirectoryOptions & { recursive: true }): string | undefined; - * (path: PathLike, options?: Mode | (MakeDirectoryOptions & { recursive?: false | undefined }) | null): void; - * (path: PathLike, options?: Mode | MakeDirectoryOptions | null): string | undefined; - * }} MkdirSync - */ - -/** - * @typedef {object} StreamOptions - * @property {(string | undefined)=} flags - * @property {(BufferEncoding | undefined)} encoding - * @property {(number | EXPECTED_ANY | undefined)=} fd - * @property {(number | undefined)=} mode - * @property {(boolean | undefined)=} autoClose - * @property {(boolean | undefined)=} emitClose - * @property {(number | undefined)=} start - * @property {(AbortSignal | null | undefined)=} signal - */ - -/** - * @typedef {object} FSImplementation - * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} open - * @property {((...args: EXPECTED_ANY[]) => EXPECTED_ANY)=} close - */ - -/** - * @typedef {FSImplementation & { write: (...args: EXPECTED_ANY[]) => EXPECTED_ANY; close?: (...args: EXPECTED_ANY[]) => EXPECTED_ANY }} CreateWriteStreamFSImplementation - */ - -/** - * @typedef {StreamOptions & { fs?: CreateWriteStreamFSImplementation | null | undefined }} WriteStreamOptions - */ - -/** - * @typedef {function(PathLike, (BufferEncoding | WriteStreamOptions)=): NodeJS.WritableStream} CreateWriteStream - */ - -/** - * @typedef {number | string} OpenMode - */ - -/** - * @typedef {{ - * (file: PathLike, flags: OpenMode | undefined, mode: Mode | undefined | null, callback: NumberCallback): void; - * (file: PathLike, flags: OpenMode | undefined, callback: NumberCallback): void; - * (file: PathLike, callback: NumberCallback): void; - * }} Open - */ - -/** - * @typedef {number | bigint} ReadPosition - */ - -/** - * @typedef {object} ReadSyncOptions - * @property {(number | undefined)=} offset - * @property {(number | undefined)=} length - * @property {(ReadPosition | null | undefined)=} position - */ - -/** - * @template {NodeJS.ArrayBufferView} TBuffer - * @typedef {object} ReadAsyncOptions - * @property {(number | undefined)=} offset - * @property {(number | undefined)=} length - * @property {(ReadPosition | null | undefined)=} position - * @property {TBuffer=} buffer - */ - -/** - * @template {NodeJS.ArrayBufferView} [TBuffer=Buffer] - * @typedef {{ - * (fd: number, buffer: TBuffer, offset: number, length: number, position: ReadPosition | null, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void; - * (fd: number, options: ReadAsyncOptions, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: TBuffer) => void): void; - * (fd: number, callback: (err: NodeJS.ErrnoException | null, bytesRead: number, buffer: NodeJS.ArrayBufferView) => void): void; - * }} Read - */ - -/** @typedef {function(number, NoParamCallback): void} Close */ - -/** @typedef {function(PathLike, PathLike, NoParamCallback): void} Rename */ - -/** - * @typedef {object} IntermediateFileSystemExtras - * @property {MkdirSync} mkdirSync - * @property {CreateWriteStream} createWriteStream - * @property {Open} open - * @property {Read} read - * @property {Close} close - * @property {Rename} rename - */ - -/** @typedef {InputFileSystem & OutputFileSystem & IntermediateFileSystemExtras} IntermediateFileSystem */ - -/** - * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system - * @param {string} rootPath the root path - * @param {string} targetPath the target path - * @returns {string} location of targetPath relative to rootPath - */ -const relative = (fs, rootPath, targetPath) => { - if (fs && fs.relative) { - return fs.relative(rootPath, targetPath); - } else if (path.posix.isAbsolute(rootPath)) { - return path.posix.relative(rootPath, targetPath); - } else if (path.win32.isAbsolute(rootPath)) { - return path.win32.relative(rootPath, targetPath); - } - throw new Error( - `${rootPath} is neither a posix nor a windows path, and there is no 'relative' method defined in the file system` - ); -}; -module.exports.relative = relative; - -/** - * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system - * @param {string} rootPath a path - * @param {string} filename a filename - * @returns {string} the joined path - */ -const join = (fs, rootPath, filename) => { - if (fs && fs.join) { - return fs.join(rootPath, filename); - } else if (path.posix.isAbsolute(rootPath)) { - return path.posix.join(rootPath, filename); - } else if (path.win32.isAbsolute(rootPath)) { - return path.win32.join(rootPath, filename); - } - throw new Error( - `${rootPath} is neither a posix nor a windows path, and there is no 'join' method defined in the file system` - ); -}; -module.exports.join = join; - -/** - * @param {InputFileSystem|OutputFileSystem|undefined} fs a file system - * @param {string} absPath an absolute path - * @returns {string} the parent directory of the absolute path - */ -const dirname = (fs, absPath) => { - if (fs && fs.dirname) { - return fs.dirname(absPath); - } else if (path.posix.isAbsolute(absPath)) { - return path.posix.dirname(absPath); - } else if (path.win32.isAbsolute(absPath)) { - return path.win32.dirname(absPath); - } - throw new Error( - `${absPath} is neither a posix nor a windows path, and there is no 'dirname' method defined in the file system` - ); -}; -module.exports.dirname = dirname; - -/** - * @param {OutputFileSystem} fs a file system - * @param {string} p an absolute path - * @param {function(Error=): void} callback callback function for the error - * @returns {void} - */ -const mkdirp = (fs, p, callback) => { - fs.mkdir(p, err => { - if (err) { - if (err.code === "ENOENT") { - const dir = dirname(fs, p); - if (dir === p) { - callback(err); - return; - } - mkdirp(fs, dir, err => { - if (err) { - callback(err); - return; - } - fs.mkdir(p, err => { - if (err) { - if (err.code === "EEXIST") { - callback(); - return; - } - callback(err); - return; - } - callback(); - }); - }); - return; - } else if (err.code === "EEXIST") { - callback(); - return; - } - callback(err); - return; - } - callback(); - }); -}; -module.exports.mkdirp = mkdirp; - -/** - * @param {IntermediateFileSystem} fs a file system - * @param {string} p an absolute path - * @returns {void} - */ -const mkdirpSync = (fs, p) => { - try { - fs.mkdirSync(p); - } catch (err) { - if (err) { - if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") { - const dir = dirname(fs, p); - if (dir === p) { - throw err; - } - mkdirpSync(fs, dir); - fs.mkdirSync(p); - return; - } else if (/** @type {NodeJS.ErrnoException} */ (err).code === "EEXIST") { - return; - } - throw err; - } - } -}; -module.exports.mkdirpSync = mkdirpSync; - -/** - * @param {InputFileSystem} fs a file system - * @param {string} p an absolute path - * @param {ReadJsonCallback} callback callback - * @returns {void} - */ -const readJson = (fs, p, callback) => { - if ("readJson" in fs) - return /** @type {NonNullable} */ ( - fs.readJson - )(p, callback); - fs.readFile(p, (err, buf) => { - if (err) return callback(err); - let data; - try { - data = JSON.parse(/** @type {Buffer} */ (buf).toString("utf-8")); - } catch (err1) { - return callback(/** @type {Error} */ (err1)); - } - return callback(null, data); - }); -}; -module.exports.readJson = readJson; - -/** - * @param {InputFileSystem} fs a file system - * @param {string} p an absolute path - * @param {function(NodeJS.ErrnoException | Error | null, (IStats | string)=): void} callback callback - * @returns {void} - */ -const lstatReadlinkAbsolute = (fs, p, callback) => { - let i = 3; - const doReadLink = () => { - fs.readlink(p, (err, target) => { - if (err && --i > 0) { - // It might was just changed from symlink to file - // we retry 2 times to catch this case before throwing the error - return doStat(); - } - if (err) return callback(err); - const value = /** @type {string} */ (target).toString(); - callback(null, join(fs, dirname(fs, p), value)); - }); - }; - const doStat = () => { - if ("lstat" in fs) { - return /** @type {NonNullable} */ (fs.lstat)( - p, - (err, stats) => { - if (err) return callback(err); - if (/** @type {IStats} */ (stats).isSymbolicLink()) { - return doReadLink(); - } - callback(null, stats); - } - ); - } - return fs.stat(p, callback); - }; - if ("lstat" in fs) return doStat(); - doReadLink(); -}; -module.exports.lstatReadlinkAbsolute = lstatReadlinkAbsolute; diff --git a/webpack-lib/lib/util/generateDebugId.js b/webpack-lib/lib/util/generateDebugId.js deleted file mode 100644 index bd501f89a2d..00000000000 --- a/webpack-lib/lib/util/generateDebugId.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -const createHash = require("./createHash"); - -/** - * @param {string | Buffer} content content - * @param {string} file file - * @returns {string} generated debug id - */ -module.exports = (content, file) => { - // We need a uuid which is 128 bits so we need 2x 64 bit hashes. - // The first 64 bits is a hash of the source. - const sourceHash = createHash("xxhash64").update(content).digest("hex"); - // The next 64 bits is a hash of the filename and sourceHash - const hash128 = `${sourceHash}${createHash("xxhash64") - .update(file) - .update(sourceHash) - .digest("hex")}`; - - return [ - hash128.slice(0, 8), - hash128.slice(8, 12), - `4${hash128.slice(12, 15)}`, - ((Number.parseInt(hash128.slice(15, 16), 16) & 3) | 8).toString(16) + - hash128.slice(17, 20), - hash128.slice(20, 32) - ].join("-"); -}; diff --git a/webpack-lib/lib/util/hash/BatchedHash.js b/webpack-lib/lib/util/hash/BatchedHash.js deleted file mode 100644 index cc030f8bd7d..00000000000 --- a/webpack-lib/lib/util/hash/BatchedHash.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Hash = require("../Hash"); -const MAX_SHORT_STRING = require("./wasm-hash").MAX_SHORT_STRING; - -class BatchedHash extends Hash { - /** - * @param {Hash} hash hash - */ - constructor(hash) { - super(); - this.string = undefined; - this.encoding = undefined; - this.hash = hash; - } - - /** - * Update hash {@link https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding} - * @param {string|Buffer} data data - * @param {string=} inputEncoding data encoding - * @returns {this} updated hash - */ - update(data, inputEncoding) { - if (this.string !== undefined) { - if ( - typeof data === "string" && - inputEncoding === this.encoding && - this.string.length + data.length < MAX_SHORT_STRING - ) { - this.string += data; - return this; - } - this.hash.update(this.string, this.encoding); - this.string = undefined; - } - if (typeof data === "string") { - if ( - data.length < MAX_SHORT_STRING && - // base64 encoding is not valid since it may contain padding chars - (!inputEncoding || !inputEncoding.startsWith("ba")) - ) { - this.string = data; - this.encoding = inputEncoding; - } else { - this.hash.update(data, inputEncoding); - } - } else { - this.hash.update(data); - } - return this; - } - - /** - * Calculates the digest {@link https://nodejs.org/api/crypto.html#crypto_hash_digest_encoding} - * @param {string=} encoding encoding of the return value - * @returns {string|Buffer} digest - */ - digest(encoding) { - if (this.string !== undefined) { - this.hash.update(this.string, this.encoding); - } - return this.hash.digest(encoding); - } -} - -module.exports = BatchedHash; diff --git a/webpack-lib/lib/util/hash/md4.js b/webpack-lib/lib/util/hash/md4.js deleted file mode 100644 index 425edc3b9ba..00000000000 --- a/webpack-lib/lib/util/hash/md4.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const create = require("./wasm-hash"); - -// #region wasm code: md4 (../../../assembly/hash/md4.asm.ts) --initialMemory 1 -const md4 = new WebAssembly.Module( - Buffer.from( - // 2154 bytes - "AGFzbQEAAAABCAJgAX8AYAAAAwUEAQAAAAUDAQABBhoFfwFBAAt/AUEAC38BQQALfwFBAAt/AUEACwciBARpbml0AAAGdXBkYXRlAAIFZmluYWwAAwZtZW1vcnkCAAqJEAQmAEGBxpS6BiQBQYnXtv5+JAJB/rnrxXkkA0H2qMmBASQEQQAkAAvQCgEZfyMBIQUjAiECIwMhAyMEIQQDQCAAIAFLBEAgASgCBCIOIAQgAyABKAIAIg8gBSAEIAIgAyAEc3FzampBA3ciCCACIANzcXNqakEHdyEJIAEoAgwiBiACIAggASgCCCIQIAMgAiAJIAIgCHNxc2pqQQt3IgogCCAJc3FzampBE3chCyABKAIUIgcgCSAKIAEoAhAiESAIIAkgCyAJIApzcXNqakEDdyIMIAogC3Nxc2pqQQd3IQ0gASgCHCIJIAsgDCABKAIYIgggCiALIA0gCyAMc3FzampBC3ciEiAMIA1zcXNqakETdyETIAEoAiQiFCANIBIgASgCICIVIAwgDSATIA0gEnNxc2pqQQN3IgwgEiATc3FzampBB3chDSABKAIsIgsgEyAMIAEoAigiCiASIBMgDSAMIBNzcXNqakELdyISIAwgDXNxc2pqQRN3IRMgASgCNCIWIA0gEiABKAIwIhcgDCANIBMgDSASc3FzampBA3ciGCASIBNzcXNqakEHdyEZIBggASgCPCINIBMgGCABKAI4IgwgEiATIBkgEyAYc3FzampBC3ciEiAYIBlzcXNqakETdyITIBIgGXJxIBIgGXFyaiAPakGZ84nUBWpBA3ciGCATIBIgGSAYIBIgE3JxIBIgE3FyaiARakGZ84nUBWpBBXciEiATIBhycSATIBhxcmogFWpBmfOJ1AVqQQl3IhMgEiAYcnEgEiAYcXJqIBdqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAOakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAHakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogFGpBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIBZqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAQakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAIakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogCmpBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIAxqQZnzidQFakENdyIYIBIgE3JxIBIgE3FyaiAGakGZ84nUBWpBA3ciGSAYIBMgEiAZIBMgGHJxIBMgGHFyaiAJakGZ84nUBWpBBXciEiAYIBlycSAYIBlxcmogC2pBmfOJ1AVqQQl3IhMgEiAZcnEgEiAZcXJqIA1qQZnzidQFakENdyIYIBNzIBJzaiAPakGh1+f2BmpBA3ciDyAYIBMgEiAPIBhzIBNzaiAVakGh1+f2BmpBCXciEiAPcyAYc2ogEWpBodfn9gZqQQt3IhEgEnMgD3NqIBdqQaHX5/YGakEPdyIPIBFzIBJzaiAQakGh1+f2BmpBA3ciECAPIBEgEiAPIBBzIBFzaiAKakGh1+f2BmpBCXciCiAQcyAPc2ogCGpBodfn9gZqQQt3IgggCnMgEHNqIAxqQaHX5/YGakEPdyIMIAhzIApzaiAOakGh1+f2BmpBA3ciDiAMIAggCiAMIA5zIAhzaiAUakGh1+f2BmpBCXciCCAOcyAMc2ogB2pBodfn9gZqQQt3IgcgCHMgDnNqIBZqQaHX5/YGakEPdyIKIAdzIAhzaiAGakGh1+f2BmpBA3ciBiAFaiEFIAIgCiAHIAggBiAKcyAHc2ogC2pBodfn9gZqQQl3IgcgBnMgCnNqIAlqQaHX5/YGakELdyIIIAdzIAZzaiANakGh1+f2BmpBD3dqIQIgAyAIaiEDIAQgB2ohBCABQUBrIQEMAQsLIAUkASACJAIgAyQDIAQkBAsNACAAEAEjACAAaiQAC/8EAgN/AX4jACAAaq1CA4YhBCAAQcgAakFAcSICQQhrIQMgACIBQQFqIQAgAUGAAToAAANAIAAgAklBACAAQQdxGwRAIABBADoAACAAQQFqIQAMAQsLA0AgACACSQRAIABCADcDACAAQQhqIQAMAQsLIAMgBDcDACACEAFBACMBrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBCCMCrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBECMDrSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwBBGCMErSIEQv//A4MgBEKAgPz/D4NCEIaEIgRC/4GAgPAfgyAEQoD+g4CA4D+DQgiGhCIEQo+AvIDwgcAHg0IIhiAEQvCBwIeAnoD4AINCBIiEIgRChoyYsODAgYMGfEIEiEKBgoSIkKDAgAGDQid+IARCsODAgYOGjJgwhHw3AwAL", - "base64" - ) -); -// #endregion - -module.exports = create.bind(null, md4, [], 64, 32); diff --git a/webpack-lib/lib/util/hash/wasm-hash.js b/webpack-lib/lib/util/hash/wasm-hash.js deleted file mode 100644 index 0c70b96158c..00000000000 --- a/webpack-lib/lib/util/hash/wasm-hash.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -// 65536 is the size of a wasm memory page -// 64 is the maximum chunk size for every possible wasm hash implementation -// 4 is the maximum number of bytes per char for string encoding (max is utf-8) -// ~3 makes sure that it's always a block of 4 chars, so avoid partially encoded bytes for base64 -const MAX_SHORT_STRING = Math.floor((65536 - 64) / 4) & ~3; - -class WasmHash { - /** - * @param {WebAssembly.Instance} instance wasm instance - * @param {WebAssembly.Instance[]} instancesPool pool of instances - * @param {number} chunkSize size of data chunks passed to wasm - * @param {number} digestSize size of digest returned by wasm - */ - constructor(instance, instancesPool, chunkSize, digestSize) { - const exports = /** @type {any} */ (instance.exports); - exports.init(); - this.exports = exports; - this.mem = Buffer.from(exports.memory.buffer, 0, 65536); - this.buffered = 0; - this.instancesPool = instancesPool; - this.chunkSize = chunkSize; - this.digestSize = digestSize; - } - - reset() { - this.buffered = 0; - this.exports.init(); - } - - /** - * @param {Buffer | string} data data - * @param {BufferEncoding=} encoding encoding - * @returns {this} itself - */ - update(data, encoding) { - if (typeof data === "string") { - while (data.length > MAX_SHORT_STRING) { - this._updateWithShortString(data.slice(0, MAX_SHORT_STRING), encoding); - data = data.slice(MAX_SHORT_STRING); - } - this._updateWithShortString(data, encoding); - return this; - } - this._updateWithBuffer(data); - return this; - } - - /** - * @param {string} data data - * @param {BufferEncoding=} encoding encoding - * @returns {void} - */ - _updateWithShortString(data, encoding) { - const { exports, buffered, mem, chunkSize } = this; - let endPos; - if (data.length < 70) { - if (!encoding || encoding === "utf-8" || encoding === "utf8") { - endPos = buffered; - for (let i = 0; i < data.length; i++) { - const cc = data.charCodeAt(i); - if (cc < 0x80) mem[endPos++] = cc; - else if (cc < 0x800) { - mem[endPos] = (cc >> 6) | 0xc0; - mem[endPos + 1] = (cc & 0x3f) | 0x80; - endPos += 2; - } else { - // bail-out for weird chars - endPos += mem.write(data.slice(i), endPos, encoding); - break; - } - } - } else if (encoding === "latin1") { - endPos = buffered; - for (let i = 0; i < data.length; i++) { - const cc = data.charCodeAt(i); - mem[endPos++] = cc; - } - } else { - endPos = buffered + mem.write(data, buffered, encoding); - } - } else { - endPos = buffered + mem.write(data, buffered, encoding); - } - if (endPos < chunkSize) { - this.buffered = endPos; - } else { - const l = endPos & ~(this.chunkSize - 1); - exports.update(l); - const newBuffered = endPos - l; - this.buffered = newBuffered; - if (newBuffered > 0) mem.copyWithin(0, l, endPos); - } - } - - /** - * @param {Buffer} data data - * @returns {void} - */ - _updateWithBuffer(data) { - const { exports, buffered, mem } = this; - const length = data.length; - if (buffered + length < this.chunkSize) { - data.copy(mem, buffered, 0, length); - this.buffered += length; - } else { - const l = (buffered + length) & ~(this.chunkSize - 1); - if (l > 65536) { - let i = 65536 - buffered; - data.copy(mem, buffered, 0, i); - exports.update(65536); - const stop = l - buffered - 65536; - while (i < stop) { - data.copy(mem, 0, i, i + 65536); - exports.update(65536); - i += 65536; - } - data.copy(mem, 0, i, l - buffered); - exports.update(l - buffered - i); - } else { - data.copy(mem, buffered, 0, l - buffered); - exports.update(l); - } - const newBuffered = length + buffered - l; - this.buffered = newBuffered; - if (newBuffered > 0) data.copy(mem, 0, length - newBuffered, length); - } - } - - /** - * @param {BufferEncoding} type type - * @returns {Buffer | string} digest - */ - digest(type) { - const { exports, buffered, mem, digestSize } = this; - exports.final(buffered); - this.instancesPool.push(this); - const hex = mem.toString("latin1", 0, digestSize); - if (type === "hex") return hex; - if (type === "binary" || !type) return Buffer.from(hex, "hex"); - return Buffer.from(hex, "hex").toString(type); - } -} - -/** - * @param {TODO} wasmModule wasm module - * @param {WasmHash[]} instancesPool pool of instances - * @param {number} chunkSize size of data chunks passed to wasm - * @param {number} digestSize size of digest returned by wasm - * @returns {WasmHash} wasm hash - */ -const create = (wasmModule, instancesPool, chunkSize, digestSize) => { - if (instancesPool.length > 0) { - const old = /** @type {WasmHash} */ (instancesPool.pop()); - old.reset(); - return old; - } - - return new WasmHash( - new WebAssembly.Instance(wasmModule), - instancesPool, - chunkSize, - digestSize - ); -}; - -module.exports = create; -module.exports.MAX_SHORT_STRING = MAX_SHORT_STRING; diff --git a/webpack-lib/lib/util/hash/xxhash64.js b/webpack-lib/lib/util/hash/xxhash64.js deleted file mode 100644 index b9262b8753c..00000000000 --- a/webpack-lib/lib/util/hash/xxhash64.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const create = require("./wasm-hash"); - -// #region wasm code: xxhash64 (../../../assembly/hash/xxhash64.asm.ts) --initialMemory 1 -const xxhash64 = new WebAssembly.Module( - Buffer.from( - // 1160 bytes - "AGFzbQEAAAABCAJgAX8AYAAAAwQDAQAABQMBAAEGGgV+AUIAC34BQgALfgFCAAt+AUIAC34BQgALByIEBGluaXQAAAZ1cGRhdGUAAQVmaW5hbAACBm1lbW9yeQIACqgIAzAAQtbrgu7q/Yn14AAkAELP1tO+0ser2UIkAUIAJAJC+erQ0OfJoeThACQDQgAkBAvUAQIBfwR+IABFBEAPCyMEIACtfCQEIwAhAiMBIQMjAiEEIwMhBQNAIAIgASkDAELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiECIAMgASkDCELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEDIAQgASkDEELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEEIAUgASkDGELP1tO+0ser2UJ+fEIfiUKHla+vmLbem55/fiEFIAFBIGoiASAASQ0ACyACJAAgAyQBIAQkAiAFJAMLngYCAn8CfiMEQgBSBH4jAEIBiSMBQgeJfCMCQgyJfCMDQhKJfCMAQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IwFCz9bTvtLHq9lCfkIfiUKHla+vmLbem55/foVCh5Wvr5i23puef35CnaO16oOxjYr6AH0jAkLP1tO+0ser2UJ+Qh+JQoeVr6+Ytt6bnn9+hUKHla+vmLbem55/fkKdo7Xqg7GNivoAfSMDQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9BULFz9my8eW66icLIwQgAK18fCEDA0AgAUEIaiICIABNBEAgAyABKQMAQs/W077Sx6vZQn5CH4lCh5Wvr5i23puef36FQhuJQoeVr6+Ytt6bnn9+Qp2jteqDsY2K+gB9IQMgAiEBDAELCyABQQRqIgIgAE0EQCADIAE1AgBCh5Wvr5i23puef36FQheJQs/W077Sx6vZQn5C+fPd8Zn2masWfCEDIAIhAQsDQCAAIAFHBEAgAyABMQAAQsXP2bLx5brqJ36FQguJQoeVr6+Ytt6bnn9+IQMgAUEBaiEBDAELC0EAIAMgA0IhiIVCz9bTvtLHq9lCfiIDQh2IIAOFQvnz3fGZ9pmrFn4iA0IgiCADhSIDQiCIIgRC//8Dg0IghiAEQoCA/P8Pg0IQiIQiBEL/gYCA8B+DQhCGIARCgP6DgIDgP4NCCIiEIgRCj4C8gPCBwAeDQgiGIARC8IHAh4CegPgAg0IEiIQiBEKGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gBEKw4MCBg4aMmDCEfDcDAEEIIANC/////w+DIgNC//8Dg0IghiADQoCA/P8Pg0IQiIQiA0L/gYCA8B+DQhCGIANCgP6DgIDgP4NCCIiEIgNCj4C8gPCBwAeDQgiGIANC8IHAh4CegPgAg0IEiIQiA0KGjJiw4MCBgwZ8QgSIQoGChIiQoMCAAYNCJ34gA0Kw4MCBg4aMmDCEfDcDAAs=", - "base64" - ) -); -// #endregion - -module.exports = create.bind(null, xxhash64, [], 32, 16); diff --git a/webpack-lib/lib/util/identifier.js b/webpack-lib/lib/util/identifier.js deleted file mode 100644 index e94a63b5034..00000000000 --- a/webpack-lib/lib/util/identifier.js +++ /dev/null @@ -1,403 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const path = require("path"); - -const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; -const SEGMENTS_SPLIT_REGEXP = /([|!])/; -const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; - -/** - * @typedef {object} MakeRelativePathsCache - * @property {Map>=} relativePaths - */ - -/** - * @param {string} relativePath relative path - * @returns {string} request - */ -const relativePathToRequest = relativePath => { - if (relativePath === "") return "./."; - if (relativePath === "..") return "../."; - if (relativePath.startsWith("../")) return relativePath; - return `./${relativePath}`; -}; - -/** - * @param {string} context context for relative path - * @param {string} maybeAbsolutePath path to make relative - * @returns {string} relative path in request style - */ -const absoluteToRequest = (context, maybeAbsolutePath) => { - if (maybeAbsolutePath[0] === "/") { - if ( - maybeAbsolutePath.length > 1 && - maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/" - ) { - // this 'path' is actually a regexp generated by dynamic requires. - // Don't treat it as an absolute path. - return maybeAbsolutePath; - } - - const querySplitPos = maybeAbsolutePath.indexOf("?"); - let resource = - querySplitPos === -1 - ? maybeAbsolutePath - : maybeAbsolutePath.slice(0, querySplitPos); - resource = relativePathToRequest(path.posix.relative(context, resource)); - return querySplitPos === -1 - ? resource - : resource + maybeAbsolutePath.slice(querySplitPos); - } - - if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { - const querySplitPos = maybeAbsolutePath.indexOf("?"); - let resource = - querySplitPos === -1 - ? maybeAbsolutePath - : maybeAbsolutePath.slice(0, querySplitPos); - resource = path.win32.relative(context, resource); - if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { - resource = relativePathToRequest( - resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/") - ); - } - return querySplitPos === -1 - ? resource - : resource + maybeAbsolutePath.slice(querySplitPos); - } - - // not an absolute path - return maybeAbsolutePath; -}; - -/** - * @param {string} context context for relative path - * @param {string} relativePath path - * @returns {string} absolute path - */ -const requestToAbsolute = (context, relativePath) => { - if (relativePath.startsWith("./") || relativePath.startsWith("../")) - return path.join(context, relativePath); - return relativePath; -}; - -/** - * @template T - * @typedef {function(string, object=): T} MakeCacheableResult - */ - -/** - * @template T - * @typedef {function(string): T} BindCacheResultFn - */ - -/** - * @template T - * @typedef {function(object): BindCacheResultFn} BindCache - */ - -/** - * @template T - * @param {(function(string): T)} realFn real function - * @returns {MakeCacheableResult & { bindCache: BindCache }} cacheable function - */ -const makeCacheable = realFn => { - /** - * @template T - * @typedef {Map} CacheItem - */ - /** @type {WeakMap>} */ - const cache = new WeakMap(); - - /** - * @param {object} associatedObjectForCache an object to which the cache will be attached - * @returns {CacheItem} cache item - */ - const getCache = associatedObjectForCache => { - const entry = cache.get(associatedObjectForCache); - if (entry !== undefined) return entry; - /** @type {Map} */ - const map = new Map(); - cache.set(associatedObjectForCache, map); - return map; - }; - - /** @type {MakeCacheableResult & { bindCache: BindCache }} */ - const fn = (str, associatedObjectForCache) => { - if (!associatedObjectForCache) return realFn(str); - const cache = getCache(associatedObjectForCache); - const entry = cache.get(str); - if (entry !== undefined) return entry; - const result = realFn(str); - cache.set(str, result); - return result; - }; - - /** @type {BindCache} */ - fn.bindCache = associatedObjectForCache => { - const cache = getCache(associatedObjectForCache); - /** - * @param {string} str string - * @returns {T} value - */ - return str => { - const entry = cache.get(str); - if (entry !== undefined) return entry; - const result = realFn(str); - cache.set(str, result); - return result; - }; - }; - - return fn; -}; - -/** @typedef {function(string, string, object=): string} MakeCacheableWithContextResult */ -/** @typedef {function(string, string): string} BindCacheForContextResultFn */ -/** @typedef {function(string): string} BindContextCacheForContextResultFn */ -/** @typedef {function(object=): BindCacheForContextResultFn} BindCacheForContext */ -/** @typedef {function(string, object=): BindContextCacheForContextResultFn} BindContextCacheForContext */ - -/** - * @param {function(string, string): string} fn function - * @returns {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} cacheable function with context - */ -const makeCacheableWithContext = fn => { - /** @type {WeakMap>>} */ - const cache = new WeakMap(); - - /** @type {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} */ - const cachedFn = (context, identifier, associatedObjectForCache) => { - if (!associatedObjectForCache) return fn(context, identifier); - - let innerCache = cache.get(associatedObjectForCache); - if (innerCache === undefined) { - innerCache = new Map(); - cache.set(associatedObjectForCache, innerCache); - } - - let cachedResult; - let innerSubCache = innerCache.get(context); - if (innerSubCache === undefined) { - innerCache.set(context, (innerSubCache = new Map())); - } else { - cachedResult = innerSubCache.get(identifier); - } - - if (cachedResult !== undefined) { - return cachedResult; - } - const result = fn(context, identifier); - innerSubCache.set(identifier, result); - return result; - }; - - /** @type {BindCacheForContext} */ - cachedFn.bindCache = associatedObjectForCache => { - let innerCache; - if (associatedObjectForCache) { - innerCache = cache.get(associatedObjectForCache); - if (innerCache === undefined) { - innerCache = new Map(); - cache.set(associatedObjectForCache, innerCache); - } - } else { - innerCache = new Map(); - } - - /** - * @param {string} context context used to create relative path - * @param {string} identifier identifier used to create relative path - * @returns {string} the returned relative path - */ - const boundFn = (context, identifier) => { - let cachedResult; - let innerSubCache = innerCache.get(context); - if (innerSubCache === undefined) { - innerCache.set(context, (innerSubCache = new Map())); - } else { - cachedResult = innerSubCache.get(identifier); - } - - if (cachedResult !== undefined) { - return cachedResult; - } - const result = fn(context, identifier); - innerSubCache.set(identifier, result); - return result; - }; - - return boundFn; - }; - - /** @type {BindContextCacheForContext} */ - cachedFn.bindContextCache = (context, associatedObjectForCache) => { - let innerSubCache; - if (associatedObjectForCache) { - let innerCache = cache.get(associatedObjectForCache); - if (innerCache === undefined) { - innerCache = new Map(); - cache.set(associatedObjectForCache, innerCache); - } - - innerSubCache = innerCache.get(context); - if (innerSubCache === undefined) { - innerCache.set(context, (innerSubCache = new Map())); - } - } else { - innerSubCache = new Map(); - } - - /** - * @param {string} identifier identifier used to create relative path - * @returns {string} the returned relative path - */ - const boundFn = identifier => { - const cachedResult = innerSubCache.get(identifier); - if (cachedResult !== undefined) { - return cachedResult; - } - const result = fn(context, identifier); - innerSubCache.set(identifier, result); - return result; - }; - - return boundFn; - }; - - return cachedFn; -}; - -/** - * @param {string} context context for relative path - * @param {string} identifier identifier for path - * @returns {string} a converted relative path - */ -const _makePathsRelative = (context, identifier) => - identifier - .split(SEGMENTS_SPLIT_REGEXP) - .map(str => absoluteToRequest(context, str)) - .join(""); - -module.exports.makePathsRelative = makeCacheableWithContext(_makePathsRelative); - -/** - * @param {string} context context for relative path - * @param {string} identifier identifier for path - * @returns {string} a converted relative path - */ -const _makePathsAbsolute = (context, identifier) => - identifier - .split(SEGMENTS_SPLIT_REGEXP) - .map(str => requestToAbsolute(context, str)) - .join(""); - -module.exports.makePathsAbsolute = makeCacheableWithContext(_makePathsAbsolute); - -/** - * @param {string} context absolute context path - * @param {string} request any request string may containing absolute paths, query string, etc. - * @returns {string} a new request string avoiding absolute paths when possible - */ -const _contextify = (context, request) => - request - .split("!") - .map(r => absoluteToRequest(context, r)) - .join("!"); - -const contextify = makeCacheableWithContext(_contextify); -module.exports.contextify = contextify; - -/** - * @param {string} context absolute context path - * @param {string} request any request string - * @returns {string} a new request string using absolute paths when possible - */ -const _absolutify = (context, request) => - request - .split("!") - .map(r => requestToAbsolute(context, r)) - .join("!"); - -const absolutify = makeCacheableWithContext(_absolutify); -module.exports.absolutify = absolutify; - -const PATH_QUERY_FRAGMENT_REGEXP = - /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; -const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/; - -/** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ -/** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */ - -/** - * @param {string} str the path with query and fragment - * @returns {ParsedResource} parsed parts - */ -const _parseResource = str => { - const match = - /** @type {[string, string, string | undefined, string | undefined]} */ - (/** @type {unknown} */ (PATH_QUERY_FRAGMENT_REGEXP.exec(str))); - return { - resource: str, - path: match[1].replace(/\0(.)/g, "$1"), - query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", - fragment: match[3] || "" - }; -}; -module.exports.parseResource = makeCacheable(_parseResource); - -/** - * Parse resource, skips fragment part - * @param {string} str the path with query and fragment - * @returns {ParsedResourceWithoutFragment} parsed parts - */ -const _parseResourceWithoutFragment = str => { - const match = - /** @type {[string, string, string | undefined]} */ - (/** @type {unknown} */ (PATH_QUERY_REGEXP.exec(str))); - return { - resource: str, - path: match[1].replace(/\0(.)/g, "$1"), - query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "" - }; -}; -module.exports.parseResourceWithoutFragment = makeCacheable( - _parseResourceWithoutFragment -); - -/** - * @param {string} filename the filename which should be undone - * @param {string} outputPath the output path that is restored (only relevant when filename contains "..") - * @param {boolean} enforceRelative true returns ./ for empty paths - * @returns {string} repeated ../ to leave the directory of the provided filename to be back on output dir - */ -module.exports.getUndoPath = (filename, outputPath, enforceRelative) => { - let depth = -1; - let append = ""; - outputPath = outputPath.replace(/[\\/]$/, ""); - for (const part of filename.split(/[/\\]+/)) { - if (part === "..") { - if (depth > -1) { - depth--; - } else { - const i = outputPath.lastIndexOf("/"); - const j = outputPath.lastIndexOf("\\"); - const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j); - if (pos < 0) return `${outputPath}/`; - append = `${outputPath.slice(pos + 1)}/${append}`; - outputPath = outputPath.slice(0, pos); - } - } else if (part !== ".") { - depth++; - } - } - return depth > 0 - ? `${"../".repeat(depth)}${append}` - : enforceRelative - ? `./${append}` - : append; -}; diff --git a/webpack-lib/lib/util/internalSerializables.js b/webpack-lib/lib/util/internalSerializables.js deleted file mode 100644 index 3ca8f2b9178..00000000000 --- a/webpack-lib/lib/util/internalSerializables.js +++ /dev/null @@ -1,224 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -// We need to include a list of requires here -// to allow webpack to be bundled with only static requires -// We could use a dynamic require(`../${request}`) but this -// would include too many modules and not every tool is able -// to process this -module.exports = { - AsyncDependenciesBlock: () => require("../AsyncDependenciesBlock"), - CommentCompilationWarning: () => require("../CommentCompilationWarning"), - ContextModule: () => require("../ContextModule"), - "cache/PackFileCacheStrategy": () => - require("../cache/PackFileCacheStrategy"), - "cache/ResolverCachePlugin": () => require("../cache/ResolverCachePlugin"), - "container/ContainerEntryDependency": () => - require("../container/ContainerEntryDependency"), - "container/ContainerEntryModule": () => - require("../container/ContainerEntryModule"), - "container/ContainerExposedDependency": () => - require("../container/ContainerExposedDependency"), - "container/FallbackDependency": () => - require("../container/FallbackDependency"), - "container/FallbackItemDependency": () => - require("../container/FallbackItemDependency"), - "container/FallbackModule": () => require("../container/FallbackModule"), - "container/RemoteModule": () => require("../container/RemoteModule"), - "container/RemoteToExternalDependency": () => - require("../container/RemoteToExternalDependency"), - "dependencies/AMDDefineDependency": () => - require("../dependencies/AMDDefineDependency"), - "dependencies/AMDRequireArrayDependency": () => - require("../dependencies/AMDRequireArrayDependency"), - "dependencies/AMDRequireContextDependency": () => - require("../dependencies/AMDRequireContextDependency"), - "dependencies/AMDRequireDependenciesBlock": () => - require("../dependencies/AMDRequireDependenciesBlock"), - "dependencies/AMDRequireDependency": () => - require("../dependencies/AMDRequireDependency"), - "dependencies/AMDRequireItemDependency": () => - require("../dependencies/AMDRequireItemDependency"), - "dependencies/CachedConstDependency": () => - require("../dependencies/CachedConstDependency"), - "dependencies/ExternalModuleDependency": () => - require("../dependencies/ExternalModuleDependency"), - "dependencies/ExternalModuleInitFragment": () => - require("../dependencies/ExternalModuleInitFragment"), - "dependencies/CreateScriptUrlDependency": () => - require("../dependencies/CreateScriptUrlDependency"), - "dependencies/CommonJsRequireContextDependency": () => - require("../dependencies/CommonJsRequireContextDependency"), - "dependencies/CommonJsExportRequireDependency": () => - require("../dependencies/CommonJsExportRequireDependency"), - "dependencies/CommonJsExportsDependency": () => - require("../dependencies/CommonJsExportsDependency"), - "dependencies/CommonJsFullRequireDependency": () => - require("../dependencies/CommonJsFullRequireDependency"), - "dependencies/CommonJsRequireDependency": () => - require("../dependencies/CommonJsRequireDependency"), - "dependencies/CommonJsSelfReferenceDependency": () => - require("../dependencies/CommonJsSelfReferenceDependency"), - "dependencies/ConstDependency": () => - require("../dependencies/ConstDependency"), - "dependencies/ContextDependency": () => - require("../dependencies/ContextDependency"), - "dependencies/ContextElementDependency": () => - require("../dependencies/ContextElementDependency"), - "dependencies/CriticalDependencyWarning": () => - require("../dependencies/CriticalDependencyWarning"), - "dependencies/CssImportDependency": () => - require("../dependencies/CssImportDependency"), - "dependencies/CssLocalIdentifierDependency": () => - require("../dependencies/CssLocalIdentifierDependency"), - "dependencies/CssSelfLocalIdentifierDependency": () => - require("../dependencies/CssSelfLocalIdentifierDependency"), - "dependencies/CssIcssImportDependency": () => - require("../dependencies/CssIcssImportDependency"), - "dependencies/CssIcssExportDependency": () => - require("../dependencies/CssIcssExportDependency"), - "dependencies/CssUrlDependency": () => - require("../dependencies/CssUrlDependency"), - "dependencies/CssIcssSymbolDependency": () => - require("../dependencies/CssIcssSymbolDependency"), - "dependencies/DelegatedSourceDependency": () => - require("../dependencies/DelegatedSourceDependency"), - "dependencies/DllEntryDependency": () => - require("../dependencies/DllEntryDependency"), - "dependencies/EntryDependency": () => - require("../dependencies/EntryDependency"), - "dependencies/ExportsInfoDependency": () => - require("../dependencies/ExportsInfoDependency"), - "dependencies/HarmonyAcceptDependency": () => - require("../dependencies/HarmonyAcceptDependency"), - "dependencies/HarmonyAcceptImportDependency": () => - require("../dependencies/HarmonyAcceptImportDependency"), - "dependencies/HarmonyCompatibilityDependency": () => - require("../dependencies/HarmonyCompatibilityDependency"), - "dependencies/HarmonyExportExpressionDependency": () => - require("../dependencies/HarmonyExportExpressionDependency"), - "dependencies/HarmonyExportHeaderDependency": () => - require("../dependencies/HarmonyExportHeaderDependency"), - "dependencies/HarmonyExportImportedSpecifierDependency": () => - require("../dependencies/HarmonyExportImportedSpecifierDependency"), - "dependencies/HarmonyExportSpecifierDependency": () => - require("../dependencies/HarmonyExportSpecifierDependency"), - "dependencies/HarmonyImportSideEffectDependency": () => - require("../dependencies/HarmonyImportSideEffectDependency"), - "dependencies/HarmonyImportSpecifierDependency": () => - require("../dependencies/HarmonyImportSpecifierDependency"), - "dependencies/HarmonyEvaluatedImportSpecifierDependency": () => - require("../dependencies/HarmonyEvaluatedImportSpecifierDependency"), - "dependencies/ImportContextDependency": () => - require("../dependencies/ImportContextDependency"), - "dependencies/ImportDependency": () => - require("../dependencies/ImportDependency"), - "dependencies/ImportEagerDependency": () => - require("../dependencies/ImportEagerDependency"), - "dependencies/ImportWeakDependency": () => - require("../dependencies/ImportWeakDependency"), - "dependencies/JsonExportsDependency": () => - require("../dependencies/JsonExportsDependency"), - "dependencies/LocalModule": () => require("../dependencies/LocalModule"), - "dependencies/LocalModuleDependency": () => - require("../dependencies/LocalModuleDependency"), - "dependencies/ModuleDecoratorDependency": () => - require("../dependencies/ModuleDecoratorDependency"), - "dependencies/ModuleHotAcceptDependency": () => - require("../dependencies/ModuleHotAcceptDependency"), - "dependencies/ModuleHotDeclineDependency": () => - require("../dependencies/ModuleHotDeclineDependency"), - "dependencies/ImportMetaHotAcceptDependency": () => - require("../dependencies/ImportMetaHotAcceptDependency"), - "dependencies/ImportMetaHotDeclineDependency": () => - require("../dependencies/ImportMetaHotDeclineDependency"), - "dependencies/ImportMetaContextDependency": () => - require("../dependencies/ImportMetaContextDependency"), - "dependencies/ProvidedDependency": () => - require("../dependencies/ProvidedDependency"), - "dependencies/PureExpressionDependency": () => - require("../dependencies/PureExpressionDependency"), - "dependencies/RequireContextDependency": () => - require("../dependencies/RequireContextDependency"), - "dependencies/RequireEnsureDependenciesBlock": () => - require("../dependencies/RequireEnsureDependenciesBlock"), - "dependencies/RequireEnsureDependency": () => - require("../dependencies/RequireEnsureDependency"), - "dependencies/RequireEnsureItemDependency": () => - require("../dependencies/RequireEnsureItemDependency"), - "dependencies/RequireHeaderDependency": () => - require("../dependencies/RequireHeaderDependency"), - "dependencies/RequireIncludeDependency": () => - require("../dependencies/RequireIncludeDependency"), - "dependencies/RequireIncludeDependencyParserPlugin": () => - require("../dependencies/RequireIncludeDependencyParserPlugin"), - "dependencies/RequireResolveContextDependency": () => - require("../dependencies/RequireResolveContextDependency"), - "dependencies/RequireResolveDependency": () => - require("../dependencies/RequireResolveDependency"), - "dependencies/RequireResolveHeaderDependency": () => - require("../dependencies/RequireResolveHeaderDependency"), - "dependencies/RuntimeRequirementsDependency": () => - require("../dependencies/RuntimeRequirementsDependency"), - "dependencies/StaticExportsDependency": () => - require("../dependencies/StaticExportsDependency"), - "dependencies/SystemPlugin": () => require("../dependencies/SystemPlugin"), - "dependencies/UnsupportedDependency": () => - require("../dependencies/UnsupportedDependency"), - "dependencies/URLDependency": () => require("../dependencies/URLDependency"), - "dependencies/WebAssemblyExportImportedDependency": () => - require("../dependencies/WebAssemblyExportImportedDependency"), - "dependencies/WebAssemblyImportDependency": () => - require("../dependencies/WebAssemblyImportDependency"), - "dependencies/WebpackIsIncludedDependency": () => - require("../dependencies/WebpackIsIncludedDependency"), - "dependencies/WorkerDependency": () => - require("../dependencies/WorkerDependency"), - "json/JsonData": () => require("../json/JsonData"), - "optimize/ConcatenatedModule": () => - require("../optimize/ConcatenatedModule"), - DelegatedModule: () => require("../DelegatedModule"), - DependenciesBlock: () => require("../DependenciesBlock"), - DllModule: () => require("../DllModule"), - ExternalModule: () => require("../ExternalModule"), - FileSystemInfo: () => require("../FileSystemInfo"), - InitFragment: () => require("../InitFragment"), - InvalidDependenciesModuleWarning: () => - require("../InvalidDependenciesModuleWarning"), - Module: () => require("../Module"), - ModuleBuildError: () => require("../ModuleBuildError"), - ModuleDependencyWarning: () => require("../ModuleDependencyWarning"), - ModuleError: () => require("../ModuleError"), - ModuleGraph: () => require("../ModuleGraph"), - ModuleParseError: () => require("../ModuleParseError"), - ModuleWarning: () => require("../ModuleWarning"), - NormalModule: () => require("../NormalModule"), - CssModule: () => require("../CssModule"), - RawDataUrlModule: () => require("../asset/RawDataUrlModule"), - RawModule: () => require("../RawModule"), - "sharing/ConsumeSharedModule": () => - require("../sharing/ConsumeSharedModule"), - "sharing/ConsumeSharedFallbackDependency": () => - require("../sharing/ConsumeSharedFallbackDependency"), - "sharing/ProvideSharedModule": () => - require("../sharing/ProvideSharedModule"), - "sharing/ProvideSharedDependency": () => - require("../sharing/ProvideSharedDependency"), - "sharing/ProvideForSharedDependency": () => - require("../sharing/ProvideForSharedDependency"), - UnsupportedFeatureWarning: () => require("../UnsupportedFeatureWarning"), - "util/LazySet": () => require("../util/LazySet"), - UnhandledSchemeError: () => require("../UnhandledSchemeError"), - NodeStuffInWebError: () => require("../NodeStuffInWebError"), - EnvironmentNotSupportAsyncWarning: () => - require("../EnvironmentNotSupportAsyncWarning"), - WebpackError: () => require("../WebpackError"), - - "util/registerExternalSerializer": () => { - // already registered - } -}; diff --git a/webpack-lib/lib/util/magicComment.js b/webpack-lib/lib/util/magicComment.js deleted file mode 100644 index c47cc5350f4..00000000000 --- a/webpack-lib/lib/util/magicComment.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -// regexp to match at least one "magic comment" -module.exports.webpackCommentRegExp = new RegExp( - /(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/ -); - -// regexp to match at least one "magic comment" -/** - * @returns {import("vm").Context} magic comment context - */ -module.exports.createMagicCommentContext = () => - require("vm").createContext(undefined, { - name: "Webpack Magic Comment Parser", - codeGeneration: { strings: false, wasm: false } - }); diff --git a/webpack-lib/lib/util/makeSerializable.js b/webpack-lib/lib/util/makeSerializable.js deleted file mode 100644 index 90b60fb3e16..00000000000 --- a/webpack-lib/lib/util/makeSerializable.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { register } = require("./serialization"); - -/** @typedef {import("../serialization/ObjectMiddleware").Constructor} Constructor */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */ - -/** @typedef {{ serialize: (context: ObjectSerializerContext) => void, deserialize: (context: ObjectDeserializerContext) => void }} SerializableClass */ -/** - * @template {SerializableClass} T - * @typedef {(new (...params: any[]) => T) & { deserialize?: (context: ObjectDeserializerContext) => T }} SerializableClassConstructor - */ - -/** - * @template {SerializableClass} T - */ -class ClassSerializer { - /** - * @param {SerializableClassConstructor} Constructor constructor - */ - constructor(Constructor) { - this.Constructor = Constructor; - } - - /** - * @param {T} obj obj - * @param {ObjectSerializerContext} context context - */ - serialize(obj, context) { - obj.serialize(context); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {T} obj - */ - deserialize(context) { - if (typeof this.Constructor.deserialize === "function") { - return this.Constructor.deserialize(context); - } - const obj = new this.Constructor(); - obj.deserialize(context); - return obj; - } -} - -/** - * @template {Constructor} T - * @param {T} Constructor the constructor - * @param {string} request the request which will be required when deserializing - * @param {string | null} [name] the name to make multiple serializer unique when sharing a request - */ -module.exports = (Constructor, request, name = null) => { - register(Constructor, request, name, new ClassSerializer(Constructor)); -}; diff --git a/webpack-lib/lib/util/memoize.js b/webpack-lib/lib/util/memoize.js deleted file mode 100644 index d3fc19634fe..00000000000 --- a/webpack-lib/lib/util/memoize.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** @template T @typedef {function(): T} FunctionReturning */ - -/** - * @template T - * @param {FunctionReturning} fn memorized function - * @returns {FunctionReturning} new function - */ -const memoize = fn => { - let cache = false; - /** @type {T | undefined} */ - let result; - return () => { - if (cache) { - return /** @type {T} */ (result); - } - - result = fn(); - cache = true; - // Allow to clean up memory for fn - // and all dependent resources - /** @type {FunctionReturning | undefined} */ - (fn) = undefined; - return /** @type {T} */ (result); - }; -}; - -module.exports = memoize; diff --git a/webpack-lib/lib/util/nonNumericOnlyHash.js b/webpack-lib/lib/util/nonNumericOnlyHash.js deleted file mode 100644 index ec8ca745ffc..00000000000 --- a/webpack-lib/lib/util/nonNumericOnlyHash.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Ivan Kopeykin @vankop -*/ - -"use strict"; - -const A_CODE = "a".charCodeAt(0); - -/** - * @param {string} hash hash - * @param {number} hashLength hash length - * @returns {string} returns hash that has at least one non numeric char - */ -module.exports = (hash, hashLength) => { - if (hashLength < 1) return ""; - const slice = hash.slice(0, hashLength); - if (/[^\d]/.test(slice)) return slice; - return `${String.fromCharCode( - A_CODE + (Number.parseInt(hash[0], 10) % 6) - )}${slice.slice(1)}`; -}; diff --git a/webpack-lib/lib/util/numberHash.js b/webpack-lib/lib/util/numberHash.js deleted file mode 100644 index 950d14bf8bb..00000000000 --- a/webpack-lib/lib/util/numberHash.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * Threshold for switching from 32-bit to 64-bit hashing. This is selected to ensure that the bias towards lower modulo results when using 32-bit hashing is <0.5%. - * @type {number} - */ -const FNV_64_THRESHOLD = 1 << 24; - -/** - * The FNV-1a offset basis for 32-bit hash values. - * @type {number} - */ -const FNV_OFFSET_32 = 2166136261; -/** - * The FNV-1a prime for 32-bit hash values. - * @type {number} - */ -const FNV_PRIME_32 = 16777619; -/** - * The mask for a positive 32-bit signed integer. - * @type {number} - */ -const MASK_31 = 0x7fffffff; - -/** - * The FNV-1a offset basis for 64-bit hash values. - * @type {bigint} - */ -const FNV_OFFSET_64 = BigInt("0xCBF29CE484222325"); -/** - * The FNV-1a prime for 64-bit hash values. - * @type {bigint} - */ -const FNV_PRIME_64 = BigInt("0x100000001B3"); - -/** - * Computes a 32-bit FNV-1a hash value for the given string. - * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - * @param {string} str The input string to hash - * @returns {number} - The computed hash value. - */ -function fnv1a32(str) { - let hash = FNV_OFFSET_32; - for (let i = 0, len = str.length; i < len; i++) { - hash ^= str.charCodeAt(i); - // Use Math.imul to do c-style 32-bit multiplication and keep only the 32 least significant bits - hash = Math.imul(hash, FNV_PRIME_32); - } - // Force the result to be positive - return hash & MASK_31; -} - -/** - * Computes a 64-bit FNV-1a hash value for the given string. - * See https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function - * @param {string} str The input string to hash - * @returns {bigint} - The computed hash value. - */ -function fnv1a64(str) { - let hash = FNV_OFFSET_64; - for (let i = 0, len = str.length; i < len; i++) { - hash ^= BigInt(str.charCodeAt(i)); - hash = BigInt.asUintN(64, hash * FNV_PRIME_64); - } - return hash; -} - -/** - * Computes a hash value for the given string and range. This hashing algorithm is a modified - * version of the [FNV-1a algorithm](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function). - * It is optimized for speed and does **not** generate a cryptographic hash value. - * - * We use `numberHash` in `lib/ids/IdHelpers.js` to generate hash values for the module identifier. The generated - * hash is used as a prefix for the module id's to avoid collisions with other modules. - * @param {string} str The input string to hash. - * @param {number} range The range of the hash value (0 to range-1). - * @returns {number} - The computed hash value. - * @example - * ```js - * const numberHash = require("webpack/lib/util/numberHash"); - * numberHash("hello", 1000); // 73 - * numberHash("hello world"); // 72 - * ``` - */ -module.exports = (str, range) => { - if (range < FNV_64_THRESHOLD) { - return fnv1a32(str) % range; - } - return Number(fnv1a64(str) % BigInt(range)); -}; diff --git a/webpack-lib/lib/util/objectToMap.js b/webpack-lib/lib/util/objectToMap.js deleted file mode 100644 index 19ce8e08f77..00000000000 --- a/webpack-lib/lib/util/objectToMap.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -/** - * Convert an object into an ES6 map - * @param {object} obj any object type that works with Object.entries() - * @returns {Map} an ES6 Map of KV pairs - */ -module.exports = function objectToMap(obj) { - return new Map(Object.entries(obj)); -}; diff --git a/webpack-lib/lib/util/processAsyncTree.js b/webpack-lib/lib/util/processAsyncTree.js deleted file mode 100644 index 38253865231..00000000000 --- a/webpack-lib/lib/util/processAsyncTree.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @template T - * @template {Error} E - * @param {Iterable} items initial items - * @param {number} concurrency number of items running in parallel - * @param {function(T, function(T): void, function(E=): void): void} processor worker which pushes more items - * @param {function(E=): void} callback all items processed - * @returns {void} - */ -const processAsyncTree = (items, concurrency, processor, callback) => { - const queue = Array.from(items); - if (queue.length === 0) return callback(); - let processing = 0; - let finished = false; - let processScheduled = true; - - /** - * @param {T} item item - */ - const push = item => { - queue.push(item); - if (!processScheduled && processing < concurrency) { - processScheduled = true; - process.nextTick(processQueue); - } - }; - - /** - * @param {E | null | undefined} err error - */ - const processorCallback = err => { - processing--; - if (err && !finished) { - finished = true; - callback(err); - return; - } - if (!processScheduled) { - processScheduled = true; - process.nextTick(processQueue); - } - }; - - const processQueue = () => { - if (finished) return; - while (processing < concurrency && queue.length > 0) { - processing++; - const item = /** @type {T} */ (queue.pop()); - processor(item, push, processorCallback); - } - processScheduled = false; - if (queue.length === 0 && processing === 0 && !finished) { - finished = true; - callback(); - } - }; - - processQueue(); -}; - -module.exports = processAsyncTree; diff --git a/webpack-lib/lib/util/propertyAccess.js b/webpack-lib/lib/util/propertyAccess.js deleted file mode 100644 index 0cf647bd9e0..00000000000 --- a/webpack-lib/lib/util/propertyAccess.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SAFE_IDENTIFIER, RESERVED_IDENTIFIER } = require("./propertyName"); - -/** - * @param {ArrayLike} properties properties - * @param {number} start start index - * @returns {string} chain of property accesses - */ -const propertyAccess = (properties, start = 0) => { - let str = ""; - for (let i = start; i < properties.length; i++) { - const p = properties[i]; - if (`${Number(p)}` === p) { - str += `[${p}]`; - } else if (SAFE_IDENTIFIER.test(p) && !RESERVED_IDENTIFIER.has(p)) { - str += `.${p}`; - } else { - str += `[${JSON.stringify(p)}]`; - } - } - return str; -}; - -module.exports = propertyAccess; diff --git a/webpack-lib/lib/util/propertyName.js b/webpack-lib/lib/util/propertyName.js deleted file mode 100644 index 4ee9e3f5485..00000000000 --- a/webpack-lib/lib/util/propertyName.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const SAFE_IDENTIFIER = /^[_a-zA-Z$][_a-zA-Z$0-9]*$/; -const RESERVED_IDENTIFIER = new Set([ - "break", - "case", - "catch", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "else", - "export", - "extends", - "finally", - "for", - "function", - "if", - "import", - "in", - "instanceof", - "new", - "return", - "super", - "switch", - "this", - "throw", - "try", - "typeof", - "var", - "void", - "while", - "with", - "enum", - // strict mode - "implements", - "interface", - "let", - "package", - "private", - "protected", - "public", - "static", - "yield", - "yield", - // module code - "await", - // skip future reserved keywords defined under ES1 till ES3 - // additional - "null", - "true", - "false" -]); - -/** - * @summary Returns a valid JS property name for the given property. - * Certain strings like "default", "null", and names with whitespace are not - * valid JS property names, so they are returned as strings. - * @param {string} prop property name to analyze - * @returns {string} valid JS property name - */ -const propertyName = prop => { - if (SAFE_IDENTIFIER.test(prop) && !RESERVED_IDENTIFIER.has(prop)) { - return prop; - } - return JSON.stringify(prop); -}; - -module.exports = { SAFE_IDENTIFIER, RESERVED_IDENTIFIER, propertyName }; diff --git a/webpack-lib/lib/util/registerExternalSerializer.js b/webpack-lib/lib/util/registerExternalSerializer.js deleted file mode 100644 index 711bcfa210a..00000000000 --- a/webpack-lib/lib/util/registerExternalSerializer.js +++ /dev/null @@ -1,337 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { register } = require("./serialization"); - -const Position = /** @type {TODO} */ (require("acorn")).Position; -const SourceLocation = require("acorn").SourceLocation; -const ValidationError = require("schema-utils").ValidationError; -const { - CachedSource, - ConcatSource, - OriginalSource, - PrefixSource, - RawSource, - ReplaceSource, - SourceMapSource -} = require("webpack-sources"); - -/** @typedef {import("acorn").Position} Position */ -/** @typedef {import("../Dependency").RealDependencyLocation} RealDependencyLocation */ -/** @typedef {import("../Dependency").SourcePosition} SourcePosition */ -/** @typedef {import("./serialization").ObjectDeserializerContext} ObjectDeserializerContext */ -/** @typedef {import("./serialization").ObjectSerializerContext} ObjectSerializerContext */ - -/** @typedef {ObjectSerializerContext & { writeLazy?: (value: any) => void }} WebpackObjectSerializerContext */ - -const CURRENT_MODULE = "webpack/lib/util/registerExternalSerializer"; - -register( - CachedSource, - CURRENT_MODULE, - "webpack-sources/CachedSource", - new (class CachedSourceSerializer { - /** - * @param {CachedSource} source the cached source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write, writeLazy }) { - if (writeLazy) { - writeLazy(source.originalLazy()); - } else { - write(source.original()); - } - write(source.getCachedData()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {CachedSource} cached source - */ - deserialize({ read }) { - const source = read(); - const cachedData = read(); - return new CachedSource(source, cachedData); - } - })() -); - -register( - RawSource, - CURRENT_MODULE, - "webpack-sources/RawSource", - new (class RawSourceSerializer { - /** - * @param {RawSource} source the raw source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.buffer()); - write(!source.isBuffer()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RawSource} raw source - */ - deserialize({ read }) { - const source = read(); - const convertToString = read(); - return new RawSource(source, convertToString); - } - })() -); - -register( - ConcatSource, - CURRENT_MODULE, - "webpack-sources/ConcatSource", - new (class ConcatSourceSerializer { - /** - * @param {ConcatSource} source the concat source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.getChildren()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ConcatSource} concat source - */ - deserialize({ read }) { - const source = new ConcatSource(); - source.addAllSkipOptimizing(read()); - return source; - } - })() -); - -register( - PrefixSource, - CURRENT_MODULE, - "webpack-sources/PrefixSource", - new (class PrefixSourceSerializer { - /** - * @param {PrefixSource} source the prefix source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.getPrefix()); - write(source.original()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {PrefixSource} prefix source - */ - deserialize({ read }) { - return new PrefixSource(read(), read()); - } - })() -); - -register( - ReplaceSource, - CURRENT_MODULE, - "webpack-sources/ReplaceSource", - new (class ReplaceSourceSerializer { - /** - * @param {ReplaceSource} source the replace source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.original()); - write(source.getName()); - const replacements = source.getReplacements(); - write(replacements.length); - for (const repl of replacements) { - write(repl.start); - write(repl.end); - } - for (const repl of replacements) { - write(repl.content); - write(repl.name); - } - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {ReplaceSource} replace source - */ - deserialize({ read }) { - const source = new ReplaceSource(read(), read()); - const len = read(); - const startEndBuffer = []; - for (let i = 0; i < len; i++) { - startEndBuffer.push(read(), read()); - } - let j = 0; - for (let i = 0; i < len; i++) { - source.replace( - startEndBuffer[j++], - startEndBuffer[j++], - read(), - read() - ); - } - return source; - } - })() -); - -register( - OriginalSource, - CURRENT_MODULE, - "webpack-sources/OriginalSource", - new (class OriginalSourceSerializer { - /** - * @param {OriginalSource} source the original source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.buffer()); - write(source.getName()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {OriginalSource} original source - */ - deserialize({ read }) { - const buffer = read(); - const name = read(); - return new OriginalSource(buffer, name); - } - })() -); - -register( - SourceLocation, - CURRENT_MODULE, - "acorn/SourceLocation", - new (class SourceLocationSerializer { - /** - * @param {SourceLocation} loc the location to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(loc, { write }) { - write(loc.start.line); - write(loc.start.column); - write(loc.end.line); - write(loc.end.column); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {RealDependencyLocation} location - */ - deserialize({ read }) { - return { - start: { - line: read(), - column: read() - }, - end: { - line: read(), - column: read() - } - }; - } - })() -); - -register( - Position, - CURRENT_MODULE, - "acorn/Position", - new (class PositionSerializer { - /** - * @param {Position} pos the position to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(pos, { write }) { - write(pos.line); - write(pos.column); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {SourcePosition} position - */ - deserialize({ read }) { - return { - line: read(), - column: read() - }; - } - })() -); - -register( - SourceMapSource, - CURRENT_MODULE, - "webpack-sources/SourceMapSource", - new (class SourceMapSourceSerializer { - /** - * @param {SourceMapSource} source the source map source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(source, { write }) { - write(source.getArgsAsBuffers()); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {SourceMapSource} source source map source - */ - deserialize({ read }) { - // @ts-expect-error - return new SourceMapSource(...read()); - } - })() -); - -register( - ValidationError, - CURRENT_MODULE, - "schema-utils/ValidationError", - new (class ValidationErrorSerializer { - // TODO error should be ValidationError, but this fails the type checks - /** - * @param {TODO} error the source map source to be serialized - * @param {WebpackObjectSerializerContext} context context - * @returns {void} - */ - serialize(error, { write }) { - write(error.errors); - write(error.schema); - write({ - name: error.headerName, - baseDataPath: error.baseDataPath, - postFormatter: error.postFormatter - }); - } - - /** - * @param {ObjectDeserializerContext} context context - * @returns {TODO} error - */ - deserialize({ read }) { - return new ValidationError(read(), read(), read()); - } - })() -); diff --git a/webpack-lib/lib/util/runtime.js b/webpack-lib/lib/util/runtime.js deleted file mode 100644 index 021f799d4e9..00000000000 --- a/webpack-lib/lib/util/runtime.js +++ /dev/null @@ -1,687 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const SortableSet = require("./SortableSet"); - -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ - -/** @typedef {string | SortableSet | undefined} RuntimeSpec */ -/** @typedef {RuntimeSpec | boolean} RuntimeCondition */ - -/** - * @param {Compilation} compilation the compilation - * @param {string} name name of the entry - * @param {EntryOptions=} options optionally already received entry options - * @returns {RuntimeSpec} runtime - */ -module.exports.getEntryRuntime = (compilation, name, options) => { - let dependOn; - let runtime; - if (options) { - ({ dependOn, runtime } = options); - } else { - const entry = compilation.entries.get(name); - if (!entry) return name; - ({ dependOn, runtime } = entry.options); - } - if (dependOn) { - /** @type {RuntimeSpec} */ - let result; - const queue = new Set(dependOn); - for (const name of queue) { - const dep = compilation.entries.get(name); - if (!dep) continue; - const { dependOn, runtime } = dep.options; - if (dependOn) { - for (const name of dependOn) { - queue.add(name); - } - } else { - result = mergeRuntimeOwned(result, runtime || name); - } - } - return result || name; - } - return runtime || name; -}; - -/** - * @param {RuntimeSpec} runtime runtime - * @param {function(string | undefined): void} fn functor - * @param {boolean} deterministicOrder enforce a deterministic order - * @returns {void} - */ -const forEachRuntime = (runtime, fn, deterministicOrder = false) => { - if (runtime === undefined) { - fn(undefined); - } else if (typeof runtime === "string") { - fn(runtime); - } else { - if (deterministicOrder) runtime.sort(); - for (const r of runtime) { - fn(r); - } - } -}; -module.exports.forEachRuntime = forEachRuntime; - -/** - * @template T - * @param {SortableSet} set set - * @returns {string} runtime key - */ -const getRuntimesKey = set => { - set.sort(); - return Array.from(set).join("\n"); -}; - -/** - * @param {RuntimeSpec} runtime runtime(s) - * @returns {string} key of runtimes - */ -const getRuntimeKey = runtime => { - if (runtime === undefined) return "*"; - if (typeof runtime === "string") return runtime; - return runtime.getFromUnorderedCache(getRuntimesKey); -}; -module.exports.getRuntimeKey = getRuntimeKey; - -/** - * @param {string} key key of runtimes - * @returns {RuntimeSpec} runtime(s) - */ -const keyToRuntime = key => { - if (key === "*") return; - const items = key.split("\n"); - if (items.length === 1) return items[0]; - return new SortableSet(items); -}; -module.exports.keyToRuntime = keyToRuntime; - -/** - * @template T - * @param {SortableSet} set set - * @returns {string} runtime string - */ -const getRuntimesString = set => { - set.sort(); - return Array.from(set).join("+"); -}; - -/** - * @param {RuntimeSpec} runtime runtime(s) - * @returns {string} readable version - */ -const runtimeToString = runtime => { - if (runtime === undefined) return "*"; - if (typeof runtime === "string") return runtime; - return runtime.getFromUnorderedCache(getRuntimesString); -}; -module.exports.runtimeToString = runtimeToString; - -/** - * @param {RuntimeCondition} runtimeCondition runtime condition - * @returns {string} readable version - */ -module.exports.runtimeConditionToString = runtimeCondition => { - if (runtimeCondition === true) return "true"; - if (runtimeCondition === false) return "false"; - return runtimeToString(runtimeCondition); -}; - -/** - * @param {RuntimeSpec} a first - * @param {RuntimeSpec} b second - * @returns {boolean} true, when they are equal - */ -const runtimeEqual = (a, b) => { - if (a === b) { - return true; - } else if ( - a === undefined || - b === undefined || - typeof a === "string" || - typeof b === "string" - ) { - return false; - } else if (a.size !== b.size) { - return false; - } - a.sort(); - b.sort(); - const aIt = a[Symbol.iterator](); - const bIt = b[Symbol.iterator](); - for (;;) { - const aV = aIt.next(); - if (aV.done) return true; - const bV = bIt.next(); - if (aV.value !== bV.value) return false; - } -}; -module.exports.runtimeEqual = runtimeEqual; - -/** - * @param {RuntimeSpec} a first - * @param {RuntimeSpec} b second - * @returns {-1|0|1} compare - */ -module.exports.compareRuntime = (a, b) => { - if (a === b) { - return 0; - } else if (a === undefined) { - return -1; - } else if (b === undefined) { - return 1; - } - const aKey = getRuntimeKey(a); - const bKey = getRuntimeKey(b); - if (aKey < bKey) return -1; - if (aKey > bKey) return 1; - return 0; -}; - -/** - * @param {RuntimeSpec} a first - * @param {RuntimeSpec} b second - * @returns {RuntimeSpec} merged - */ -const mergeRuntime = (a, b) => { - if (a === undefined) { - return b; - } else if (b === undefined) { - return a; - } else if (a === b) { - return a; - } else if (typeof a === "string") { - if (typeof b === "string") { - const set = new SortableSet(); - set.add(a); - set.add(b); - return set; - } else if (b.has(a)) { - return b; - } - const set = new SortableSet(b); - set.add(a); - return set; - } - if (typeof b === "string") { - if (a.has(b)) return a; - const set = new SortableSet(a); - set.add(b); - return set; - } - const set = new SortableSet(a); - for (const item of b) set.add(item); - if (set.size === a.size) return a; - return set; -}; -module.exports.mergeRuntime = mergeRuntime; - -/** - * @param {RuntimeCondition} a first - * @param {RuntimeCondition} b second - * @param {RuntimeSpec} runtime full runtime - * @returns {RuntimeCondition} result - */ -module.exports.mergeRuntimeCondition = (a, b, runtime) => { - if (a === false) return b; - if (b === false) return a; - if (a === true || b === true) return true; - const merged = mergeRuntime(a, b); - if (merged === undefined) return; - if (typeof merged === "string") { - if (typeof runtime === "string" && merged === runtime) return true; - return merged; - } - if (typeof runtime === "string" || runtime === undefined) return merged; - if (merged.size === runtime.size) return true; - return merged; -}; - -/** - * @param {RuntimeSpec | true} a first - * @param {RuntimeSpec | true} b second - * @param {RuntimeSpec} runtime full runtime - * @returns {RuntimeSpec | true} result - */ -module.exports.mergeRuntimeConditionNonFalse = (a, b, runtime) => { - if (a === true || b === true) return true; - const merged = mergeRuntime(a, b); - if (merged === undefined) return; - if (typeof merged === "string") { - if (typeof runtime === "string" && merged === runtime) return true; - return merged; - } - if (typeof runtime === "string" || runtime === undefined) return merged; - if (merged.size === runtime.size) return true; - return merged; -}; - -/** - * @param {RuntimeSpec} a first (may be modified) - * @param {RuntimeSpec} b second - * @returns {RuntimeSpec} merged - */ -const mergeRuntimeOwned = (a, b) => { - if (b === undefined) { - return a; - } else if (a === b) { - return a; - } else if (a === undefined) { - if (typeof b === "string") { - return b; - } - return new SortableSet(b); - } else if (typeof a === "string") { - if (typeof b === "string") { - const set = new SortableSet(); - set.add(a); - set.add(b); - return set; - } - const set = new SortableSet(b); - set.add(a); - return set; - } - if (typeof b === "string") { - a.add(b); - return a; - } - for (const item of b) a.add(item); - return a; -}; -module.exports.mergeRuntimeOwned = mergeRuntimeOwned; - -/** - * @param {RuntimeSpec} a first - * @param {RuntimeSpec} b second - * @returns {RuntimeSpec} merged - */ -module.exports.intersectRuntime = (a, b) => { - if (a === undefined) { - return b; - } else if (b === undefined) { - return a; - } else if (a === b) { - return a; - } else if (typeof a === "string") { - if (typeof b === "string") { - return; - } else if (b.has(a)) { - return a; - } - return; - } - if (typeof b === "string") { - if (a.has(b)) return b; - return; - } - const set = new SortableSet(); - for (const item of b) { - if (a.has(item)) set.add(item); - } - if (set.size === 0) return; - if (set.size === 1) { - const [item] = set; - return item; - } - return set; -}; - -/** - * @param {RuntimeSpec} a first - * @param {RuntimeSpec} b second - * @returns {RuntimeSpec} result - */ -const subtractRuntime = (a, b) => { - if (a === undefined) { - return; - } else if (b === undefined) { - return a; - } else if (a === b) { - return; - } else if (typeof a === "string") { - if (typeof b === "string") { - return a; - } else if (b.has(a)) { - return; - } - return a; - } - if (typeof b === "string") { - if (!a.has(b)) return a; - if (a.size === 2) { - for (const item of a) { - if (item !== b) return item; - } - } - const set = new SortableSet(a); - set.delete(b); - return set; - } - const set = new SortableSet(); - for (const item of a) { - if (!b.has(item)) set.add(item); - } - if (set.size === 0) return; - if (set.size === 1) { - const [item] = set; - return item; - } - return set; -}; -module.exports.subtractRuntime = subtractRuntime; - -/** - * @param {RuntimeCondition} a first - * @param {RuntimeCondition} b second - * @param {RuntimeSpec} runtime runtime - * @returns {RuntimeCondition} result - */ -module.exports.subtractRuntimeCondition = (a, b, runtime) => { - if (b === true) return false; - if (b === false) return a; - if (a === false) return false; - const result = subtractRuntime(a === true ? runtime : a, b); - return result === undefined ? false : result; -}; - -/** - * @param {RuntimeSpec} runtime runtime - * @param {function(RuntimeSpec=): boolean} filter filter function - * @returns {boolean | RuntimeSpec} true/false if filter is constant for all runtimes, otherwise runtimes that are active - */ -module.exports.filterRuntime = (runtime, filter) => { - if (runtime === undefined) return filter(); - if (typeof runtime === "string") return filter(runtime); - let some = false; - let every = true; - let result; - for (const r of runtime) { - const v = filter(r); - if (v) { - some = true; - result = mergeRuntimeOwned(result, r); - } else { - every = false; - } - } - if (!some) return false; - if (every) return true; - return result; -}; - -/** - * @template T - * @typedef {Map} RuntimeSpecMapInnerMap - */ - -/** - * @template T - */ -class RuntimeSpecMap { - /** - * @param {RuntimeSpecMap=} clone copy form this - */ - constructor(clone) { - this._mode = clone ? clone._mode : 0; // 0 = empty, 1 = single entry, 2 = map - /** @type {RuntimeSpec} */ - this._singleRuntime = clone ? clone._singleRuntime : undefined; - /** @type {T | undefined} */ - this._singleValue = clone ? clone._singleValue : undefined; - /** @type {RuntimeSpecMapInnerMap | undefined} */ - this._map = clone && clone._map ? new Map(clone._map) : undefined; - } - - /** - * @param {RuntimeSpec} runtime the runtimes - * @returns {T | undefined} value - */ - get(runtime) { - switch (this._mode) { - case 0: - return; - case 1: - return runtimeEqual(this._singleRuntime, runtime) - ? this._singleValue - : undefined; - default: - return /** @type {RuntimeSpecMapInnerMap} */ (this._map).get( - getRuntimeKey(runtime) - ); - } - } - - /** - * @param {RuntimeSpec} runtime the runtimes - * @returns {boolean} true, when the runtime is stored - */ - has(runtime) { - switch (this._mode) { - case 0: - return false; - case 1: - return runtimeEqual(this._singleRuntime, runtime); - default: - return /** @type {RuntimeSpecMapInnerMap} */ (this._map).has( - getRuntimeKey(runtime) - ); - } - } - - /** - * @param {RuntimeSpec} runtime the runtimes - * @param {T} value the value - */ - set(runtime, value) { - switch (this._mode) { - case 0: - this._mode = 1; - this._singleRuntime = runtime; - this._singleValue = value; - break; - case 1: - if (runtimeEqual(this._singleRuntime, runtime)) { - this._singleValue = value; - break; - } - this._mode = 2; - this._map = new Map(); - this._map.set( - getRuntimeKey(this._singleRuntime), - /** @type {T} */ (this._singleValue) - ); - this._singleRuntime = undefined; - this._singleValue = undefined; - /* falls through */ - default: - /** @type {RuntimeSpecMapInnerMap} */ - (this._map).set(getRuntimeKey(runtime), value); - } - } - - /** - * @param {RuntimeSpec} runtime the runtimes - * @param {() => TODO} computer function to compute the value - * @returns {TODO} true, when the runtime was deleted - */ - provide(runtime, computer) { - switch (this._mode) { - case 0: - this._mode = 1; - this._singleRuntime = runtime; - return (this._singleValue = computer()); - case 1: { - if (runtimeEqual(this._singleRuntime, runtime)) { - return /** @type {T} */ (this._singleValue); - } - this._mode = 2; - this._map = new Map(); - this._map.set( - getRuntimeKey(this._singleRuntime), - /** @type {T} */ (this._singleValue) - ); - this._singleRuntime = undefined; - this._singleValue = undefined; - const newValue = computer(); - this._map.set(getRuntimeKey(runtime), newValue); - return newValue; - } - default: { - const key = getRuntimeKey(runtime); - const value = /** @type {Map} */ (this._map).get(key); - if (value !== undefined) return value; - const newValue = computer(); - /** @type {Map} */ - (this._map).set(key, newValue); - return newValue; - } - } - } - - /** - * @param {RuntimeSpec} runtime the runtimes - */ - delete(runtime) { - switch (this._mode) { - case 0: - return; - case 1: - if (runtimeEqual(this._singleRuntime, runtime)) { - this._mode = 0; - this._singleRuntime = undefined; - this._singleValue = undefined; - } - return; - default: - /** @type {RuntimeSpecMapInnerMap} */ - (this._map).delete(getRuntimeKey(runtime)); - } - } - - /** - * @param {RuntimeSpec} runtime the runtimes - * @param {function(T | undefined): T} fn function to update the value - */ - update(runtime, fn) { - switch (this._mode) { - case 0: - throw new Error("runtime passed to update must exist"); - case 1: { - if (runtimeEqual(this._singleRuntime, runtime)) { - this._singleValue = fn(this._singleValue); - break; - } - const newValue = fn(undefined); - if (newValue !== undefined) { - this._mode = 2; - this._map = new Map(); - this._map.set( - getRuntimeKey(this._singleRuntime), - /** @type {T} */ (this._singleValue) - ); - this._singleRuntime = undefined; - this._singleValue = undefined; - this._map.set(getRuntimeKey(runtime), newValue); - } - break; - } - default: { - const key = getRuntimeKey(runtime); - const oldValue = /** @type {Map} */ (this._map).get(key); - const newValue = fn(oldValue); - if (newValue !== oldValue) - /** @type {RuntimeSpecMapInnerMap} */ - (this._map).set(key, newValue); - } - } - } - - keys() { - switch (this._mode) { - case 0: - return []; - case 1: - return [this._singleRuntime]; - default: - return Array.from( - /** @type {RuntimeSpecMapInnerMap} */ - (this._map).keys(), - keyToRuntime - ); - } - } - - /** - * @returns {IterableIterator} values - */ - values() { - switch (this._mode) { - case 0: - return [][Symbol.iterator](); - case 1: - return [/** @type {T} */ (this._singleValue)][Symbol.iterator](); - default: - return /** @type {Map} */ (this._map).values(); - } - } - - get size() { - if (/** @type {number} */ (this._mode) <= 1) { - return /** @type {number} */ (this._mode); - } - - return /** @type {Map} */ (this._map).size; - } -} - -module.exports.RuntimeSpecMap = RuntimeSpecMap; - -class RuntimeSpecSet { - /** - * @param {Iterable=} iterable iterable - */ - constructor(iterable) { - /** @type {Map} */ - this._map = new Map(); - if (iterable) { - for (const item of iterable) { - this.add(item); - } - } - } - - /** - * @param {RuntimeSpec} runtime runtime - */ - add(runtime) { - this._map.set(getRuntimeKey(runtime), runtime); - } - - /** - * @param {RuntimeSpec} runtime runtime - * @returns {boolean} true, when the runtime exists - */ - has(runtime) { - return this._map.has(getRuntimeKey(runtime)); - } - - /** - * @returns {IterableIterator} iterable iterator - */ - [Symbol.iterator]() { - return this._map.values(); - } - - get size() { - return this._map.size; - } -} - -module.exports.RuntimeSpecSet = RuntimeSpecSet; diff --git a/webpack-lib/lib/util/semver.js b/webpack-lib/lib/util/semver.js deleted file mode 100644 index 86628eadd40..00000000000 --- a/webpack-lib/lib/util/semver.js +++ /dev/null @@ -1,602 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {string | number | undefined} SemVerRangeItem */ -/** @typedef {(SemVerRangeItem | SemVerRangeItem[])[]} SemVerRange */ - -/** - * @param {string} str version string - * @returns {SemVerRange} parsed version - */ -const parseVersion = str => { - /** - * @param {str} str str - * @returns {(string | number)[]} result - */ - var splitAndConvert = function (str) { - return str.split(".").map(function (item) { - // eslint-disable-next-line eqeqeq - return +item == /** @type {EXPECTED_ANY} */ (item) ? +item : item; - }); - }; - - var match = - /** @type {RegExpExecArray} */ - (/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str)); - - /** @type {(string | number | undefined | [])[]} */ - var ver = match[1] ? splitAndConvert(match[1]) : []; - - if (match[2]) { - ver.length++; - ver.push.apply(ver, splitAndConvert(match[2])); - } - - if (match[3]) { - ver.push([]); - ver.push.apply(ver, splitAndConvert(match[3])); - } - - return ver; -}; -module.exports.parseVersion = parseVersion; - -/* eslint-disable eqeqeq */ -/** - * @param {string} a version - * @param {string} b version - * @returns {boolean} true, iff a < b - */ -const versionLt = (a, b) => { - // @ts-expect-error - a = parseVersion(a); - // @ts-expect-error - b = parseVersion(b); - var i = 0; - for (;;) { - // a b EOA object undefined number string - // EOA a == b a < b b < a a < b a < b - // object b < a (0) b < a a < b a < b - // undefined a < b a < b (0) a < b a < b - // number b < a b < a b < a (1) a < b - // string b < a b < a b < a b < a (1) - // EOA end of array - // (0) continue on - // (1) compare them via "<" - - // Handles first row in table - if (i >= a.length) return i < b.length && (typeof b[i])[0] != "u"; - - var aValue = a[i]; - var aType = (typeof aValue)[0]; - - // Handles first column in table - if (i >= b.length) return aType == "u"; - - var bValue = b[i]; - var bType = (typeof bValue)[0]; - - if (aType == bType) { - if (aType != "o" && aType != "u" && aValue != bValue) { - return aValue < bValue; - } - i++; - } else { - // Handles remaining cases - if (aType == "o" && bType == "n") return true; - return bType == "s" || aType == "u"; - } - } -}; -/* eslint-enable eqeqeq */ -module.exports.versionLt = versionLt; - -/** - * @param {string} str range string - * @returns {SemVerRange} parsed range - */ -module.exports.parseRange = str => { - /** - * @param {string} str str - * @returns {(string | number)[]} result - */ - const splitAndConvert = str => { - return str - .split(".") - .map(item => (item !== "NaN" && `${+item}` === item ? +item : item)); - }; - - // see https://docs.npmjs.com/misc/semver#range-grammar for grammar - /** - * @param {string} str str - * @returns {SemVerRangeItem[]} - */ - const parsePartial = str => { - const match = - /** @type {RegExpExecArray} */ - (/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(str)); - /** @type {SemVerRangeItem[]} */ - const ver = match[1] ? [0, ...splitAndConvert(match[1])] : [0]; - - if (match[2]) { - ver.length++; - ver.push.apply(ver, splitAndConvert(match[2])); - } - - // remove trailing any matchers - let last = ver[ver.length - 1]; - while ( - ver.length && - (last === undefined || /^[*xX]$/.test(/** @type {string} */ (last))) - ) { - ver.pop(); - last = ver[ver.length - 1]; - } - - return ver; - }; - - /** - * - * @param {SemVerRangeItem[]} range range - * @returns {SemVerRangeItem[]} - */ - const toFixed = range => { - if (range.length === 1) { - // Special case for "*" is "x.x.x" instead of "=" - return [0]; - } else if (range.length === 2) { - // Special case for "1" is "1.x.x" instead of "=1" - return [1, ...range.slice(1)]; - } else if (range.length === 3) { - // Special case for "1.2" is "1.2.x" instead of "=1.2" - return [2, ...range.slice(1)]; - } - - return [range.length, ...range.slice(1)]; - }; - - /** - * - * @param {SemVerRangeItem[]} range - * @returns {SemVerRangeItem[]} result - */ - const negate = range => { - return [-(/** @type { [number]} */ (range)[0]) - 1, ...range.slice(1)]; - }; - - /** - * @param {string} str str - * @returns {SemVerRange} - */ - const parseSimple = str => { - // simple ::= primitive | partial | tilde | caret - // primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | '!' ) ( ' ' ) * partial - // tilde ::= '~' ( ' ' ) * partial - // caret ::= '^' ( ' ' ) * partial - const match = /^(\^|~|<=|<|>=|>|=|v|!)/.exec(str); - const start = match ? match[0] : ""; - const remainder = parsePartial( - start.length ? str.slice(start.length).trim() : str.trim() - ); - - switch (start) { - case "^": - if (remainder.length > 1 && remainder[1] === 0) { - if (remainder.length > 2 && remainder[2] === 0) { - return [3, ...remainder.slice(1)]; - } - return [2, ...remainder.slice(1)]; - } - return [1, ...remainder.slice(1)]; - case "~": - if (remainder.length === 2 && remainder[0] === 0) { - return [1, ...remainder.slice(1)]; - } - return [2, ...remainder.slice(1)]; - case ">=": - return remainder; - case "=": - case "v": - case "": - return toFixed(remainder); - case "<": - return negate(remainder); - case ">": { - // and( >=, not( = ) ) => >=, =, not, and - const fixed = toFixed(remainder); - // eslint-disable-next-line no-sparse-arrays - return [, fixed, 0, remainder, 2]; - } - case "<=": - // or( <, = ) => <, =, or - // eslint-disable-next-line no-sparse-arrays - return [, toFixed(remainder), negate(remainder), 1]; - case "!": { - // not = - const fixed = toFixed(remainder); - // eslint-disable-next-line no-sparse-arrays - return [, fixed, 0]; - } - default: - throw new Error("Unexpected start value"); - } - }; - - /** - * - * @param {SemVerRangeItem[][]} items items - * @param {number} fn fn - * @returns {SemVerRange} result - */ - const combine = (items, fn) => { - if (items.length === 1) return items[0]; - const arr = []; - for (const item of items.slice().reverse()) { - if (0 in item) { - arr.push(item); - } else { - arr.push(...item.slice(1)); - } - } - - // eslint-disable-next-line no-sparse-arrays - return [, ...arr, ...items.slice(1).map(() => fn)]; - }; - - /** - * @param {string} str str - * @returns {SemVerRange} - */ - const parseRange = str => { - // range ::= hyphen | simple ( ' ' ( ' ' ) * simple ) * | '' - // hyphen ::= partial ( ' ' ) * ' - ' ( ' ' ) * partial - const items = str.split(/\s+-\s+/); - - if (items.length === 1) { - str = str.trim(); - - /** @type {SemVerRangeItem[][]} */ - const items = []; - const r = /[-0-9A-Za-z]\s+/g; - var start = 0; - var match; - while ((match = r.exec(str))) { - const end = match.index + 1; - items.push( - /** @type {SemVerRangeItem[]} */ - (parseSimple(str.slice(start, end).trim())) - ); - start = end; - } - items.push( - /** @type {SemVerRangeItem[]} */ - (parseSimple(str.slice(start).trim())) - ); - return combine(items, 2); - } - - const a = parsePartial(items[0]); - const b = parsePartial(items[1]); - // >=a <=b => and( >=a, or( >=a, { - // range-set ::= range ( logical-or range ) * - // logical-or ::= ( ' ' ) * '||' ( ' ' ) * - const items = - /** @type {SemVerRangeItem[][]} */ - (str.split(/\s*\|\|\s*/).map(parseRange)); - - return combine(items, 1); - }; - - return parseLogicalOr(str); -}; - -/* eslint-disable eqeqeq */ -/** - * @param {SemVerRange} range - * @returns {string} - */ -const rangeToString = range => { - var fixCount = /** @type {number} */ (range[0]); - var str = ""; - if (range.length === 1) { - return "*"; - } else if (fixCount + 0.5) { - str += - fixCount == 0 - ? ">=" - : fixCount == -1 - ? "<" - : fixCount == 1 - ? "^" - : fixCount == 2 - ? "~" - : fixCount > 0 - ? "=" - : "!="; - var needDot = 1; - for (var i = 1; i < range.length; i++) { - var item = range[i]; - var t = (typeof item)[0]; - needDot--; - str += - t == "u" - ? // undefined: prerelease marker, add an "-" - "-" - : // number or string: add the item, set flag to add an "." between two of them - (needDot > 0 ? "." : "") + ((needDot = 2), item); - } - return str; - } - /** @type {string[]} */ - var stack = []; - // eslint-disable-next-line no-redeclare - for (var i = 1; i < range.length; i++) { - // eslint-disable-next-line no-redeclare - var item = range[i]; - stack.push( - item === 0 - ? "not(" + pop() + ")" - : item === 1 - ? "(" + pop() + " || " + pop() + ")" - : item === 2 - ? stack.pop() + " " + stack.pop() - : rangeToString(/** @type {SemVerRange} */ (item)) - ); - } - return pop(); - - function pop() { - return /** @type {string} */ (stack.pop()).replace(/^\((.+)\)$/, "$1"); - } -}; - -module.exports.rangeToString = rangeToString; - -/** - * @param {SemVerRange} range version range - * @param {string} version the version - * @returns {boolean} if version satisfy the range - */ -const satisfy = (range, version) => { - if (0 in range) { - // @ts-expect-error - version = parseVersion(version); - var fixCount = /** @type {number} */ (range[0]); - // when negated is set it swill set for < instead of >= - var negated = fixCount < 0; - if (negated) fixCount = -fixCount - 1; - for (var i = 0, j = 1, isEqual = true; ; j++, i++) { - // cspell:word nequal nequ - - // when isEqual = true: - // range version: EOA/object undefined number string - // EOA equal block big-ver big-ver - // undefined bigger next big-ver big-ver - // number smaller block cmp big-cmp - // fixed number smaller block cmp-fix differ - // string smaller block differ cmp - // fixed string smaller block small-cmp cmp-fix - - // when isEqual = false: - // range version: EOA/object undefined number string - // EOA nequal block next-ver next-ver - // undefined nequal block next-ver next-ver - // number nequal block next next - // fixed number nequal block next next (this never happens) - // string nequal block next next - // fixed string nequal block next next (this never happens) - - // EOA end of array - // equal (version is equal range): - // when !negated: return true, - // when negated: return false - // bigger (version is bigger as range): - // when fixed: return false, - // when !negated: return true, - // when negated: return false, - // smaller (version is smaller as range): - // when !negated: return false, - // when negated: return true - // nequal (version is not equal range (> resp <)): return true - // block (version is in different prerelease area): return false - // differ (version is different from fixed range (string vs. number)): return false - // next: continues to the next items - // next-ver: when fixed: return false, continues to the next item only for the version, sets isEqual=false - // big-ver: when fixed || negated: return false, continues to the next item only for the version, sets isEqual=false - // next-nequ: continues to the next items, sets isEqual=false - // cmp (negated === false): version < range => return false, version > range => next-nequ, else => next - // cmp (negated === true): version > range => return false, version < range => next-nequ, else => next - // cmp-fix: version == range => next, else => return false - // big-cmp: when negated => return false, else => next-nequ - // small-cmp: when negated => next-nequ, else => return false - - var rangeType = - /** @type {"s" | "n" | "u" | ""} */ - (j < range.length ? (typeof range[j])[0] : ""); - - /** @type {number | string | undefined} */ - var versionValue; - /** @type {"n" | "s" | "u" | "o" | undefined} */ - var versionType; - - // Handles first column in both tables (end of version or object) - if ( - i >= version.length || - ((versionValue = version[i]), - (versionType = /** @type {"n" | "s" | "u" | "o"} */ ( - (typeof versionValue)[0] - )) == "o") - ) { - // Handles nequal - if (!isEqual) return true; - // Handles bigger - if (rangeType == "u") return j > fixCount && !negated; - // Handles equal and smaller: (range === EOA) XOR negated - return (rangeType == "") != negated; // equal + smaller - } - - // Handles second column in both tables (version = undefined) - if (versionType == "u") { - if (!isEqual || rangeType != "u") { - return false; - } - } - - // switch between first and second table - else if (isEqual) { - // Handle diagonal - if (rangeType == versionType) { - if (j <= fixCount) { - // Handles "cmp-fix" cases - if (versionValue != range[j]) { - return false; - } - } else { - // Handles "cmp" cases - if ( - negated - ? versionValue > /** @type {(number | string)[]} */ (range)[j] - : versionValue < /** @type {(number | string)[]} */ (range)[j] - ) { - return false; - } - if (versionValue != range[j]) isEqual = false; - } - } - - // Handle big-ver - else if (rangeType != "s" && rangeType != "n") { - if (negated || j <= fixCount) return false; - isEqual = false; - j--; - } - - // Handle differ, big-cmp and small-cmp - else if (j <= fixCount || versionType < rangeType != negated) { - return false; - } else { - isEqual = false; - } - } else { - // Handles all "next-ver" cases in the second table - // eslint-disable-next-line no-lonely-if - if (rangeType != "s" && rangeType != "n") { - isEqual = false; - j--; - } - - // next is applied by default - } - } - } - - /** @type {(boolean | number)[]} */ - var stack = []; - var p = stack.pop.bind(stack); - // eslint-disable-next-line no-redeclare - for (var i = 1; i < range.length; i++) { - var item = /** @type {SemVerRangeItem[] | 0 | 1 | 2} */ (range[i]); - - stack.push( - item == 1 - ? /** @type {() => number} */ (p)() | /** @type {() => number} */ (p)() - : item == 2 - ? /** @type {() => number} */ (p)() & - /** @type {() => number} */ (p)() - : item - ? satisfy(item, version) - : !p() - ); - } - return !!p(); -}; -/* eslint-enable eqeqeq */ -module.exports.satisfy = satisfy; - -/** - * @param {SemVerRange | string | number | false | undefined} json - * @returns {string} - */ -module.exports.stringifyHoley = json => { - switch (typeof json) { - case "undefined": - return ""; - case "object": - if (Array.isArray(json)) { - let str = "["; - for (let i = 0; i < json.length; i++) { - if (i !== 0) str += ","; - str += this.stringifyHoley(json[i]); - } - str += "]"; - return str; - } - - return JSON.stringify(json); - default: - return JSON.stringify(json); - } -}; - -//#region runtime code: parseVersion -/** - * @param {RuntimeTemplate} runtimeTemplate - * @returns {string} - */ -exports.parseVersionRuntimeCode = runtimeTemplate => - `var parseVersion = ${runtimeTemplate.basicFunction("str", [ - "// see webpack/lib/util/semver.js for original code", - `var p=${runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(p)"}{return p.split(".").map((${runtimeTemplate.supportsArrowFunction() ? "p=>" : "function(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;` - ])}`; -//#endregion - -//#region runtime code: versionLt -/** - * @param {RuntimeTemplate} runtimeTemplate - * @returns {string} - */ -exports.versionLtRuntimeCode = runtimeTemplate => - `var versionLt = ${runtimeTemplate.basicFunction("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 - `var rangeToString = ${runtimeTemplate.basicFunction("range", [ - "// 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 - `var satisfy = ${runtimeTemplate.basicFunction("range, version", [ - "// 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 - require("../serialization/BinaryMiddleware") -); -const getObjectMiddleware = memoize(() => - require("../serialization/ObjectMiddleware") -); -const getSingleItemMiddleware = memoize(() => - require("../serialization/SingleItemMiddleware") -); -const getSerializer = memoize(() => require("../serialization/Serializer")); -const getSerializerMiddleware = memoize(() => - require("../serialization/SerializerMiddleware") -); - -const getBinaryMiddlewareInstance = memoize( - () => new (getBinaryMiddleware())() -); - -const registerSerializers = memoize(() => { - require("./registerExternalSerializer"); - - // Load internal paths with a relative require - // This allows bundling all internal serializers - const internalSerializables = require("./internalSerializables"); - getObjectMiddleware().registerLoader(/^webpack\/lib\//, req => { - const loader = - internalSerializables[ - /** @type {keyof import("./internalSerializables")} */ - (req.slice("webpack/lib/".length)) - ]; - if (loader) { - loader(); - } else { - console.warn(`${req} not found in internalSerializables`); - } - return true; - }); -}); - -/** @type {Serializer} */ -let buffersSerializer; - -// Expose serialization API -module.exports = { - get register() { - return getObjectMiddleware().register; - }, - get registerLoader() { - return getObjectMiddleware().registerLoader; - }, - get registerNotSerializable() { - return getObjectMiddleware().registerNotSerializable; - }, - get NOT_SERIALIZABLE() { - return getObjectMiddleware().NOT_SERIALIZABLE; - }, - /** @type {MEASURE_START_OPERATION} */ - get MEASURE_START_OPERATION() { - return getBinaryMiddleware().MEASURE_START_OPERATION; - }, - /** @type {MEASURE_END_OPERATION} */ - get MEASURE_END_OPERATION() { - return getBinaryMiddleware().MEASURE_END_OPERATION; - }, - /** - * @returns {Serializer} buffer serializer - */ - get buffersSerializer() { - if (buffersSerializer !== undefined) return buffersSerializer; - registerSerializers(); - const Serializer = getSerializer(); - const binaryMiddleware = getBinaryMiddlewareInstance(); - const SerializerMiddleware = getSerializerMiddleware(); - const SingleItemMiddleware = getSingleItemMiddleware(); - return (buffersSerializer = new Serializer([ - new SingleItemMiddleware(), - new (getObjectMiddleware())(context => { - if (context.write) { - /** - * @param {any} value value - */ - context.writeLazy = value => { - context.write( - SerializerMiddleware.createLazy(value, binaryMiddleware) - ); - }; - } - }, "md4"), - binaryMiddleware - ])); - }, - /** - * @param {IntermediateFileSystem} fs filesystem - * @param {string | Hash} hashFunction hash function to use - * @returns {Serializer} file serializer - */ - createFileSerializer: (fs, hashFunction) => { - registerSerializers(); - const Serializer = getSerializer(); - const FileMiddleware = require("../serialization/FileMiddleware"); - const fileMiddleware = new FileMiddleware(fs, hashFunction); - const binaryMiddleware = getBinaryMiddlewareInstance(); - const SerializerMiddleware = getSerializerMiddleware(); - const SingleItemMiddleware = getSingleItemMiddleware(); - return new Serializer([ - new SingleItemMiddleware(), - new (getObjectMiddleware())(context => { - if (context.write) { - /** - * @param {any} value value - */ - context.writeLazy = value => { - context.write( - SerializerMiddleware.createLazy(value, binaryMiddleware) - ); - }; - /** - * @param {any} value value - * @param {object=} options lazy options - * @returns {function(): Promise | any} lazy function - */ - context.writeSeparate = (value, options) => { - const lazy = SerializerMiddleware.createLazy( - value, - fileMiddleware, - options - ); - context.write(lazy); - return lazy; - }; - } - }, hashFunction), - binaryMiddleware, - fileMiddleware - ]); - } -}; diff --git a/webpack-lib/lib/util/smartGrouping.js b/webpack-lib/lib/util/smartGrouping.js deleted file mode 100644 index f75648c45b8..00000000000 --- a/webpack-lib/lib/util/smartGrouping.js +++ /dev/null @@ -1,206 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** - * @typedef {object} GroupOptions - * @property {boolean=} groupChildren - * @property {boolean=} force - * @property {number=} targetGroupCount - */ - -/** - * @template T - * @template R - * @typedef {object} GroupConfig - * @property {function(T): string[] | undefined} getKeys - * @property {function(string, (R | T)[], T[]): R} createGroup - * @property {function(string, T[]): GroupOptions=} getOptions - */ - -/** - * @template T - * @template R - * @typedef {object} ItemWithGroups - * @property {T} item - * @property {Set>} groups - */ - -/** - * @template T - * @template R - * @typedef {{ config: GroupConfig, name: string, alreadyGrouped: boolean, items: Set> | undefined }} Group - */ - -/** - * @template T - * @template R - * @param {T[]} items the list of items - * @param {GroupConfig[]} groupConfigs configuration - * @returns {(R | T)[]} grouped items - */ -const smartGrouping = (items, groupConfigs) => { - /** @type {Set>} */ - const itemsWithGroups = new Set(); - /** @type {Map>} */ - const allGroups = new Map(); - for (const item of items) { - /** @type {Set>} */ - const groups = new Set(); - for (let i = 0; i < groupConfigs.length; i++) { - const groupConfig = groupConfigs[i]; - const keys = groupConfig.getKeys(item); - if (keys) { - for (const name of keys) { - const key = `${i}:${name}`; - let group = allGroups.get(key); - if (group === undefined) { - allGroups.set( - key, - (group = { - config: groupConfig, - name, - alreadyGrouped: false, - items: undefined - }) - ); - } - groups.add(group); - } - } - } - itemsWithGroups.add({ - item, - groups - }); - } - /** - * @param {Set>} itemsWithGroups input items with groups - * @returns {(T | R)[]} groups items - */ - const runGrouping = itemsWithGroups => { - const totalSize = itemsWithGroups.size; - for (const entry of itemsWithGroups) { - for (const group of entry.groups) { - if (group.alreadyGrouped) continue; - const items = group.items; - if (items === undefined) { - group.items = new Set([entry]); - } else { - items.add(entry); - } - } - } - /** @type {Map, { items: Set>, options: GroupOptions | false | undefined, used: boolean }>} */ - const groupMap = new Map(); - for (const group of allGroups.values()) { - if (group.items) { - const items = group.items; - group.items = undefined; - groupMap.set(group, { - items, - options: undefined, - used: false - }); - } - } - /** @type {(T | R)[]} */ - const results = []; - for (;;) { - /** @type {Group | undefined} */ - let bestGroup; - let bestGroupSize = -1; - let bestGroupItems; - let bestGroupOptions; - for (const [group, state] of groupMap) { - const { items, used } = state; - let options = state.options; - if (options === undefined) { - const groupConfig = group.config; - state.options = options = - (groupConfig.getOptions && - groupConfig.getOptions( - group.name, - Array.from(items, ({ item }) => item) - )) || - false; - } - - const force = options && options.force; - if (!force) { - if (bestGroupOptions && bestGroupOptions.force) continue; - if (used) continue; - if (items.size <= 1 || totalSize - items.size <= 1) { - continue; - } - } - const targetGroupCount = (options && options.targetGroupCount) || 4; - const sizeValue = force - ? items.size - : Math.min( - items.size, - (totalSize * 2) / targetGroupCount + - itemsWithGroups.size - - items.size - ); - if ( - sizeValue > bestGroupSize || - (force && (!bestGroupOptions || !bestGroupOptions.force)) - ) { - bestGroup = group; - bestGroupSize = sizeValue; - bestGroupItems = items; - bestGroupOptions = options; - } - } - if (bestGroup === undefined) { - break; - } - const items = new Set(bestGroupItems); - const options = bestGroupOptions; - - const groupChildren = !options || options.groupChildren !== false; - - for (const item of items) { - itemsWithGroups.delete(item); - // Remove all groups that items have from the map to not select them again - for (const group of item.groups) { - const state = groupMap.get(group); - if (state !== undefined) { - state.items.delete(item); - if (state.items.size === 0) { - groupMap.delete(group); - } else { - state.options = undefined; - if (groupChildren) { - state.used = true; - } - } - } - } - } - groupMap.delete(bestGroup); - - const key = bestGroup.name; - const groupConfig = bestGroup.config; - - const allItems = Array.from(items, ({ item }) => item); - - bestGroup.alreadyGrouped = true; - const children = groupChildren ? runGrouping(items) : allItems; - bestGroup.alreadyGrouped = false; - - results.push(groupConfig.createGroup(key, children, allItems)); - } - for (const { item } of itemsWithGroups) { - results.push(item); - } - return results; - }; - return runGrouping(itemsWithGroups); -}; - -module.exports = smartGrouping; diff --git a/webpack-lib/lib/util/source.js b/webpack-lib/lib/util/source.js deleted file mode 100644 index b9516786ba1..00000000000 --- a/webpack-lib/lib/util/source.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("webpack-sources").Source} Source */ - -/** @type {WeakMap>} */ -const equalityCache = new WeakMap(); - -/** - * @param {Source} a a source - * @param {Source} b another source - * @returns {boolean} true, when both sources are equal - */ -const _isSourceEqual = (a, b) => { - // prefer .buffer(), it's called anyway during emit - /** @type {Buffer|string} */ - let aSource = typeof a.buffer === "function" ? a.buffer() : a.source(); - /** @type {Buffer|string} */ - let bSource = typeof b.buffer === "function" ? b.buffer() : b.source(); - if (aSource === bSource) return true; - if (typeof aSource === "string" && typeof bSource === "string") return false; - if (!Buffer.isBuffer(aSource)) aSource = Buffer.from(aSource, "utf-8"); - if (!Buffer.isBuffer(bSource)) bSource = Buffer.from(bSource, "utf-8"); - return aSource.equals(bSource); -}; - -/** - * @param {Source} a a source - * @param {Source} b another source - * @returns {boolean} true, when both sources are equal - */ -const isSourceEqual = (a, b) => { - if (a === b) return true; - const cache1 = equalityCache.get(a); - if (cache1 !== undefined) { - const result = cache1.get(b); - if (result !== undefined) return result; - } - const result = _isSourceEqual(a, b); - if (cache1 !== undefined) { - cache1.set(b, result); - } else { - const map = new WeakMap(); - map.set(b, result); - equalityCache.set(a, map); - } - const cache2 = equalityCache.get(b); - if (cache2 !== undefined) { - cache2.set(a, result); - } else { - const map = new WeakMap(); - map.set(a, result); - equalityCache.set(b, map); - } - return result; -}; -module.exports.isSourceEqual = isSourceEqual; diff --git a/webpack-lib/lib/validateSchema.js b/webpack-lib/lib/validateSchema.js deleted file mode 100644 index 83841b61119..00000000000 --- a/webpack-lib/lib/validateSchema.js +++ /dev/null @@ -1,176 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { validate } = require("schema-utils"); - -/* cSpell:disable */ -const DID_YOU_MEAN = { - rules: "module.rules", - loaders: "module.rules or module.rules.*.use", - query: "module.rules.*.options (BREAKING CHANGE since webpack 5)", - noParse: "module.noParse", - filename: "output.filename or module.rules.*.generator.filename", - file: "output.filename", - chunkFilename: "output.chunkFilename", - chunkfilename: "output.chunkFilename", - ecmaVersion: - "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", - ecmaversion: - "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", - ecma: "output.environment (output.ecmaVersion was a temporary configuration option during webpack 5 beta)", - path: "output.path", - pathinfo: "output.pathinfo", - pathInfo: "output.pathinfo", - jsonpFunction: "output.chunkLoadingGlobal (BREAKING CHANGE since webpack 5)", - chunkCallbackName: - "output.chunkLoadingGlobal (BREAKING CHANGE since webpack 5)", - jsonpScriptType: "output.scriptType (BREAKING CHANGE since webpack 5)", - hotUpdateFunction: "output.hotUpdateGlobal (BREAKING CHANGE since webpack 5)", - splitChunks: "optimization.splitChunks", - immutablePaths: "snapshot.immutablePaths", - managedPaths: "snapshot.managedPaths", - maxModules: "stats.modulesSpace (BREAKING CHANGE since webpack 5)", - hashedModuleIds: - 'optimization.moduleIds: "hashed" (BREAKING CHANGE since webpack 5)', - namedChunks: - 'optimization.chunkIds: "named" (BREAKING CHANGE since webpack 5)', - namedModules: - 'optimization.moduleIds: "named" (BREAKING CHANGE since webpack 5)', - occurrenceOrder: - 'optimization.chunkIds: "size" and optimization.moduleIds: "size" (BREAKING CHANGE since webpack 5)', - automaticNamePrefix: - "optimization.splitChunks.[cacheGroups.*].idHint (BREAKING CHANGE since webpack 5)", - noEmitOnErrors: - "optimization.emitOnErrors (BREAKING CHANGE since webpack 5: logic is inverted to avoid negative flags)", - Buffer: - "to use the ProvidePlugin to process the Buffer variable to modules as polyfill\n" + - "BREAKING CHANGE: webpack 5 no longer provided Node.js polyfills by default.\n" + - "Note: if you are using 'node.Buffer: false', you can just remove that as this is the default behavior now.\n" + - "To provide a polyfill to modules use:\n" + - 'new ProvidePlugin({ Buffer: ["buffer", "Buffer"] }) and npm install buffer.', - process: - "to use the ProvidePlugin to process the process variable to modules as polyfill\n" + - "BREAKING CHANGE: webpack 5 no longer provided Node.js polyfills by default.\n" + - "Note: if you are using 'node.process: false', you can just remove that as this is the default behavior now.\n" + - "To provide a polyfill to modules use:\n" + - 'new ProvidePlugin({ process: "process" }) and npm install buffer.' -}; - -const REMOVED = { - concord: - "BREAKING CHANGE: resolve.concord has been removed and is no longer available.", - devtoolLineToLine: - "BREAKING CHANGE: output.devtoolLineToLine has been removed and is no longer available." -}; -/* cSpell:enable */ - -/** - * @param {Parameters[0]} schema a json schema - * @param {Parameters[1]} options the options that should be validated - * @param {Parameters[2]=} validationConfiguration configuration for generating errors - * @returns {void} - */ -const validateSchema = (schema, options, validationConfiguration) => { - validate( - schema, - options, - validationConfiguration || { - name: "Webpack", - postFormatter: (formattedError, error) => { - const children = error.children; - if ( - children && - children.some( - child => - child.keyword === "absolutePath" && - child.instancePath === "/output/filename" - ) - ) { - return `${formattedError}\nPlease use output.path to specify absolute path and output.filename for the file name.`; - } - - if ( - children && - children.some( - child => - child.keyword === "pattern" && child.instancePath === "/devtool" - ) - ) { - return ( - `${formattedError}\n` + - "BREAKING CHANGE since webpack 5: The devtool option is more strict.\n" + - "Please strictly follow the order of the keywords in the pattern." - ); - } - - if (error.keyword === "additionalProperties") { - const params = error.params; - if ( - Object.prototype.hasOwnProperty.call( - DID_YOU_MEAN, - params.additionalProperty - ) - ) { - return `${formattedError}\nDid you mean ${ - DID_YOU_MEAN[ - /** @type {keyof DID_YOU_MEAN} */ (params.additionalProperty) - ] - }?`; - } - - if ( - Object.prototype.hasOwnProperty.call( - REMOVED, - params.additionalProperty - ) - ) { - return `${formattedError}\n${ - REMOVED[/** @type {keyof REMOVED} */ (params.additionalProperty)] - }?`; - } - - if (!error.instancePath) { - if (params.additionalProperty === "debug") { - return ( - `${formattedError}\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" + - "]" - ); - } - - if (params.additionalProperty) { - return ( - `${formattedError}\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" + - ` ${params.additionalProperty}: …\n` + - " }\n" + - " })\n" + - " ]" - ); - } - } - } - - return formattedError; - } - } - ); -}; -module.exports = validateSchema; diff --git a/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js b/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js deleted file mode 100644 index e1f1c3a4b14..00000000000 --- a/webpack-lib/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ - -/** - * @typedef {object} AsyncWasmLoadingRuntimeModuleOptions - * @property {(function(string): string)=} generateBeforeLoadBinaryCode - * @property {function(string): string} generateLoadBinaryCode - * @property {(function(): string)=} generateBeforeInstantiateStreaming - * @property {boolean} supportsStreaming - */ - -class AsyncWasmLoadingRuntimeModule extends RuntimeModule { - /** - * @param {AsyncWasmLoadingRuntimeModuleOptions} options options - */ - constructor({ - generateLoadBinaryCode, - generateBeforeLoadBinaryCode, - generateBeforeInstantiateStreaming, - supportsStreaming - }) { - super("wasm loading", RuntimeModule.STAGE_NORMAL); - this.generateLoadBinaryCode = generateLoadBinaryCode; - this.generateBeforeLoadBinaryCode = generateBeforeLoadBinaryCode; - this.generateBeforeInstantiateStreaming = - generateBeforeInstantiateStreaming; - this.supportsStreaming = supportsStreaming; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const chunk = /** @type {Chunk} */ (this.chunk); - const { outputOptions, runtimeTemplate } = compilation; - const fn = RuntimeGlobals.instantiateWasm; - const wasmModuleSrcPath = compilation.getPath( - JSON.stringify(outputOptions.webassemblyModuleFilename), - { - hash: `" + ${RuntimeGlobals.getFullHash}() + "`, - hashWithLength: length => - `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`, - module: { - id: '" + wasmModuleId + "', - hash: '" + wasmModuleHash + "', - hashWithLength(length) { - return `" + wasmModuleHash.slice(0, ${length}) + "`; - } - }, - runtime: chunk.runtime - } - ); - - const loader = this.generateLoadBinaryCode(wasmModuleSrcPath); - const fallback = [ - `.then(${runtimeTemplate.returningFunction("x.arrayBuffer()", "x")})`, - `.then(${runtimeTemplate.returningFunction( - "WebAssembly.instantiate(bytes, importsObj)", - "bytes" - )})`, - `.then(${runtimeTemplate.returningFunction( - "Object.assign(exports, res.instance.exports)", - "res" - )})` - ]; - const getStreaming = () => { - const concat = (/** @type {string[]} */ ...text) => text.join(""); - return [ - this.generateBeforeLoadBinaryCode - ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath) - : "", - `var req = ${loader};`, - `var fallback = ${runtimeTemplate.returningFunction( - Template.asString(["req", Template.indent(fallback)]) - )};`, - concat( - "return req.then(", - runtimeTemplate.basicFunction("res", [ - 'if (typeof WebAssembly.instantiateStreaming === "function") {', - Template.indent( - this.generateBeforeInstantiateStreaming - ? this.generateBeforeInstantiateStreaming() - : "" - ), - Template.indent([ - "return WebAssembly.instantiateStreaming(res, importsObj)", - Template.indent([ - ".then(", - Template.indent([ - `${runtimeTemplate.returningFunction( - "Object.assign(exports, res.instance.exports)", - "res" - )},`, - runtimeTemplate.basicFunction("e", [ - 'if(res.headers.get("Content-Type") !== "application/wasm") {', - Template.indent([ - 'console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\\n", e);', - "return fallback();" - ]), - "}", - "throw e;" - ]) - ]), - ");" - ]) - ]), - "}", - "return fallback();" - ]), - ");" - ) - ]; - }; - - return `${fn} = ${runtimeTemplate.basicFunction( - "exports, wasmModuleId, wasmModuleHash, importsObj", - this.supportsStreaming - ? getStreaming() - : [ - this.generateBeforeLoadBinaryCode - ? this.generateBeforeLoadBinaryCode(wasmModuleSrcPath) - : "", - `return ${loader}`, - `${Template.indent(fallback)};` - ] - )};`; - } -} - -module.exports = AsyncWasmLoadingRuntimeModule; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js deleted file mode 100644 index 558541e0d84..00000000000 --- a/webpack-lib/lib/wasm-async/AsyncWebAssemblyGenerator.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Generator = require("../Generator"); -const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ - -/** - * @typedef {object} AsyncWebAssemblyGeneratorOptions - * @property {boolean} [mangleImports] mangle imports - */ - -class AsyncWebAssemblyGenerator extends Generator { - /** - * @param {AsyncWebAssemblyGeneratorOptions} options options - */ - constructor(options) { - super(); - this.options = options; - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return WEBASSEMBLY_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - const originalSource = module.originalSource(); - if (!originalSource) { - return 0; - } - return originalSource.size(); - } - - /** - * @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) { - return /** @type {Source} */ (module.originalSource()); - } -} - -module.exports = AsyncWebAssemblyGenerator; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js deleted file mode 100644 index 30f30b0ece4..00000000000 --- a/webpack-lib/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +++ /dev/null @@ -1,206 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const Generator = require("../Generator"); -const InitFragment = require("../InitFragment"); -const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -/** - * @typedef {{ request: string, importVar: string }} ImportObjRequestItem - */ - -class AsyncWebAssemblyJavascriptGenerator extends Generator { - /** - * @param {OutputOptions["webassemblyModuleFilename"]} filenameTemplate template for the WebAssembly module filename - */ - constructor(filenameTemplate) { - super(); - this.filenameTemplate = filenameTemplate; - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return WEBASSEMBLY_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - return 40 + module.dependencies.length * 10; - } - - /** - * @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 { - runtimeTemplate, - chunkGraph, - moduleGraph, - runtimeRequirements, - runtime - } = generateContext; - runtimeRequirements.add(RuntimeGlobals.module); - runtimeRequirements.add(RuntimeGlobals.moduleId); - runtimeRequirements.add(RuntimeGlobals.exports); - runtimeRequirements.add(RuntimeGlobals.instantiateWasm); - /** @type {InitFragment>[]} */ - const initFragments = []; - /** @type {Map} */ - const depModules = new Map(); - /** @type {Map} */ - const wasmDepsByRequest = new Map(); - for (const dep of module.dependencies) { - if (dep instanceof WebAssemblyImportDependency) { - const module = /** @type {Module} */ (moduleGraph.getModule(dep)); - if (!depModules.has(module)) { - depModules.set(module, { - request: dep.request, - importVar: `WEBPACK_IMPORTED_MODULE_${depModules.size}` - }); - } - let list = wasmDepsByRequest.get(dep.request); - if (list === undefined) { - list = []; - wasmDepsByRequest.set(dep.request, list); - } - list.push(dep); - } - } - - /** @type {Array} */ - const promises = []; - - const importStatements = Array.from( - depModules, - ([importedModule, { request, importVar }]) => { - if (moduleGraph.isAsync(importedModule)) { - promises.push(importVar); - } - return runtimeTemplate.importStatement({ - update: false, - module: importedModule, - chunkGraph, - request, - originModule: module, - importVar, - runtimeRequirements - }); - } - ); - const importsCode = importStatements.map(([x]) => x).join(""); - const importsCompatCode = importStatements.map(([_, x]) => x).join(""); - - const importObjRequestItems = Array.from( - wasmDepsByRequest, - ([request, deps]) => { - const exportItems = deps.map(dep => { - const importedModule = - /** @type {Module} */ - (moduleGraph.getModule(dep)); - const importVar = - /** @type {ImportObjRequestItem} */ - (depModules.get(importedModule)).importVar; - return `${JSON.stringify( - dep.name - )}: ${runtimeTemplate.exportFromImport({ - moduleGraph, - module: importedModule, - request, - exportName: dep.name, - originModule: module, - asiSafe: true, - isCall: false, - callContext: false, - defaultInterop: true, - importVar, - initFragments, - runtime, - runtimeRequirements - })}`; - }); - return Template.asString([ - `${JSON.stringify(request)}: {`, - Template.indent(exportItems.join(",\n")), - "}" - ]); - } - ); - - const importsObj = - importObjRequestItems.length > 0 - ? Template.asString([ - "{", - Template.indent(importObjRequestItems.join(",\n")), - "}" - ]) - : undefined; - - const instantiateCall = `${RuntimeGlobals.instantiateWasm}(${module.exportsArgument}, ${ - module.moduleArgument - }.id, ${JSON.stringify( - chunkGraph.getRenderedModuleHash(module, runtime) - )}${importsObj ? `, ${importsObj})` : ")"}`; - - if (promises.length > 0) - runtimeRequirements.add(RuntimeGlobals.asyncModule); - - const source = new RawSource( - promises.length > 0 - ? Template.asString([ - `var __webpack_instantiate__ = ${runtimeTemplate.basicFunction( - `[${promises.join(", ")}]`, - `${importsCompatCode}return ${instantiateCall};` - )}`, - `${RuntimeGlobals.asyncModule}(${ - module.moduleArgument - }, async ${runtimeTemplate.basicFunction( - "__webpack_handle_async_dependencies__, __webpack_async_result__", - [ - "try {", - importsCode, - `var __webpack_async_dependencies__ = __webpack_handle_async_dependencies__([${promises.join( - ", " - )}]);`, - `var [${promises.join( - ", " - )}] = __webpack_async_dependencies__.then ? (await __webpack_async_dependencies__)() : __webpack_async_dependencies__;`, - `${importsCompatCode}await ${instantiateCall};`, - "__webpack_async_result__();", - "} catch(e) { __webpack_async_result__(e); }" - ] - )}, 1);` - ]) - : `${importsCode}${importsCompatCode}module.exports = ${instantiateCall};` - ); - - return InitFragment.addToSource(source, initFragments, generateContext); - } -} - -module.exports = AsyncWebAssemblyJavascriptGenerator; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js deleted file mode 100644 index 74a612757e9..00000000000 --- a/webpack-lib/lib/wasm-async/AsyncWebAssemblyModulesPlugin.js +++ /dev/null @@ -1,218 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const Compilation = require("../Compilation"); -const Generator = require("../Generator"); -const { tryRunOrWebpackError } = require("../HookWebpackError"); -const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const memoize = require("../util/memoize"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../Template").RenderManifestEntry} RenderManifestEntry */ -/** @typedef {import("../Template").RenderManifestOptions} RenderManifestOptions */ -/** @typedef {import("../WebpackError")} WebpackError */ - -const getAsyncWebAssemblyGenerator = memoize(() => - require("./AsyncWebAssemblyGenerator") -); -const getAsyncWebAssemblyJavascriptGenerator = memoize(() => - require("./AsyncWebAssemblyJavascriptGenerator") -); -const getAsyncWebAssemblyParser = memoize(() => - require("./AsyncWebAssemblyParser") -); - -/** - * @typedef {object} WebAssemblyRenderContext - * @property {Chunk} chunk the chunk - * @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 {CodeGenerationResults} codeGenerationResults results of code generation - */ - -/** - * @typedef {object} CompilationHooks - * @property {SyncWaterfallHook<[Source, Module, WebAssemblyRenderContext]>} renderModuleContent - */ - -/** - * @typedef {object} AsyncWebAssemblyModulesPluginOptions - * @property {boolean} [mangleImports] mangle imports - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -const PLUGIN_NAME = "AsyncWebAssemblyModulesPlugin"; - -class AsyncWebAssemblyModulesPlugin { - /** - * @param {Compilation} compilation the compilation - * @returns {CompilationHooks} 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 = { - renderModuleContent: new SyncWaterfallHook([ - "source", - "module", - "renderContext" - ]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {AsyncWebAssemblyModulesPluginOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - const hooks = - AsyncWebAssemblyModulesPlugin.getCompilationHooks(compilation); - compilation.dependencyFactories.set( - WebAssemblyImportDependency, - normalModuleFactory - ); - - normalModuleFactory.hooks.createParser - .for(WEBASSEMBLY_MODULE_TYPE_ASYNC) - .tap(PLUGIN_NAME, () => { - const AsyncWebAssemblyParser = getAsyncWebAssemblyParser(); - - return new AsyncWebAssemblyParser(); - }); - normalModuleFactory.hooks.createGenerator - .for(WEBASSEMBLY_MODULE_TYPE_ASYNC) - .tap(PLUGIN_NAME, () => { - const AsyncWebAssemblyJavascriptGenerator = - getAsyncWebAssemblyJavascriptGenerator(); - const AsyncWebAssemblyGenerator = getAsyncWebAssemblyGenerator(); - - return Generator.byType({ - javascript: new AsyncWebAssemblyJavascriptGenerator( - compilation.outputOptions.webassemblyModuleFilename - ), - webassembly: new AsyncWebAssemblyGenerator(this.options) - }); - }); - - compilation.hooks.renderManifest.tap( - "WebAssemblyModulesPlugin", - (result, options) => { - const { moduleGraph, chunkGraph, runtimeTemplate } = compilation; - const { - chunk, - outputOptions, - dependencyTemplates, - codeGenerationResults - } = options; - - for (const module of chunkGraph.getOrderedChunkModulesIterable( - chunk, - compareModulesByIdentifier - )) { - if (module.type === WEBASSEMBLY_MODULE_TYPE_ASYNC) { - const filenameTemplate = - /** @type {NonNullable} */ - (outputOptions.webassemblyModuleFilename); - - result.push({ - render: () => - this.renderModule( - module, - { - chunk, - dependencyTemplates, - runtimeTemplate, - moduleGraph, - chunkGraph, - codeGenerationResults - }, - hooks - ), - filenameTemplate, - pathOptions: { - module, - runtime: chunk.runtime, - chunkGraph - }, - auxiliary: true, - identifier: `webassemblyAsyncModule${chunkGraph.getModuleId( - module - )}`, - hash: chunkGraph.getModuleHash(module, chunk.runtime) - }); - } - } - - return result; - } - ); - } - ); - } - - /** - * @param {Module} module the rendered module - * @param {WebAssemblyRenderContext} renderContext options object - * @param {CompilationHooks} hooks hooks - * @returns {Source} the newly generated source from rendering - */ - renderModule(module, renderContext, hooks) { - const { codeGenerationResults, chunk } = renderContext; - try { - const moduleSource = codeGenerationResults.getSource( - module, - chunk.runtime, - "webassembly" - ); - return tryRunOrWebpackError( - () => - hooks.renderModuleContent.call(moduleSource, module, renderContext), - "AsyncWebAssemblyModulesPlugin.getCompilationHooks().renderModuleContent" - ); - } catch (err) { - /** @type {WebpackError} */ (err).module = module; - throw err; - } - } -} - -module.exports = AsyncWebAssemblyModulesPlugin; diff --git a/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js b/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js deleted file mode 100644 index 40f1c79eacc..00000000000 --- a/webpack-lib/lib/wasm-async/AsyncWebAssemblyParser.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const t = require("@webassemblyjs/ast"); -const { decode } = require("@webassemblyjs/wasm-parser"); -const EnvironmentNotSupportAsyncWarning = require("../EnvironmentNotSupportAsyncWarning"); -const Parser = require("../Parser"); -const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); - -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ - -const decoderOpts = { - ignoreCodeSection: true, - ignoreDataSection: true, - - // this will avoid having to lookup with identifiers in the ModuleContext - ignoreCustomNameSection: true -}; - -class WebAssemblyParser extends Parser { - /** - * @param {{}=} options parser options - */ - constructor(options) { - super(); - this.hooks = Object.freeze({}); - this.options = options; - } - - /** - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - if (!Buffer.isBuffer(source)) { - throw new Error("WebAssemblyParser input must be a Buffer"); - } - - // flag it as async module - const buildInfo = /** @type {BuildInfo} */ (state.module.buildInfo); - buildInfo.strict = true; - const BuildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); - BuildMeta.exportsType = "namespace"; - BuildMeta.async = true; - EnvironmentNotSupportAsyncWarning.check( - state.module, - state.compilation.runtimeTemplate, - "asyncWebAssembly" - ); - - // parse it - const program = decode(source, decoderOpts); - const module = program.body[0]; - /** @type {Array} */ - const exports = []; - t.traverse(module, { - ModuleExport({ node }) { - exports.push(node.name); - }, - - ModuleImport({ node }) { - const dep = new WebAssemblyImportDependency( - node.module, - node.name, - node.descr, - false - ); - - state.module.addDependency(dep); - } - }); - - state.module.addDependency(new StaticExportsDependency(exports, false)); - - return state; - } -} - -module.exports = WebAssemblyParser; diff --git a/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js b/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js deleted file mode 100644 index 34b6341ce0a..00000000000 --- a/webpack-lib/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Alexander Akait @alexander-akait -*/ - -"use strict"; - -const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -const PLUGIN_NAME = "UniversalCompileAsyncWasmPlugin"; - -class UniversalCompileAsyncWasmPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const globalWasmLoading = compilation.outputOptions.wasmLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const wasmLoading = - options && options.wasmLoading !== undefined - ? options.wasmLoading - : globalWasmLoading; - return wasmLoading === "universal"; - }; - const generateBeforeInstantiateStreaming = () => - Template.asString([ - "if (!useFetch) {", - Template.indent(["return fallback();"]), - "}" - ]); - const generateBeforeLoadBinaryCode = path => - Template.asString([ - "var useFetch = typeof document !== 'undefined' || typeof self !== 'undefined';", - `var wasmUrl = ${path};` - ]); - /** - * @type {(path: string) => string} - */ - const generateLoadBinaryCode = () => - Template.asString([ - "(useFetch", - Template.indent([ - `? fetch(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url))` - ]), - Template.indent([ - ": Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {", - Template.indent([ - `readFile(new URL(wasmUrl, ${compilation.outputOptions.importMetaName}.url), (err, buffer) => {`, - Template.indent([ - "if (err) return reject(err);", - "", - "// Fake fetch response", - "resolve({", - Template.indent(["arrayBuffer() { return buffer; }"]), - "});" - ]), - "});" - ]), - "})))" - ]) - ]); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.instantiateWasm) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC - ) - ) { - return; - } - compilation.addRuntimeModule( - chunk, - new AsyncWasmLoadingRuntimeModule({ - generateBeforeLoadBinaryCode, - generateLoadBinaryCode, - generateBeforeInstantiateStreaming, - supportsStreaming: true - }) - ); - }); - }); - } -} - -module.exports = UniversalCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js b/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js deleted file mode 100644 index 5174862ca5c..00000000000 --- a/webpack-lib/lib/wasm-sync/UnsupportedWebAssemblyFeatureError.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); - -module.exports = class UnsupportedWebAssemblyFeatureError extends WebpackError { - /** @param {string} message Error message */ - constructor(message) { - super(message); - this.name = "UnsupportedWebAssemblyFeatureError"; - this.hideStack = true; - } -}; diff --git a/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js b/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js deleted file mode 100644 index 654a6204f63..00000000000 --- a/webpack-lib/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,413 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const WebAssemblyUtils = require("./WebAssemblyUtils"); - -/** @typedef {import("@webassemblyjs/ast").Signature} Signature */ -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../ChunkGraph").ModuleId} ModuleId */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ - -// TODO webpack 6 remove the whole folder - -// Get all wasm modules -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Chunk} chunk the chunk - * @returns {Module[]} all wasm modules - */ -const getAllWasmModules = (moduleGraph, chunkGraph, chunk) => { - const wasmModules = chunk.getAllAsyncChunks(); - const array = []; - for (const chunk of wasmModules) { - for (const m of chunkGraph.getOrderedChunkModulesIterable( - chunk, - compareModulesByIdentifier - )) { - if (m.type.startsWith("webassembly")) { - array.push(m); - } - } - } - - return array; -}; - -/** - * generates the import object function for a module - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {Module} module the module - * @param {boolean | undefined} mangle mangle imports - * @param {string[]} declarations array where declarations are pushed to - * @param {RuntimeSpec} runtime the runtime - * @returns {string} source code - */ -const generateImportObject = ( - chunkGraph, - module, - mangle, - declarations, - runtime -) => { - const moduleGraph = chunkGraph.moduleGraph; - /** @type {Map} */ - const waitForInstances = new Map(); - const properties = []; - const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies( - moduleGraph, - module, - mangle - ); - for (const usedDep of usedWasmDependencies) { - const dep = usedDep.dependency; - const importedModule = moduleGraph.getModule(dep); - const exportName = dep.name; - const usedName = - importedModule && - moduleGraph - .getExportsInfo(importedModule) - .getUsedName(exportName, runtime); - const description = dep.description; - const direct = dep.onlyDirectImport; - - const module = usedDep.module; - const name = usedDep.name; - - if (direct) { - const instanceVar = `m${waitForInstances.size}`; - waitForInstances.set( - instanceVar, - /** @type {ModuleId} */ - (chunkGraph.getModuleId(/** @type {Module} */ (importedModule))) - ); - properties.push({ - module, - name, - value: `${instanceVar}[${JSON.stringify(usedName)}]` - }); - } else { - const params = - /** @type {Signature} */ - (description.signature).params.map( - (param, k) => `p${k}${param.valtype}` - ); - - const mod = `${RuntimeGlobals.moduleCache}[${JSON.stringify( - chunkGraph.getModuleId(/** @type {Module} */ (importedModule)) - )}]`; - const modExports = `${mod}.exports`; - - const cache = `wasmImportedFuncCache${declarations.length}`; - declarations.push(`var ${cache};`); - - const modCode = - /** @type {Module} */ - (importedModule).type.startsWith("webassembly") - ? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : ` - : ""; - - properties.push({ - module, - name, - value: Template.asString([ - `${modCode}function(${params}) {`, - Template.indent([ - `if(${cache} === undefined) ${cache} = ${modExports};`, - `return ${cache}[${JSON.stringify(usedName)}](${params});` - ]), - "}" - ]) - }); - } - } - - let importObject; - if (mangle) { - importObject = [ - "return {", - Template.indent([ - properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") - ]), - "};" - ]; - } else { - /** @type {Map>} */ - const propertiesByModule = new Map(); - for (const p of properties) { - let list = propertiesByModule.get(p.module); - if (list === undefined) { - propertiesByModule.set(p.module, (list = [])); - } - list.push(p); - } - importObject = [ - "return {", - Template.indent([ - Array.from(propertiesByModule, ([module, list]) => - Template.asString([ - `${JSON.stringify(module)}: {`, - Template.indent([ - list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") - ]), - "}" - ]) - ).join(",\n") - ]), - "};" - ]; - } - - const moduleIdStringified = JSON.stringify(chunkGraph.getModuleId(module)); - if (waitForInstances.size === 1) { - const moduleId = Array.from(waitForInstances.values())[0]; - const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`; - const variable = Array.from(waitForInstances.keys())[0]; - return Template.asString([ - `${moduleIdStringified}: function() {`, - Template.indent([ - `return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`, - Template.indent(importObject), - "});" - ]), - "}," - ]); - } else if (waitForInstances.size > 0) { - const promises = Array.from( - waitForInstances.values(), - id => `installedWasmModules[${JSON.stringify(id)}]` - ).join(", "); - const variables = Array.from( - waitForInstances.keys(), - (name, i) => `${name} = array[${i}]` - ).join(", "); - return Template.asString([ - `${moduleIdStringified}: function() {`, - Template.indent([ - `return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`, - Template.indent([`var ${variables};`, ...importObject]), - "});" - ]), - "}," - ]); - } - return Template.asString([ - `${moduleIdStringified}: function() {`, - Template.indent(importObject), - "}," - ]); -}; - -/** - * @typedef {object} WasmChunkLoadingRuntimeModuleOptions - * @property {(path: string) => string} generateLoadBinaryCode - * @property {boolean} [supportsStreaming] - * @property {boolean} [mangleImports] - * @property {ReadOnlyRuntimeRequirements} runtimeRequirements - */ - -class WasmChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {WasmChunkLoadingRuntimeModuleOptions} options options - */ - constructor({ - generateLoadBinaryCode, - supportsStreaming, - mangleImports, - runtimeRequirements - }) { - super("wasm chunk loading", RuntimeModule.STAGE_ATTACH); - this.generateLoadBinaryCode = generateLoadBinaryCode; - this.supportsStreaming = supportsStreaming; - this.mangleImports = mangleImports; - this._runtimeRequirements = runtimeRequirements; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const fn = RuntimeGlobals.ensureChunkHandlers; - const withHmr = this._runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const compilation = /** @type {Compilation} */ (this.compilation); - const { moduleGraph, outputOptions } = compilation; - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk); - const { mangleImports } = this; - /** @type {string[]} */ - const declarations = []; - const importObjects = wasmModules.map(module => - generateImportObject( - chunkGraph, - module, - mangleImports, - declarations, - chunk.runtime - ) - ); - const chunkModuleIdMap = chunkGraph.getChunkModuleIdMap(chunk, m => - m.type.startsWith("webassembly") - ); - /** - * @param {string} content content - * @returns {string} created import object - */ - const createImportObject = content => - mangleImports - ? `{ ${JSON.stringify(WebAssemblyUtils.MANGLED_MODULE)}: ${content} }` - : content; - const wasmModuleSrcPath = compilation.getPath( - JSON.stringify(outputOptions.webassemblyModuleFilename), - { - hash: `" + ${RuntimeGlobals.getFullHash}() + "`, - hashWithLength: length => - `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`, - module: { - id: '" + wasmModuleId + "', - hash: `" + ${JSON.stringify( - chunkGraph.getChunkModuleRenderedHashMap(chunk, m => - m.type.startsWith("webassembly") - ) - )}[chunkId][wasmModuleId] + "`, - hashWithLength(length) { - return `" + ${JSON.stringify( - chunkGraph.getChunkModuleRenderedHashMap( - chunk, - m => m.type.startsWith("webassembly"), - length - ) - )}[chunkId][wasmModuleId] + "`; - } - }, - runtime: chunk.runtime - } - ); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_wasm` - : undefined; - - return Template.asString([ - "// object to store loaded and loading wasm modules", - `var installedWasmModules = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{};`, - "", - // This function is used to delay reading the installed wasm module promises - // by a microtask. Sorting them doesn't help because there are edge cases where - // sorting is not possible (modules splitted into different chunks). - // So we not even trying and solve this by a microtask delay. - "function promiseResolve() { return Promise.resolve(); }", - "", - Template.asString(declarations), - "var wasmImportObjects = {", - Template.indent(importObjects), - "};", - "", - `var wasmModuleMap = ${JSON.stringify( - chunkModuleIdMap, - undefined, - "\t" - )};`, - "", - "// object with all WebAssembly.instance exports", - `${RuntimeGlobals.wasmInstances} = {};`, - "", - "// Fetch + compile chunk loading for webassembly", - `${fn}.wasm = function(chunkId, promises) {`, - Template.indent([ - "", - "var wasmModules = wasmModuleMap[chunkId] || [];", - "", - "wasmModules.forEach(function(wasmModuleId, idx) {", - Template.indent([ - "var installedWasmModuleData = installedWasmModules[wasmModuleId];", - "", - '// a Promise means "currently loading" or "already loaded".', - "if(installedWasmModuleData)", - Template.indent(["promises.push(installedWasmModuleData);"]), - "else {", - Template.indent([ - "var importObject = wasmImportObjects[wasmModuleId]();", - `var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`, - "var promise;", - this.supportsStreaming - ? Template.asString([ - "if(importObject && typeof importObject.then === 'function' && typeof WebAssembly.compileStreaming === 'function') {", - Template.indent([ - "promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {", - Template.indent([ - `return WebAssembly.instantiate(items[0], ${createImportObject( - "items[1]" - )});` - ]), - "});" - ]), - "} else if(typeof WebAssembly.instantiateStreaming === 'function') {", - Template.indent([ - `promise = WebAssembly.instantiateStreaming(req, ${createImportObject( - "importObject" - )});` - ]) - ]) - : Template.asString([ - "if(importObject && typeof importObject.then === 'function') {", - Template.indent([ - "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", - "promise = Promise.all([", - Template.indent([ - "bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),", - "importObject" - ]), - "]).then(function(items) {", - Template.indent([ - `return WebAssembly.instantiate(items[0], ${createImportObject( - "items[1]" - )});` - ]), - "});" - ]) - ]), - "} else {", - Template.indent([ - "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", - "promise = bytesPromise.then(function(bytes) {", - Template.indent([ - `return WebAssembly.instantiate(bytes, ${createImportObject( - "importObject" - )});` - ]), - "});" - ]), - "}", - "promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {", - Template.indent([ - `return ${RuntimeGlobals.wasmInstances}[wasmModuleId] = (res.instance || res).exports;` - ]), - "}));" - ]), - "}" - ]), - "});" - ]), - "};" - ]); - } -} - -module.exports = WasmChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js b/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js deleted file mode 100644 index 7e5668798be..00000000000 --- a/webpack-lib/lib/wasm-sync/WasmFinalizeExportsPlugin.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const formatLocation = require("../formatLocation"); -const UnsupportedWebAssemblyFeatureError = require("./UnsupportedWebAssemblyFeatureError"); - -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ - -class WasmFinalizeExportsPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap("WasmFinalizeExportsPlugin", compilation => { - compilation.hooks.finishModules.tap( - "WasmFinalizeExportsPlugin", - modules => { - for (const module of modules) { - // 1. if a WebAssembly module - if (module.type.startsWith("webassembly") === true) { - const jsIncompatibleExports = - /** @type {BuildMeta} */ - (module.buildMeta).jsIncompatibleExports; - - if (jsIncompatibleExports === undefined) { - continue; - } - - for (const connection of compilation.moduleGraph.getIncomingConnections( - module - )) { - // 2. is active and referenced by a non-WebAssembly module - if ( - connection.isTargetActive(undefined) && - /** @type {Module} */ - (connection.originModule).type.startsWith("webassembly") === - false - ) { - const referencedExports = - compilation.getDependencyReferencedExports( - /** @type {Dependency} */ (connection.dependency), - undefined - ); - - for (const info of referencedExports) { - const names = Array.isArray(info) ? info : info.name; - if (names.length === 0) continue; - const name = names[0]; - if (typeof name === "object") continue; - // 3. and uses a func with an incompatible JS signature - if ( - Object.prototype.hasOwnProperty.call( - jsIncompatibleExports, - name - ) - ) { - // 4. error - const error = new UnsupportedWebAssemblyFeatureError( - `Export "${name}" with ${jsIncompatibleExports[name]} can only be used for direct wasm to wasm dependencies\n` + - `It's used from ${ - /** @type {Module} */ - (connection.originModule).readableIdentifier( - compilation.requestShortener - ) - } at ${formatLocation( - /** @type {Dependency} */ (connection.dependency) - .loc - )}.` - ); - error.module = module; - compilation.errors.push(error); - } - } - } - } - } - } - } - ); - }); - } -} - -module.exports = WasmFinalizeExportsPlugin; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js b/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js deleted file mode 100644 index d315539a755..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyGenerator.js +++ /dev/null @@ -1,520 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const t = require("@webassemblyjs/ast"); -const { moduleContextFromModuleAST } = require("@webassemblyjs/ast"); -const { editWithAST, addWithAST } = require("@webassemblyjs/wasm-edit"); -const { decode } = require("@webassemblyjs/wasm-parser"); -const { RawSource } = require("webpack-sources"); -const Generator = require("../Generator"); -const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); -const WebAssemblyUtils = require("./WebAssemblyUtils"); - -const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ -/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {import("./WebAssemblyUtils").UsedWasmDependency} UsedWasmDependency */ -/** @typedef {import("@webassemblyjs/ast").Instruction} Instruction */ -/** @typedef {import("@webassemblyjs/ast").ModuleImport} ModuleImport */ -/** @typedef {import("@webassemblyjs/ast").ModuleExport} ModuleExport */ -/** @typedef {import("@webassemblyjs/ast").Global} Global */ -/** - * @template T - * @typedef {import("@webassemblyjs/ast").NodePath} NodePath - */ - -/** - * @typedef {(buf: ArrayBuffer) => ArrayBuffer} ArrayBufferTransform - */ - -/** - * @template T - * @param {((prev: ArrayBuffer) => ArrayBuffer)[]} fns transforms - * @returns {Function} composed transform - */ -const compose = (...fns) => - fns.reduce( - (prevFn, nextFn) => value => nextFn(prevFn(value)), - value => value - ); - -/** - * Removes the start instruction - * @param {object} state state - * @param {object} state.ast Module's ast - * @returns {ArrayBufferTransform} transform - */ -const removeStartFunc = state => bin => - editWithAST(state.ast, bin, { - Start(path) { - path.remove(); - } - }); - -/** - * Get imported globals - * @param {object} ast Module's AST - * @returns {t.ModuleImport[]} - nodes - */ -const getImportedGlobals = ast => { - /** @type {t.ModuleImport[]} */ - const importedGlobals = []; - - t.traverse(ast, { - ModuleImport({ node }) { - if (t.isGlobalType(node.descr)) { - importedGlobals.push(node); - } - } - }); - - return importedGlobals; -}; - -/** - * Get the count for imported func - * @param {object} ast Module's AST - * @returns {number} - count - */ -const getCountImportedFunc = ast => { - let count = 0; - - t.traverse(ast, { - ModuleImport({ node }) { - if (t.isFuncImportDescr(node.descr)) { - count++; - } - } - }); - - return count; -}; - -/** - * Get next type index - * @param {object} ast Module's AST - * @returns {t.Index} - index - */ -const getNextTypeIndex = ast => { - const typeSectionMetadata = t.getSectionMetadata(ast, "type"); - - if (typeSectionMetadata === undefined) { - return t.indexLiteral(0); - } - - return t.indexLiteral(typeSectionMetadata.vectorOfSize.value); -}; - -/** - * Get next func index - * The Func section metadata provide information for implemented funcs - * in order to have the correct index we shift the index by number of external - * functions. - * @param {object} ast Module's AST - * @param {number} countImportedFunc number of imported funcs - * @returns {t.Index} - index - */ -const getNextFuncIndex = (ast, countImportedFunc) => { - const funcSectionMetadata = t.getSectionMetadata(ast, "func"); - - if (funcSectionMetadata === undefined) { - return t.indexLiteral(0 + countImportedFunc); - } - - const vectorOfSize = funcSectionMetadata.vectorOfSize.value; - - return t.indexLiteral(vectorOfSize + countImportedFunc); -}; - -/** - * Creates an init instruction for a global type - * @param {t.GlobalType} globalType the global type - * @returns {t.Instruction} init expression - */ -const createDefaultInitForGlobal = globalType => { - if (globalType.valtype[0] === "i") { - // create NumberLiteral global initializer - return t.objectInstruction("const", globalType.valtype, [ - t.numberLiteralFromRaw(66) - ]); - } else if (globalType.valtype[0] === "f") { - // create FloatLiteral global initializer - return t.objectInstruction("const", globalType.valtype, [ - t.floatLiteral(66, false, false, "66") - ]); - } - throw new Error(`unknown type: ${globalType.valtype}`); -}; - -/** - * Rewrite the import globals: - * - removes the ModuleImport instruction - * - injects at the same offset a mutable global of the same type - * - * Since the imported globals are before the other global declarations, our - * indices will be preserved. - * - * Note that globals will become mutable. - * @param {object} state transformation state - * @param {object} state.ast Module's ast - * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function - * @returns {ArrayBufferTransform} transform - */ -const rewriteImportedGlobals = state => bin => { - const additionalInitCode = state.additionalInitCode; - /** @type {Array} */ - const newGlobals = []; - - bin = editWithAST(state.ast, bin, { - ModuleImport(path) { - if (t.isGlobalType(path.node.descr)) { - const globalType = /** @type {TODO} */ (path.node.descr); - - globalType.mutability = "var"; - - const init = [ - createDefaultInitForGlobal(globalType), - t.instruction("end") - ]; - - newGlobals.push(t.global(globalType, init)); - - path.remove(); - } - }, - - // in order to preserve non-imported global's order we need to re-inject - // those as well - /** - * @param {NodePath} path path - */ - Global(path) { - const { node } = path; - const [init] = node.init; - - if (init.id === "get_global") { - node.globalType.mutability = "var"; - - const initialGlobalIdx = init.args[0]; - - node.init = [ - createDefaultInitForGlobal(node.globalType), - t.instruction("end") - ]; - - additionalInitCode.push( - /** - * get_global in global initializer only works for imported globals. - * They have the same indices as the init params, so use the - * same index. - */ - t.instruction("get_local", [initialGlobalIdx]), - t.instruction("set_global", [t.indexLiteral(newGlobals.length)]) - ); - } - - newGlobals.push(node); - - path.remove(); - } - }); - - // Add global declaration instructions - return addWithAST(state.ast, bin, newGlobals); -}; - -/** - * Rewrite the export names - * @param {object} state state - * @param {object} state.ast Module's ast - * @param {Module} state.module Module - * @param {ModuleGraph} state.moduleGraph module graph - * @param {Set} state.externalExports Module - * @param {RuntimeSpec} state.runtime runtime - * @returns {ArrayBufferTransform} transform - */ -const rewriteExportNames = - ({ ast, moduleGraph, module, externalExports, runtime }) => - bin => - editWithAST(ast, bin, { - /** - * @param {NodePath} path path - */ - ModuleExport(path) { - const isExternal = externalExports.has(path.node.name); - if (isExternal) { - path.remove(); - return; - } - const usedName = moduleGraph - .getExportsInfo(module) - .getUsedName(path.node.name, runtime); - if (!usedName) { - path.remove(); - return; - } - path.node.name = /** @type {string} */ (usedName); - } - }); - -/** - * Mangle import names and modules - * @param {object} state state - * @param {object} state.ast Module's ast - * @param {Map} state.usedDependencyMap mappings to mangle names - * @returns {ArrayBufferTransform} transform - */ -const rewriteImports = - ({ ast, usedDependencyMap }) => - bin => - editWithAST(ast, bin, { - /** - * @param {NodePath} path path - */ - ModuleImport(path) { - const result = usedDependencyMap.get( - `${path.node.module}:${path.node.name}` - ); - - if (result !== undefined) { - path.node.module = result.module; - path.node.name = result.name; - } - } - }); - -/** - * Add an init function. - * - * The init function fills the globals given input arguments. - * @param {object} state transformation state - * @param {object} state.ast Module's ast - * @param {t.Identifier} state.initFuncId identifier of the init function - * @param {t.Index} state.startAtFuncOffset index of the start function - * @param {t.ModuleImport[]} state.importedGlobals list of imported globals - * @param {t.Instruction[]} state.additionalInitCode list of addition instructions for the init function - * @param {t.Index} state.nextFuncIndex index of the next function - * @param {t.Index} state.nextTypeIndex index of the next type - * @returns {ArrayBufferTransform} transform - */ -const addInitFunction = - ({ - ast, - initFuncId, - startAtFuncOffset, - importedGlobals, - additionalInitCode, - nextFuncIndex, - nextTypeIndex - }) => - bin => { - const funcParams = importedGlobals.map(importedGlobal => { - // used for debugging - const id = t.identifier( - `${importedGlobal.module}.${importedGlobal.name}` - ); - - return t.funcParam( - /** @type {string} */ (importedGlobal.descr.valtype), - id - ); - }); - - /** @type {Instruction[]} */ - const funcBody = []; - for (const [index, _importedGlobal] of importedGlobals.entries()) { - const args = [t.indexLiteral(index)]; - const body = [ - t.instruction("get_local", args), - t.instruction("set_global", args) - ]; - - funcBody.push(...body); - } - - if (typeof startAtFuncOffset === "number") { - funcBody.push( - t.callInstruction(t.numberLiteralFromRaw(startAtFuncOffset)) - ); - } - - for (const instr of additionalInitCode) { - funcBody.push(instr); - } - - funcBody.push(t.instruction("end")); - - /** @type {string[]} */ - const funcResults = []; - - // Code section - const funcSignature = t.signature(funcParams, funcResults); - const func = t.func(initFuncId, funcSignature, funcBody); - - // Type section - const functype = t.typeInstruction(undefined, funcSignature); - - // Func section - const funcindex = t.indexInFuncSection(nextTypeIndex); - - // Export section - const moduleExport = t.moduleExport( - initFuncId.value, - t.moduleExportDescr("Func", nextFuncIndex) - ); - - return addWithAST(ast, bin, [func, moduleExport, funcindex, functype]); - }; - -/** - * Extract mangle mappings from module - * @param {ModuleGraph} moduleGraph module graph - * @param {Module} module current module - * @param {boolean | undefined} mangle mangle imports - * @returns {Map} mappings to mangled names - */ -const getUsedDependencyMap = (moduleGraph, module, mangle) => { - /** @type {Map} */ - const map = new Map(); - for (const usedDep of WebAssemblyUtils.getUsedDependencies( - moduleGraph, - module, - mangle - )) { - const dep = usedDep.dependency; - const request = dep.request; - const exportName = dep.name; - map.set(`${request}:${exportName}`, usedDep); - } - return map; -}; - -/** - * @typedef {object} WebAssemblyGeneratorOptions - * @property {boolean} [mangleImports] mangle imports - */ - -class WebAssemblyGenerator extends Generator { - /** - * @param {WebAssemblyGeneratorOptions} options options - */ - constructor(options) { - super(); - this.options = options; - } - - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return WEBASSEMBLY_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - const originalSource = module.originalSource(); - if (!originalSource) { - return 0; - } - return originalSource.size(); - } - - /** - * @param {NormalModule} module module for which the code should be generated - * @param {GenerateContext} generateContext context for generate - * @returns {Source | null} generated code - */ - generate(module, { moduleGraph, runtime }) { - const bin = /** @type {Source} */ (module.originalSource()).source(); - - const initFuncId = t.identifier(""); - - // parse it - const ast = decode(bin, { - ignoreDataSection: true, - ignoreCodeSection: true, - ignoreCustomNameSection: true - }); - - const moduleContext = moduleContextFromModuleAST(ast.body[0]); - - const importedGlobals = getImportedGlobals(ast); - const countImportedFunc = getCountImportedFunc(ast); - const startAtFuncOffset = moduleContext.getStart(); - const nextFuncIndex = getNextFuncIndex(ast, countImportedFunc); - const nextTypeIndex = getNextTypeIndex(ast); - - const usedDependencyMap = getUsedDependencyMap( - moduleGraph, - module, - this.options.mangleImports - ); - const externalExports = new Set( - module.dependencies - .filter(d => d instanceof WebAssemblyExportImportedDependency) - .map(d => { - const wasmDep = /** @type {WebAssemblyExportImportedDependency} */ ( - d - ); - return wasmDep.exportName; - }) - ); - - /** @type {t.Instruction[]} */ - const additionalInitCode = []; - - const transform = compose( - rewriteExportNames({ - ast, - moduleGraph, - module, - externalExports, - runtime - }), - - removeStartFunc({ ast }), - - rewriteImportedGlobals({ ast, additionalInitCode }), - - rewriteImports({ - ast, - usedDependencyMap - }), - - addInitFunction({ - ast, - initFuncId, - importedGlobals, - additionalInitCode, - startAtFuncOffset, - nextFuncIndex, - nextTypeIndex - }) - ); - - const newBin = transform(bin); - - const newBuf = Buffer.from(newBin); - - return new RawSource(newBuf); - } -} - -module.exports = WebAssemblyGenerator; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js b/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js deleted file mode 100644 index 9d78ed205f4..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyInInitialChunkError.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const WebpackError = require("../WebpackError"); - -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ -/** @typedef {import("../RequestShortener")} RequestShortener */ - -/** - * @param {Module} module module to get chains from - * @param {ModuleGraph} moduleGraph the module graph - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {RequestShortener} requestShortener to make readable identifiers - * @returns {string[]} all chains to the module - */ -const getInitialModuleChains = ( - module, - moduleGraph, - chunkGraph, - requestShortener -) => { - const queue = [ - { head: module, message: module.readableIdentifier(requestShortener) } - ]; - /** @type {Set} */ - const results = new Set(); - /** @type {Set} */ - const incompleteResults = new Set(); - /** @type {Set} */ - const visitedModules = new Set(); - - for (const chain of queue) { - const { head, message } = chain; - let final = true; - /** @type {Set} */ - const alreadyReferencedModules = new Set(); - for (const connection of moduleGraph.getIncomingConnections(head)) { - const newHead = connection.originModule; - if (newHead) { - if (!chunkGraph.getModuleChunks(newHead).some(c => c.canBeInitial())) - continue; - final = false; - if (alreadyReferencedModules.has(newHead)) continue; - alreadyReferencedModules.add(newHead); - const moduleName = newHead.readableIdentifier(requestShortener); - const detail = connection.explanation - ? ` (${connection.explanation})` - : ""; - const newMessage = `${moduleName}${detail} --> ${message}`; - if (visitedModules.has(newHead)) { - incompleteResults.add(`... --> ${newMessage}`); - continue; - } - visitedModules.add(newHead); - queue.push({ - head: newHead, - message: newMessage - }); - } else { - final = false; - const newMessage = connection.explanation - ? `(${connection.explanation}) --> ${message}` - : message; - results.add(newMessage); - } - } - if (final) { - results.add(message); - } - } - for (const result of incompleteResults) { - results.add(result); - } - return Array.from(results); -}; - -module.exports = class WebAssemblyInInitialChunkError extends WebpackError { - /** - * @param {Module} module WASM module - * @param {ModuleGraph} moduleGraph the module graph - * @param {ChunkGraph} chunkGraph the chunk graph - * @param {RequestShortener} requestShortener request shortener - */ - constructor(module, moduleGraph, chunkGraph, requestShortener) { - const moduleChains = getInitialModuleChains( - module, - moduleGraph, - chunkGraph, - requestShortener - ); - const message = `WebAssembly module is included in initial chunk. -This is not allowed, because WebAssembly download and compilation must happen asynchronous. -Add an async split point (i. e. import()) somewhere between your entrypoint and the WebAssembly module: -${moduleChains.map(s => `* ${s}`).join("\n")}`; - - super(message); - this.name = "WebAssemblyInInitialChunkError"; - this.hideStack = true; - this.module = module; - } -}; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js b/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js deleted file mode 100644 index 7b4353a08c6..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +++ /dev/null @@ -1,217 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { RawSource } = require("webpack-sources"); -const { UsageState } = require("../ExportsInfo"); -const Generator = require("../Generator"); -const InitFragment = require("../InitFragment"); -const { WEBASSEMBLY_TYPES } = require("../ModuleSourceTypesConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const Template = require("../Template"); -const ModuleDependency = require("../dependencies/ModuleDependency"); -const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../Dependency")} Dependency */ -/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ -/** @typedef {import("../Generator").GenerateContext} GenerateContext */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").SourceTypes} SourceTypes */ -/** @typedef {import("../NormalModule")} NormalModule */ -/** @typedef {import("../RuntimeTemplate")} RuntimeTemplate */ - -class WebAssemblyJavascriptGenerator extends Generator { - /** - * @param {NormalModule} module fresh module - * @returns {SourceTypes} available types (do not mutate) - */ - getTypes(module) { - return WEBASSEMBLY_TYPES; - } - - /** - * @param {NormalModule} module the module - * @param {string=} type source type - * @returns {number} estimate size of the module - */ - getSize(module, type) { - return 95 + module.dependencies.length * 5; - } - - /** - * @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 { - runtimeTemplate, - moduleGraph, - chunkGraph, - runtimeRequirements, - runtime - } = generateContext; - /** @type {InitFragment>[]} */ - const initFragments = []; - - const exportsInfo = moduleGraph.getExportsInfo(module); - - let needExportsCopy = false; - const importedModules = new Map(); - const initParams = []; - let index = 0; - for (const dep of module.dependencies) { - const moduleDep = - dep && dep instanceof ModuleDependency ? dep : undefined; - if (moduleGraph.getModule(dep)) { - let importData = importedModules.get(moduleGraph.getModule(dep)); - if (importData === undefined) { - importedModules.set( - moduleGraph.getModule(dep), - (importData = { - importVar: `m${index}`, - index, - request: (moduleDep && moduleDep.userRequest) || undefined, - names: new Set(), - reexports: [] - }) - ); - index++; - } - if (dep instanceof WebAssemblyImportDependency) { - importData.names.add(dep.name); - if (dep.description.type === "GlobalType") { - const exportName = dep.name; - const importedModule = moduleGraph.getModule(dep); - - if (importedModule) { - const usedName = moduleGraph - .getExportsInfo(importedModule) - .getUsedName(exportName, runtime); - if (usedName) { - initParams.push( - runtimeTemplate.exportFromImport({ - moduleGraph, - module: importedModule, - request: dep.request, - importVar: importData.importVar, - originModule: module, - exportName: dep.name, - asiSafe: true, - isCall: false, - callContext: null, - defaultInterop: true, - initFragments, - runtime, - runtimeRequirements - }) - ); - } - } - } - } - if (dep instanceof WebAssemblyExportImportedDependency) { - importData.names.add(dep.name); - const usedName = moduleGraph - .getExportsInfo(module) - .getUsedName(dep.exportName, runtime); - if (usedName) { - runtimeRequirements.add(RuntimeGlobals.exports); - const exportProp = `${module.exportsArgument}[${JSON.stringify( - usedName - )}]`; - const defineStatement = Template.asString([ - `${exportProp} = ${runtimeTemplate.exportFromImport({ - moduleGraph, - module: /** @type {Module} */ (moduleGraph.getModule(dep)), - request: dep.request, - importVar: importData.importVar, - originModule: module, - exportName: dep.name, - asiSafe: true, - isCall: false, - callContext: null, - defaultInterop: true, - initFragments, - runtime, - runtimeRequirements - })};`, - `if(WebAssembly.Global) ${exportProp} = ` + - `new WebAssembly.Global({ value: ${JSON.stringify( - dep.valueType - )} }, ${exportProp});` - ]); - importData.reexports.push(defineStatement); - needExportsCopy = true; - } - } - } - } - const importsCode = Template.asString( - Array.from( - importedModules, - ([module, { importVar, request, reexports }]) => { - const importStatement = runtimeTemplate.importStatement({ - module, - chunkGraph, - request, - importVar, - originModule: module, - runtimeRequirements - }); - return importStatement[0] + importStatement[1] + reexports.join("\n"); - } - ) - ); - - const copyAllExports = - exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused && - !needExportsCopy; - - // need these globals - runtimeRequirements.add(RuntimeGlobals.module); - runtimeRequirements.add(RuntimeGlobals.moduleId); - runtimeRequirements.add(RuntimeGlobals.wasmInstances); - if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) { - runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); - runtimeRequirements.add(RuntimeGlobals.exports); - } - if (!copyAllExports) { - runtimeRequirements.add(RuntimeGlobals.exports); - } - - // create source - const source = new RawSource( - [ - '"use strict";', - "// Instantiate WebAssembly module", - `var wasmExports = ${RuntimeGlobals.wasmInstances}[${module.moduleArgument}.id];`, - - exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused - ? `${RuntimeGlobals.makeNamespaceObject}(${module.exportsArgument});` - : "", - - // this must be before import for circular dependencies - "// export exports from WebAssembly module", - copyAllExports - ? `${module.moduleArgument}.exports = wasmExports;` - : "for(var name in wasmExports) " + - "if(name) " + - `${module.exportsArgument}[name] = wasmExports[name];`, - "// exec imports from WebAssembly module (for esm order)", - importsCode, - "", - "// exec wasm module", - `wasmExports[""](${initParams.join(", ")})` - ].join("\n") - ); - return InitFragment.addToSource(source, initFragments, generateContext); - } -} - -module.exports = WebAssemblyJavascriptGenerator; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js b/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js deleted file mode 100644 index dc3ff32ef5f..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyModulesPlugin.js +++ /dev/null @@ -1,152 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Generator = require("../Generator"); -const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); -const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); -const { compareModulesByIdentifier } = require("../util/comparators"); -const memoize = require("../util/memoize"); -const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError"); - -/** @typedef {import("webpack-sources").Source} Source */ -/** @typedef {import("../../declarations/WebpackOptions").OutputNormalized} OutputOptions */ -/** @typedef {import("../Compiler")} Compiler */ -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleTemplate")} ModuleTemplate */ -/** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ - -const getWebAssemblyGenerator = memoize(() => - require("./WebAssemblyGenerator") -); -const getWebAssemblyJavascriptGenerator = memoize(() => - require("./WebAssemblyJavascriptGenerator") -); -const getWebAssemblyParser = memoize(() => require("./WebAssemblyParser")); - -const PLUGIN_NAME = "WebAssemblyModulesPlugin"; - -/** - * @typedef {object} WebAssemblyModulesPluginOptions - * @property {boolean} [mangleImports] mangle imports - */ - -class WebAssemblyModulesPlugin { - /** - * @param {WebAssemblyModulesPluginOptions} options options - */ - constructor(options) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.compilation.tap( - PLUGIN_NAME, - (compilation, { normalModuleFactory }) => { - compilation.dependencyFactories.set( - WebAssemblyImportDependency, - normalModuleFactory - ); - - compilation.dependencyFactories.set( - WebAssemblyExportImportedDependency, - normalModuleFactory - ); - - normalModuleFactory.hooks.createParser - .for(WEBASSEMBLY_MODULE_TYPE_SYNC) - .tap(PLUGIN_NAME, () => { - const WebAssemblyParser = getWebAssemblyParser(); - - return new WebAssemblyParser(); - }); - - normalModuleFactory.hooks.createGenerator - .for(WEBASSEMBLY_MODULE_TYPE_SYNC) - .tap(PLUGIN_NAME, () => { - const WebAssemblyJavascriptGenerator = - getWebAssemblyJavascriptGenerator(); - const WebAssemblyGenerator = getWebAssemblyGenerator(); - - return Generator.byType({ - javascript: new WebAssemblyJavascriptGenerator(), - webassembly: new WebAssemblyGenerator(this.options) - }); - }); - - compilation.hooks.renderManifest.tap(PLUGIN_NAME, (result, options) => { - const { chunkGraph } = compilation; - const { chunk, outputOptions, codeGenerationResults } = options; - - for (const module of chunkGraph.getOrderedChunkModulesIterable( - chunk, - compareModulesByIdentifier - )) { - if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) { - const filenameTemplate = - /** @type {NonNullable} */ - (outputOptions.webassemblyModuleFilename); - - result.push({ - render: () => - codeGenerationResults.getSource( - module, - chunk.runtime, - "webassembly" - ), - filenameTemplate, - pathOptions: { - module, - runtime: chunk.runtime, - chunkGraph - }, - auxiliary: true, - identifier: `webassemblyModule${chunkGraph.getModuleId( - module - )}`, - hash: chunkGraph.getModuleHash(module, chunk.runtime) - }); - } - } - - return result; - }); - - compilation.hooks.afterChunks.tap(PLUGIN_NAME, () => { - const chunkGraph = compilation.chunkGraph; - const initialWasmModules = new Set(); - for (const chunk of compilation.chunks) { - if (chunk.canBeInitial()) { - for (const module of chunkGraph.getChunkModulesIterable(chunk)) { - if (module.type === WEBASSEMBLY_MODULE_TYPE_SYNC) { - initialWasmModules.add(module); - } - } - } - } - for (const module of initialWasmModules) { - compilation.errors.push( - new WebAssemblyInInitialChunkError( - module, - compilation.moduleGraph, - compilation.chunkGraph, - compilation.requestShortener - ) - ); - } - }); - } - ); - } -} - -module.exports = WebAssemblyModulesPlugin; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyParser.js b/webpack-lib/lib/wasm-sync/WebAssemblyParser.js deleted file mode 100644 index 72210b88aba..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyParser.js +++ /dev/null @@ -1,206 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const t = require("@webassemblyjs/ast"); -const { moduleContextFromModuleAST } = require("@webassemblyjs/ast"); -const { decode } = require("@webassemblyjs/wasm-parser"); -const Parser = require("../Parser"); -const StaticExportsDependency = require("../dependencies/StaticExportsDependency"); -const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); - -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../Module").BuildInfo} BuildInfo */ -/** @typedef {import("../Module").BuildMeta} BuildMeta */ -/** @typedef {import("../Parser").ParserState} ParserState */ -/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */ - -const JS_COMPAT_TYPES = new Set(["i32", "i64", "f32", "f64", "externref"]); - -/** - * @param {t.Signature} signature the func signature - * @returns {null | string} the type incompatible with js types - */ -const getJsIncompatibleType = signature => { - for (const param of signature.params) { - if (!JS_COMPAT_TYPES.has(param.valtype)) { - return `${param.valtype} as parameter`; - } - } - for (const type of signature.results) { - if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; - } - return null; -}; - -/** - * TODO why are there two different Signature types? - * @param {t.FuncSignature} signature the func signature - * @returns {null | string} the type incompatible with js types - */ -const getJsIncompatibleTypeOfFuncSignature = signature => { - for (const param of signature.args) { - if (!JS_COMPAT_TYPES.has(param)) { - return `${param} as parameter`; - } - } - for (const type of signature.result) { - if (!JS_COMPAT_TYPES.has(type)) return `${type} as result`; - } - return null; -}; - -const decoderOpts = { - ignoreCodeSection: true, - ignoreDataSection: true, - - // this will avoid having to lookup with identifiers in the ModuleContext - ignoreCustomNameSection: true -}; - -class WebAssemblyParser extends Parser { - /** - * @param {{}=} options parser options - */ - constructor(options) { - super(); - this.hooks = Object.freeze({}); - this.options = options; - } - - /** - * @param {string | Buffer | PreparsedAst} source the source to parse - * @param {ParserState} state the parser state - * @returns {ParserState} the parser state - */ - parse(source, state) { - if (!Buffer.isBuffer(source)) { - throw new Error("WebAssemblyParser input must be a Buffer"); - } - - // flag it as ESM - /** @type {BuildInfo} */ - (state.module.buildInfo).strict = true; - /** @type {BuildMeta} */ - (state.module.buildMeta).exportsType = "namespace"; - - // parse it - const program = decode(source, decoderOpts); - const module = program.body[0]; - - const moduleContext = moduleContextFromModuleAST(module); - - // extract imports and exports - /** @type {string[]} */ - const exports = []; - const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta); - /** @type {Record | undefined} */ - let jsIncompatibleExports = (buildMeta.jsIncompatibleExports = undefined); - - /** @type {TODO[]} */ - const importedGlobals = []; - t.traverse(module, { - ModuleExport({ node }) { - const descriptor = node.descr; - - if (descriptor.exportType === "Func") { - const funcIdx = descriptor.id.value; - - /** @type {t.FuncSignature} */ - const funcSignature = moduleContext.getFunction(funcIdx); - - const incompatibleType = - getJsIncompatibleTypeOfFuncSignature(funcSignature); - - if (incompatibleType) { - if (jsIncompatibleExports === undefined) { - jsIncompatibleExports = - /** @type {BuildMeta} */ - (state.module.buildMeta).jsIncompatibleExports = {}; - } - jsIncompatibleExports[node.name] = incompatibleType; - } - } - - exports.push(node.name); - - if (node.descr && node.descr.exportType === "Global") { - const refNode = - importedGlobals[/** @type {TODO} */ (node.descr.id.value)]; - if (refNode) { - const dep = new WebAssemblyExportImportedDependency( - node.name, - refNode.module, - refNode.name, - refNode.descr.valtype - ); - - state.module.addDependency(dep); - } - } - }, - - Global({ node }) { - const init = node.init[0]; - - let importNode = null; - - if (init.id === "get_global") { - const globalIdx = init.args[0].value; - - if (globalIdx < importedGlobals.length) { - importNode = importedGlobals[globalIdx]; - } - } - - importedGlobals.push(importNode); - }, - - ModuleImport({ node }) { - /** @type {false | string} */ - let onlyDirectImport = false; - - if (t.isMemory(node.descr) === true) { - onlyDirectImport = "Memory"; - } else if (t.isTable(node.descr) === true) { - onlyDirectImport = "Table"; - } else if (t.isFuncImportDescr(node.descr) === true) { - const incompatibleType = getJsIncompatibleType( - /** @type {t.Signature} */ (node.descr.signature) - ); - if (incompatibleType) { - onlyDirectImport = `Non-JS-compatible Func Signature (${incompatibleType})`; - } - } else if (t.isGlobalType(node.descr) === true) { - const type = /** @type {string} */ (node.descr.valtype); - if (!JS_COMPAT_TYPES.has(type)) { - onlyDirectImport = `Non-JS-compatible Global Type (${type})`; - } - } - - const dep = new WebAssemblyImportDependency( - node.module, - node.name, - node.descr, - onlyDirectImport - ); - - state.module.addDependency(dep); - - if (t.isGlobalType(node.descr)) { - importedGlobals.push(node); - } - } - }); - - state.module.addDependency(new StaticExportsDependency(exports, false)); - - return state; - } -} - -module.exports = WebAssemblyParser; diff --git a/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js b/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js deleted file mode 100644 index a67f3557268..00000000000 --- a/webpack-lib/lib/wasm-sync/WebAssemblyUtils.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const Template = require("../Template"); -const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency"); - -/** @typedef {import("../Module")} Module */ -/** @typedef {import("../ModuleGraph")} ModuleGraph */ - -/** - * @typedef {object} UsedWasmDependency - * @property {WebAssemblyImportDependency} dependency the dependency - * @property {string} name the export name - * @property {string} module the module name - */ - -const MANGLED_MODULE = "a"; - -/** - * @param {ModuleGraph} moduleGraph the module graph - * @param {Module} module the module - * @param {boolean | undefined} mangle mangle module and export names - * @returns {UsedWasmDependency[]} used dependencies and (mangled) name - */ -const getUsedDependencies = (moduleGraph, module, mangle) => { - /** @type {UsedWasmDependency[]} */ - const array = []; - let importIndex = 0; - for (const dep of module.dependencies) { - if (dep instanceof WebAssemblyImportDependency) { - if ( - dep.description.type === "GlobalType" || - moduleGraph.getModule(dep) === null - ) { - continue; - } - - const exportName = dep.name; - // TODO add the following 3 lines when removing of ModuleExport is possible - // const importedModule = moduleGraph.getModule(dep); - // const usedName = importedModule && moduleGraph.getExportsInfo(importedModule).getUsedName(exportName, runtime); - // if (usedName !== false) { - if (mangle) { - array.push({ - dependency: dep, - name: Template.numberToIdentifier(importIndex++), - module: MANGLED_MODULE - }); - } else { - array.push({ - dependency: dep, - name: exportName, - module: dep.request - }); - } - } - } - return array; -}; - -module.exports.getUsedDependencies = getUsedDependencies; -module.exports.MANGLED_MODULE = MANGLED_MODULE; diff --git a/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js b/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js deleted file mode 100644 index 250dd0a2d71..00000000000 --- a/webpack-lib/lib/wasm/EnableWasmLoadingPlugin.js +++ /dev/null @@ -1,134 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ -/** @typedef {import("../../declarations/WebpackOptions").WasmLoadingType} WasmLoadingType */ -/** @typedef {import("../Compiler")} Compiler */ - -/** @type {WeakMap>} */ -const enabledTypes = new WeakMap(); - -/** - * @param {Compiler} compiler compiler instance - * @returns {Set} enabled types - */ -const getEnabledTypes = compiler => { - let set = enabledTypes.get(compiler); - if (set === undefined) { - set = new Set(); - enabledTypes.set(compiler, set); - } - return set; -}; - -class EnableWasmLoadingPlugin { - /** - * @param {WasmLoadingType} type library type that should be available - */ - constructor(type) { - this.type = type; - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {WasmLoadingType} type type of library - * @returns {void} - */ - static setEnabled(compiler, type) { - getEnabledTypes(compiler).add(type); - } - - /** - * @param {Compiler} compiler the compiler instance - * @param {WasmLoadingType} type type of library - * @returns {void} - */ - static checkEnabled(compiler, type) { - if (!getEnabledTypes(compiler).has(type)) { - throw new Error( - `Library type "${type}" is not enabled. ` + - "EnableWasmLoadingPlugin need to be used to enable this type of wasm loading. " + - 'This usually happens through the "output.enabledWasmLoadingTypes" option. ' + - 'If you are using a function as entry which sets "wasmLoading", you need to add all potential library types to "output.enabledWasmLoadingTypes". ' + - `These types are enabled: ${Array.from( - getEnabledTypes(compiler) - ).join(", ")}` - ); - } - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - const { type } = this; - - // Only enable once - const enabled = getEnabledTypes(compiler); - if (enabled.has(type)) return; - enabled.add(type); - - if (typeof type === "string") { - switch (type) { - case "fetch": { - if (compiler.options.experiments.syncWebAssembly) { - // TODO webpack 6 remove FetchCompileWasmPlugin - const FetchCompileWasmPlugin = require("../web/FetchCompileWasmPlugin"); - new FetchCompileWasmPlugin({ - mangleImports: compiler.options.optimization.mangleWasmImports - }).apply(compiler); - } - - if (compiler.options.experiments.asyncWebAssembly) { - const FetchCompileAsyncWasmPlugin = require("../web/FetchCompileAsyncWasmPlugin"); - new FetchCompileAsyncWasmPlugin().apply(compiler); - } - - break; - } - case "async-node": { - if (compiler.options.experiments.syncWebAssembly) { - // TODO webpack 6 remove ReadFileCompileWasmPlugin - const ReadFileCompileWasmPlugin = require("../node/ReadFileCompileWasmPlugin"); - new ReadFileCompileWasmPlugin({ - mangleImports: compiler.options.optimization.mangleWasmImports, - import: - compiler.options.output.environment.module && - compiler.options.output.environment.dynamicImport - }).apply(compiler); - } - - if (compiler.options.experiments.asyncWebAssembly) { - const ReadFileCompileAsyncWasmPlugin = require("../node/ReadFileCompileAsyncWasmPlugin"); - new ReadFileCompileAsyncWasmPlugin({ - import: - compiler.options.output.environment.module && - compiler.options.output.environment.dynamicImport - }).apply(compiler); - } - - break; - } - case "universal": { - const UniversalCompileAsyncWasmPlugin = require("../wasm-async/UniversalCompileAsyncWasmPlugin"); - new UniversalCompileAsyncWasmPlugin().apply(compiler); - break; - } - default: - throw new Error(`Unsupported wasm loading type ${type}. -Plugins which provide custom wasm loading types must call EnableWasmLoadingPlugin.setEnabled(compiler, type) to disable this error.`); - } - } else { - // TODO support plugin instances here - // apply them to the compiler - } - } -} - -module.exports = EnableWasmLoadingPlugin; diff --git a/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js b/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js deleted file mode 100644 index dca39338c2b..00000000000 --- a/webpack-lib/lib/web/FetchCompileAsyncWasmPlugin.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { WEBASSEMBLY_MODULE_TYPE_ASYNC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const AsyncWasmLoadingRuntimeModule = require("../wasm-async/AsyncWasmLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -const PLUGIN_NAME = "FetchCompileAsyncWasmPlugin"; - -class FetchCompileAsyncWasmPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const globalWasmLoading = compilation.outputOptions.wasmLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const wasmLoading = - options && options.wasmLoading !== undefined - ? options.wasmLoading - : globalWasmLoading; - return wasmLoading === "fetch"; - }; - /** - * @param {string} path path to the wasm file - * @returns {string} code to load the wasm file - */ - const generateLoadBinaryCode = path => - `fetch(${RuntimeGlobals.publicPath} + ${path})`; - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.instantiateWasm) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => m.type === WEBASSEMBLY_MODULE_TYPE_ASYNC - ) - ) { - return; - } - set.add(RuntimeGlobals.publicPath); - compilation.addRuntimeModule( - chunk, - new AsyncWasmLoadingRuntimeModule({ - generateLoadBinaryCode, - supportsStreaming: true - }) - ); - }); - }); - } -} - -module.exports = FetchCompileAsyncWasmPlugin; diff --git a/webpack-lib/lib/web/FetchCompileWasmPlugin.js b/webpack-lib/lib/web/FetchCompileWasmPlugin.js deleted file mode 100644 index a4b5dbcf79d..00000000000 --- a/webpack-lib/lib/web/FetchCompileWasmPlugin.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const { WEBASSEMBLY_MODULE_TYPE_SYNC } = require("../ModuleTypeConstants"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const WasmChunkLoadingRuntimeModule = require("../wasm-sync/WasmChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -/** - * @typedef {object} FetchCompileWasmPluginOptions - * @property {boolean} [mangleImports] mangle imports - */ - -// TODO webpack 6 remove - -const PLUGIN_NAME = "FetchCompileWasmPlugin"; - -class FetchCompileWasmPlugin { - /** - * @param {FetchCompileWasmPluginOptions} [options] options - */ - constructor(options = {}) { - this.options = options; - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap(PLUGIN_NAME, compilation => { - const globalWasmLoading = compilation.outputOptions.wasmLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const wasmLoading = - options && options.wasmLoading !== undefined - ? options.wasmLoading - : globalWasmLoading; - return wasmLoading === "fetch"; - }; - /** - * @param {string} path path to the wasm file - * @returns {string} code to load the wasm file - */ - const generateLoadBinaryCode = path => - `fetch(${RuntimeGlobals.publicPath} + ${path})`; - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap(PLUGIN_NAME, (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; - if ( - !chunkGraph.hasModuleInGraph( - chunk, - m => m.type === WEBASSEMBLY_MODULE_TYPE_SYNC - ) - ) { - return; - } - set.add(RuntimeGlobals.moduleCache); - set.add(RuntimeGlobals.publicPath); - compilation.addRuntimeModule( - chunk, - new WasmChunkLoadingRuntimeModule({ - generateLoadBinaryCode, - supportsStreaming: true, - mangleImports: this.options.mangleImports, - runtimeRequirements: set - }) - ); - }); - }); - } -} - -module.exports = FetchCompileWasmPlugin; diff --git a/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js b/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js deleted file mode 100644 index 57b75f81f40..00000000000 --- a/webpack-lib/lib/web/JsonpChunkLoadingPlugin.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const JsonpChunkLoadingRuntimeModule = require("./JsonpChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -class JsonpChunkLoadingPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.hooks.thisCompilation.tap( - "JsonpChunkLoadingPlugin", - compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === "jsonp"; - }; - const onceForChunkSet = new WeakSet(); - /** - * @param {Chunk} chunk chunk - * @param {Set} set runtime requirements - */ - const handler = (chunk, set) => { - if (onceForChunkSet.has(chunk)) return; - onceForChunkSet.add(chunk); - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.hasOwnProperty); - compilation.addRuntimeModule( - chunk, - new JsonpChunkLoadingRuntimeModule(set) - ); - }; - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("JsonpChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("JsonpChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("JsonpChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.baseURI) - .tap("JsonpChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.onChunksLoaded) - .tap("JsonpChunkLoadingPlugin", handler); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("JsonpChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.loadScript); - set.add(RuntimeGlobals.getChunkScriptFilename); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("JsonpChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.loadScript); - set.add(RuntimeGlobals.getChunkUpdateScriptFilename); - set.add(RuntimeGlobals.moduleCache); - set.add(RuntimeGlobals.hmrModuleData); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("JsonpChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getUpdateManifestFilename); - }); - } - ); - } -} - -module.exports = JsonpChunkLoadingPlugin; diff --git a/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js b/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js deleted file mode 100644 index efc4ac0e463..00000000000 --- a/webpack-lib/lib/web/JsonpChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,470 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const { SyncWaterfallHook } = require("tapable"); -const Compilation = require("../Compilation"); -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const chunkHasJs = require("../javascript/JavascriptModulesPlugin").chunkHasJs; -const { getInitialChunkIds } = require("../javascript/StartupHelpers"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -/** - * @typedef {object} JsonpCompilationPluginHooks - * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload - * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch - */ - -/** @type {WeakMap} */ -const compilationHooksMap = new WeakMap(); - -class JsonpChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {Compilation} compilation the compilation - * @returns {JsonpCompilationPluginHooks} 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 = { - linkPreload: new SyncWaterfallHook(["source", "chunk"]), - linkPrefetch: new SyncWaterfallHook(["source", "chunk"]) - }; - compilationHooksMap.set(compilation, hooks); - } - return hooks; - } - - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - */ - constructor(runtimeRequirements) { - super("jsonp chunk loading", RuntimeModule.STAGE_ATTACH); - this._runtimeRequirements = runtimeRequirements; - } - - /** - * @private - * @param {Chunk} chunk chunk - * @returns {string} generated code - */ - _generateBaseUri(chunk) { - const options = chunk.getEntryOptions(); - if (options && options.baseUri) { - return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; - } - return `${RuntimeGlobals.baseURI} = document.baseURI || self.location.href;`; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const { - runtimeTemplate, - outputOptions: { - chunkLoadingGlobal, - hotUpdateGlobal, - crossOriginLoading, - scriptType - } - } = compilation; - const globalObject = runtimeTemplate.globalObject; - const { linkPreload, linkPrefetch } = - JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation); - const fn = RuntimeGlobals.ensureChunkHandlers; - const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI); - const withLoading = this._runtimeRequirements.has( - RuntimeGlobals.ensureChunkHandlers - ); - const withCallback = this._runtimeRequirements.has( - RuntimeGlobals.chunkCallback - ); - const withOnChunkLoad = this._runtimeRequirements.has( - RuntimeGlobals.onChunksLoaded - ); - const withHmr = this._runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const withHmrManifest = this._runtimeRequirements.has( - RuntimeGlobals.hmrDownloadManifest - ); - const withFetchPriority = this._runtimeRequirements.has( - RuntimeGlobals.hasFetchPriority - ); - const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( - chunkLoadingGlobal - )}]`; - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const withPrefetch = - this._runtimeRequirements.has(RuntimeGlobals.prefetchChunkHandlers) && - chunk.hasChildByOrder(chunkGraph, "prefetch", true, chunkHasJs); - const withPreload = - this._runtimeRequirements.has(RuntimeGlobals.preloadChunkHandlers) && - chunk.hasChildByOrder(chunkGraph, "preload", true, chunkHasJs); - const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs); - const hasJsMatcher = compileBooleanMatcher(conditionMap); - const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_jsonp` - : undefined; - - return Template.asString([ - withBaseURI ? this._generateBaseUri(chunk) : "// 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", - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join( - ",\n" - ) - ), - "};", - "", - withLoading - ? Template.asString([ - `${fn}.j = ${runtimeTemplate.basicFunction( - `chunkId, promises${withFetchPriority ? ", fetchPriority" : ""}`, - hasJsMatcher !== false - ? Template.indent([ - "// JSONP chunk loading for javascript", - `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`, - 'if(installedChunkData !== 0) { // 0 means "already installed".', - Template.indent([ - "", - '// a Promise means "currently loading".', - "if(installedChunkData) {", - Template.indent([ - "promises.push(installedChunkData[2]);" - ]), - "} else {", - Template.indent([ - hasJsMatcher === true - ? "if(true) { // all chunks have JS" - : `if(${hasJsMatcher("chunkId")}) {`, - Template.indent([ - "// setup Promise in chunk cache", - `var promise = new Promise(${runtimeTemplate.expressionFunction( - "installedChunkData = installedChunks[chunkId] = [resolve, reject]", - "resolve, reject" - )});`, - "promises.push(installedChunkData[2] = promise);", - "", - "// start chunk loading", - `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, - "// create error before stack unwound to get useful stacktrace later", - "var error = new Error();", - `var loadingEnded = ${runtimeTemplate.basicFunction( - "event", - [ - `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, - Template.indent([ - "installedChunkData = installedChunks[chunkId];", - "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", - "if(installedChunkData) {", - Template.indent([ - "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", - "var 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);" - ]), - "}" - ]), - "}" - ] - )};`, - `${ - RuntimeGlobals.loadScript - }(url, loadingEnded, "chunk-" + chunkId, chunkId${ - withFetchPriority ? ", fetchPriority" : "" - });` - ]), - hasJsMatcher === true - ? "}" - : "} else installedChunks[chunkId] = 0;" - ]), - "}" - ]), - "}" - ]) - : Template.indent(["installedChunks[chunkId] = 0;"]) - )};` - ]) - : "// no chunk on demand loading", - "", - withPrefetch && hasJsMatcher !== false - ? `${ - RuntimeGlobals.prefetchChunkHandlers - }.j = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - linkPrefetch.call( - Template.asString([ - "var link = document.createElement('link');", - crossOriginLoading - ? `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - : "", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - 'link.rel = "prefetch";', - 'link.as = "script";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);` - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no prefetching", - "", - withPreload && hasJsMatcher !== false - ? `${ - RuntimeGlobals.preloadChunkHandlers - }.j = ${runtimeTemplate.basicFunction("chunkId", [ - `if((!${ - RuntimeGlobals.hasOwnProperty - }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${ - hasJsMatcher === true ? "true" : hasJsMatcher("chunkId") - }) {`, - Template.indent([ - "installedChunks[chunkId] = null;", - linkPreload.call( - Template.asString([ - "var link = document.createElement('link');", - scriptType && scriptType !== "module" - ? `link.type = ${JSON.stringify(scriptType)};` - : "", - "link.charset = 'utf-8';", - `if (${RuntimeGlobals.scriptNonce}) {`, - Template.indent( - `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});` - ), - "}", - scriptType === "module" - ? 'link.rel = "modulepreload";' - : 'link.rel = "preload";', - scriptType === "module" ? "" : 'link.as = "script";', - `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, - crossOriginLoading - ? crossOriginLoading === "use-credentials" - ? 'link.crossOrigin = "use-credentials";' - : Template.asString([ - "if (link.href.indexOf(window.location.origin + '/') !== 0) {", - Template.indent( - `link.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - ), - "}" - ]) - : "" - ]), - chunk - ), - "document.head.appendChild(link);" - ]), - "}" - ])};` - : "// no preloaded", - "", - withHmr - ? Template.asString([ - "var currentUpdatedModulesList;", - "var waitingUpdateResolves = {};", - "function loadUpdateChunk(chunkId, updatedModulesList) {", - Template.indent([ - "currentUpdatedModulesList = updatedModulesList;", - `return new Promise(${runtimeTemplate.basicFunction( - "resolve, reject", - [ - "waitingUpdateResolves[chunkId] = resolve;", - "// start update chunk loading", - `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`, - "// create error before stack unwound to get useful stacktrace later", - "var error = new Error();", - `var loadingEnded = ${runtimeTemplate.basicFunction("event", [ - "if(waitingUpdateResolves[chunkId]) {", - Template.indent([ - "waitingUpdateResolves[chunkId] = undefined", - "var errorType = event && (event.type === 'load' ? 'missing' : event.type);", - "var realSrc = event && event.target && event.target.src;", - "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';", - "error.name = 'ChunkLoadError';", - "error.type = errorType;", - "error.request = realSrc;", - "reject(error);" - ]), - "}" - ])};`, - `${RuntimeGlobals.loadScript}(url, loadingEnded);` - ] - )});` - ]), - "}", - "", - `${globalObject}[${JSON.stringify( - hotUpdateGlobal - )}] = ${runtimeTemplate.basicFunction( - "chunkId, moreModules, runtime", - [ - "for(var moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent([ - "currentUpdate[moduleId] = moreModules[moduleId];", - "if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);" - ]), - "}" - ]), - "}", - "if(runtime) currentUpdateRuntime.push(runtime);", - "if(waitingUpdateResolves[chunkId]) {", - Template.indent([ - "waitingUpdateResolves[chunkId]();", - "waitingUpdateResolves[chunkId] = undefined;" - ]), - "}" - ] - )};`, - "", - Template.getFunctionContent( - require("../hmr/JavascriptHotModuleReplacement.runtime.js") - ) - .replace(/\$key\$/g, "jsonp") - .replace(/\$installedChunks\$/g, "installedChunks") - .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") - .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) - .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) - .replace( - /\$ensureChunkHandlers\$/g, - RuntimeGlobals.ensureChunkHandlers - ) - .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) - .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) - .replace( - /\$hmrDownloadUpdateHandlers\$/g, - RuntimeGlobals.hmrDownloadUpdateHandlers - ) - .replace( - /\$hmrInvalidateModuleHandlers\$/g, - RuntimeGlobals.hmrInvalidateModuleHandlers - ) - ]) - : "// no HMR", - "", - withHmrManifest - ? Template.asString([ - `${ - RuntimeGlobals.hmrDownloadManifest - } = ${runtimeTemplate.basicFunction("", [ - 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");', - `return fetch(${RuntimeGlobals.publicPath} + ${ - RuntimeGlobals.getUpdateManifestFilename - }()).then(${runtimeTemplate.basicFunction("response", [ - "if(response.status === 404) return; // no update available", - 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);', - "return response.json();" - ])});` - ])};` - ]) - : "// no HMR manifest", - "", - withOnChunkLoad - ? `${ - RuntimeGlobals.onChunksLoaded - }.j = ${runtimeTemplate.returningFunction( - "installedChunks[chunkId] === 0", - "chunkId" - )};` - : "// no on chunks loaded", - "", - withCallback || withLoading - ? Template.asString([ - "// install a JSONP callback for chunk loading", - `var webpackJsonpCallback = ${runtimeTemplate.basicFunction( - "parentChunkLoadingFunction, data", - [ - runtimeTemplate.destructureArray( - ["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(${runtimeTemplate.returningFunction( - "installedChunks[id] !== 0", - "id" - )})) {`, - Template.indent([ - "for(moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent( - `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` - ), - "}" - ]), - "}", - `if(runtime) var result = runtime(${RuntimeGlobals.require});` - ]), - "}", - "if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);", - "for(;i < chunkIds.length; i++) {", - Template.indent([ - "chunkId = chunkIds[i];", - `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`, - Template.indent("installedChunks[chunkId][0]();"), - "}", - "installedChunks[chunkId] = 0;" - ]), - "}", - withOnChunkLoad - ? `return ${RuntimeGlobals.onChunksLoaded}(result);` - : "" - ] - )}`, - "", - `var chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, - "chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));", - "chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));" - ]) - : "// no jsonp function" - ]); - } -} - -module.exports = JsonpChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/web/JsonpTemplatePlugin.js b/webpack-lib/lib/web/JsonpTemplatePlugin.js deleted file mode 100644 index eeed68a28ba..00000000000 --- a/webpack-lib/lib/web/JsonpTemplatePlugin.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ArrayPushCallbackChunkFormatPlugin = require("../javascript/ArrayPushCallbackChunkFormatPlugin"); -const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); -const JsonpChunkLoadingRuntimeModule = require("./JsonpChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Compiler")} Compiler */ - -class JsonpTemplatePlugin { - /** - * @deprecated use JsonpChunkLoadingRuntimeModule.getCompilationHooks instead - * @param {Compilation} compilation the compilation - * @returns {JsonpChunkLoadingRuntimeModule.JsonpCompilationPluginHooks} hooks - */ - static getCompilationHooks(compilation) { - return JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation); - } - - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.options.output.chunkLoading = "jsonp"; - new ArrayPushCallbackChunkFormatPlugin().apply(compiler); - new EnableChunkLoadingPlugin("jsonp").apply(compiler); - } -} - -module.exports = JsonpTemplatePlugin; diff --git a/webpack-lib/lib/webpack.js b/webpack-lib/lib/webpack.js deleted file mode 100644 index 7396300a0b9..00000000000 --- a/webpack-lib/lib/webpack.js +++ /dev/null @@ -1,195 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const util = require("util"); -const webpackOptionsSchemaCheck = require("../schemas/WebpackOptions.check.js"); -const webpackOptionsSchema = require("../schemas/WebpackOptions.json"); -const Compiler = require("./Compiler"); -const MultiCompiler = require("./MultiCompiler"); -const WebpackOptionsApply = require("./WebpackOptionsApply"); -const { - applyWebpackOptionsDefaults, - applyWebpackOptionsBaseDefaults -} = require("./config/defaults"); -const { getNormalizedWebpackOptions } = require("./config/normalization"); -const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin"); -const memoize = require("./util/memoize"); - -/** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ -/** @typedef {import("../declarations/WebpackOptions").WebpackPluginFunction} WebpackPluginFunction */ -/** @typedef {import("./Compiler").WatchOptions} WatchOptions */ -/** @typedef {import("./MultiCompiler").MultiCompilerOptions} MultiCompilerOptions */ -/** @typedef {import("./MultiStats")} MultiStats */ -/** @typedef {import("./Stats")} Stats */ - -const getValidateSchema = memoize(() => require("./validateSchema")); - -/** - * @template T - * @callback Callback - * @param {Error | null} err - * @param {T=} stats - * @returns {void} - */ - -/** - * @param {ReadonlyArray} childOptions options array - * @param {MultiCompilerOptions} options options - * @returns {MultiCompiler} a multi-compiler - */ -const createMultiCompiler = (childOptions, options) => { - const compilers = childOptions.map((options, index) => - createCompiler(options, index) - ); - const compiler = new MultiCompiler(compilers, options); - for (const childCompiler of compilers) { - if (childCompiler.options.dependencies) { - compiler.setDependencies( - childCompiler, - childCompiler.options.dependencies - ); - } - } - return compiler; -}; - -/** - * @param {WebpackOptions} rawOptions options object - * @param {number} [compilerIndex] index of compiler - * @returns {Compiler} a compiler - */ -const createCompiler = (rawOptions, compilerIndex) => { - const options = getNormalizedWebpackOptions(rawOptions); - applyWebpackOptionsBaseDefaults(options); - const compiler = new Compiler( - /** @type {string} */ (options.context), - options - ); - new NodeEnvironmentPlugin({ - infrastructureLogging: options.infrastructureLogging - }).apply(compiler); - if (Array.isArray(options.plugins)) { - for (const plugin of options.plugins) { - if (typeof plugin === "function") { - /** @type {WebpackPluginFunction} */ - (plugin).call(compiler, compiler); - } else if (plugin) { - plugin.apply(compiler); - } - } - } - const resolvedDefaultOptions = applyWebpackOptionsDefaults( - options, - compilerIndex - ); - if (resolvedDefaultOptions.platform) { - compiler.platform = resolvedDefaultOptions.platform; - } - compiler.hooks.environment.call(); - compiler.hooks.afterEnvironment.call(); - new WebpackOptionsApply().process(options, compiler); - compiler.hooks.initialize.call(); - return compiler; -}; - -/** - * @callback WebpackFunctionSingle - * @param {WebpackOptions} options options object - * @param {Callback=} callback callback - * @returns {Compiler} the compiler object - */ - -/** - * @callback WebpackFunctionMulti - * @param {ReadonlyArray & MultiCompilerOptions} options options objects - * @param {Callback=} callback callback - * @returns {MultiCompiler} the multi compiler object - */ - -/** - * @template T - * @param {Array | T} options options - * @returns {Array} array of options - */ -const asArray = options => - Array.isArray(options) ? Array.from(options) : [options]; - -const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ ( - /** - * @param {WebpackOptions | (ReadonlyArray & MultiCompilerOptions)} options options - * @param {Callback & Callback=} callback callback - * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler - */ - (options, callback) => { - const create = () => { - if (!asArray(options).every(webpackOptionsSchemaCheck)) { - getValidateSchema()(webpackOptionsSchema, options); - util.deprecate( - () => {}, - "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.", - "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID" - )(); - } - /** @type {MultiCompiler|Compiler} */ - let compiler; - /** @type {boolean | undefined} */ - let watch = false; - /** @type {WatchOptions|WatchOptions[]} */ - let watchOptions; - if (Array.isArray(options)) { - /** @type {MultiCompiler} */ - compiler = createMultiCompiler( - options, - /** @type {MultiCompilerOptions} */ (options) - ); - watch = options.some(options => options.watch); - watchOptions = options.map(options => options.watchOptions || {}); - } else { - const webpackOptions = /** @type {WebpackOptions} */ (options); - /** @type {Compiler} */ - compiler = createCompiler(webpackOptions); - watch = webpackOptions.watch; - watchOptions = webpackOptions.watchOptions || {}; - } - return { compiler, watch, watchOptions }; - }; - if (callback) { - try { - const { compiler, watch, watchOptions } = create(); - if (watch) { - compiler.watch(watchOptions, callback); - } else { - compiler.run((err, stats) => { - compiler.close(err2 => { - callback( - err || err2, - /** @type {options extends WebpackOptions ? Stats : MultiStats} */ - (stats) - ); - }); - }); - } - return compiler; - } catch (err) { - process.nextTick(() => callback(/** @type {Error} */ (err))); - return null; - } - } else { - const { compiler, watch } = create(); - if (watch) { - util.deprecate( - () => {}, - "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.", - "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK" - )(); - } - return compiler; - } - } -); - -module.exports = webpack; diff --git a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js deleted file mode 100644 index ddb6cf51a7d..00000000000 --- a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingPlugin.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const StartupChunkDependenciesPlugin = require("../runtime/StartupChunkDependenciesPlugin"); -const ImportScriptsChunkLoadingRuntimeModule = require("./ImportScriptsChunkLoadingRuntimeModule"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../Compiler")} Compiler */ - -class ImportScriptsChunkLoadingPlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - new StartupChunkDependenciesPlugin({ - chunkLoading: "import-scripts", - asyncChunkLoading: true - }).apply(compiler); - compiler.hooks.thisCompilation.tap( - "ImportScriptsChunkLoadingPlugin", - compilation => { - const globalChunkLoading = compilation.outputOptions.chunkLoading; - /** - * @param {Chunk} chunk chunk - * @returns {boolean} true, if wasm loading is enabled for the chunk - */ - const isEnabledForChunk = chunk => { - const options = chunk.getEntryOptions(); - const chunkLoading = - options && options.chunkLoading !== undefined - ? options.chunkLoading - : globalChunkLoading; - return chunkLoading === "import-scripts"; - }; - const onceForChunkSet = new WeakSet(); - /** - * @param {Chunk} chunk chunk - * @param {Set} set runtime requirements - */ - const handler = (chunk, set) => { - if (onceForChunkSet.has(chunk)) return; - onceForChunkSet.add(chunk); - if (!isEnabledForChunk(chunk)) return; - const withCreateScriptUrl = Boolean( - compilation.outputOptions.trustedTypes - ); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - set.add(RuntimeGlobals.hasOwnProperty); - if (withCreateScriptUrl) { - set.add(RuntimeGlobals.createScriptUrl); - } - compilation.addRuntimeModule( - chunk, - new ImportScriptsChunkLoadingRuntimeModule(set, withCreateScriptUrl) - ); - }; - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("ImportScriptsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("ImportScriptsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("ImportScriptsChunkLoadingPlugin", handler); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.baseURI) - .tap("ImportScriptsChunkLoadingPlugin", handler); - - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.ensureChunkHandlers) - .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getChunkScriptFilename); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadUpdateHandlers) - .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getChunkUpdateScriptFilename); - set.add(RuntimeGlobals.moduleCache); - set.add(RuntimeGlobals.hmrModuleData); - set.add(RuntimeGlobals.moduleFactoriesAddOnly); - }); - compilation.hooks.runtimeRequirementInTree - .for(RuntimeGlobals.hmrDownloadManifest) - .tap("ImportScriptsChunkLoadingPlugin", (chunk, set) => { - if (!isEnabledForChunk(chunk)) return; - set.add(RuntimeGlobals.publicPath); - set.add(RuntimeGlobals.getUpdateManifestFilename); - }); - } - ); - } -} -module.exports = ImportScriptsChunkLoadingPlugin; diff --git a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js b/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js deleted file mode 100644 index 7d2ae3a3d61..00000000000 --- a/webpack-lib/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php -*/ - -"use strict"; - -const RuntimeGlobals = require("../RuntimeGlobals"); -const RuntimeModule = require("../RuntimeModule"); -const Template = require("../Template"); -const { - getChunkFilenameTemplate, - chunkHasJs -} = require("../javascript/JavascriptModulesPlugin"); -const { getInitialChunkIds } = require("../javascript/StartupHelpers"); -const compileBooleanMatcher = require("../util/compileBooleanMatcher"); -const { getUndoPath } = require("../util/identifier"); - -/** @typedef {import("../Chunk")} Chunk */ -/** @typedef {import("../ChunkGraph")} ChunkGraph */ -/** @typedef {import("../Compilation")} Compilation */ -/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */ - -class ImportScriptsChunkLoadingRuntimeModule extends RuntimeModule { - /** - * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements - * @param {boolean} withCreateScriptUrl with createScriptUrl support - */ - constructor(runtimeRequirements, withCreateScriptUrl) { - super("importScripts chunk loading", RuntimeModule.STAGE_ATTACH); - this.runtimeRequirements = runtimeRequirements; - this._withCreateScriptUrl = withCreateScriptUrl; - } - - /** - * @private - * @param {Chunk} chunk chunk - * @returns {string} generated code - */ - _generateBaseUri(chunk) { - const options = chunk.getEntryOptions(); - if (options && options.baseUri) { - return `${RuntimeGlobals.baseURI} = ${JSON.stringify(options.baseUri)};`; - } - const compilation = /** @type {Compilation} */ (this.compilation); - const outputName = compilation.getPath( - getChunkFilenameTemplate(chunk, compilation.outputOptions), - { - chunk, - contentHashType: "javascript" - } - ); - const rootOutputDir = getUndoPath( - outputName, - /** @type {string} */ (compilation.outputOptions.path), - false - ); - return `${RuntimeGlobals.baseURI} = self.location + ${JSON.stringify( - rootOutputDir ? `/../${rootOutputDir}` : "" - )};`; - } - - /** - * @returns {string | null} runtime code - */ - generate() { - const compilation = /** @type {Compilation} */ (this.compilation); - const fn = RuntimeGlobals.ensureChunkHandlers; - const withBaseURI = this.runtimeRequirements.has(RuntimeGlobals.baseURI); - const withLoading = this.runtimeRequirements.has( - RuntimeGlobals.ensureChunkHandlers - ); - const withHmr = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadUpdateHandlers - ); - const withHmrManifest = this.runtimeRequirements.has( - RuntimeGlobals.hmrDownloadManifest - ); - const globalObject = compilation.runtimeTemplate.globalObject; - const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify( - compilation.outputOptions.chunkLoadingGlobal - )}]`; - const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); - const chunk = /** @type {Chunk} */ (this.chunk); - const hasJsMatcher = compileBooleanMatcher( - chunkGraph.getChunkConditionMap(chunk, chunkHasJs) - ); - const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs); - - const stateExpression = withHmr - ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_importScripts` - : undefined; - const runtimeTemplate = compilation.runtimeTemplate; - const { _withCreateScriptUrl: withCreateScriptUrl } = this; - - return Template.asString([ - withBaseURI ? this._generateBaseUri(chunk) : "// no baseURI", - "", - "// object to store loaded chunks", - '// "1" means "already loaded"', - `var installedChunks = ${ - stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" - }{`, - Template.indent( - Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 1`).join( - ",\n" - ) - ), - "};", - "", - withLoading - ? Template.asString([ - "// importScripts chunk loading", - `var installChunk = ${runtimeTemplate.basicFunction("data", [ - runtimeTemplate.destructureArray( - ["chunkIds", "moreModules", "runtime"], - "data" - ), - "for(var moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent( - `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];` - ), - "}" - ]), - "}", - `if(runtime) runtime(${RuntimeGlobals.require});`, - "while(chunkIds.length)", - Template.indent("installedChunks[chunkIds.pop()] = 1;"), - "parentChunkLoadingFunction(data);" - ])};` - ]) - : "// no chunk install function needed", - withLoading - ? Template.asString([ - `${fn}.i = ${runtimeTemplate.basicFunction( - "chunkId, promises", - hasJsMatcher !== false - ? [ - '// "1" is the signal for "already loaded"', - "if(!installedChunks[chunkId]) {", - Template.indent([ - hasJsMatcher === true - ? "if(true) { // all chunks have JS" - : `if(${hasJsMatcher("chunkId")}) {`, - Template.indent( - `importScripts(${ - withCreateScriptUrl - ? `${RuntimeGlobals.createScriptUrl}(${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId))` - : `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId)` - });` - ), - "}" - ]), - "}" - ] - : "installedChunks[chunkId] = 1;" - )};`, - "", - `var chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`, - "var parentChunkLoadingFunction = chunkLoadingGlobal.push.bind(chunkLoadingGlobal);", - "chunkLoadingGlobal.push = installChunk;" - ]) - : "// no chunk loading", - "", - withHmr - ? Template.asString([ - "function loadUpdateChunk(chunkId, updatedModulesList) {", - Template.indent([ - "var success = false;", - `${globalObject}[${JSON.stringify( - compilation.outputOptions.hotUpdateGlobal - )}] = ${runtimeTemplate.basicFunction("_, moreModules, runtime", [ - "for(var moduleId in moreModules) {", - Template.indent([ - `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`, - Template.indent([ - "currentUpdate[moduleId] = moreModules[moduleId];", - "if(updatedModulesList) updatedModulesList.push(moduleId);" - ]), - "}" - ]), - "}", - "if(runtime) currentUpdateRuntime.push(runtime);", - "success = true;" - ])};`, - "// start update chunk loading", - `importScripts(${ - withCreateScriptUrl - ? `${RuntimeGlobals.createScriptUrl}(${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId))` - : `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId)` - });`, - 'if(!success) throw new Error("Loading update chunk failed for unknown reason");' - ]), - "}", - "", - Template.getFunctionContent( - require("../hmr/JavascriptHotModuleReplacement.runtime.js") - ) - .replace(/\$key\$/g, "importScripts") - .replace(/\$installedChunks\$/g, "installedChunks") - .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk") - .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache) - .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories) - .replace( - /\$ensureChunkHandlers\$/g, - RuntimeGlobals.ensureChunkHandlers - ) - .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty) - .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData) - .replace( - /\$hmrDownloadUpdateHandlers\$/g, - RuntimeGlobals.hmrDownloadUpdateHandlers - ) - .replace( - /\$hmrInvalidateModuleHandlers\$/g, - RuntimeGlobals.hmrInvalidateModuleHandlers - ) - ]) - : "// no HMR", - "", - withHmrManifest - ? Template.asString([ - `${ - RuntimeGlobals.hmrDownloadManifest - } = ${runtimeTemplate.basicFunction("", [ - 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");', - `return fetch(${RuntimeGlobals.publicPath} + ${ - RuntimeGlobals.getUpdateManifestFilename - }()).then(${runtimeTemplate.basicFunction("response", [ - "if(response.status === 404) return; // no update available", - 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);', - "return response.json();" - ])});` - ])};` - ]) - : "// no HMR manifest" - ]); - } -} - -module.exports = ImportScriptsChunkLoadingRuntimeModule; diff --git a/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js b/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js deleted file mode 100644 index 382c81243e8..00000000000 --- a/webpack-lib/lib/webworker/WebWorkerTemplatePlugin.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - MIT License http://www.opensource.org/licenses/mit-license.php - Author Tobias Koppers @sokra -*/ - -"use strict"; - -const ArrayPushCallbackChunkFormatPlugin = require("../javascript/ArrayPushCallbackChunkFormatPlugin"); -const EnableChunkLoadingPlugin = require("../javascript/EnableChunkLoadingPlugin"); - -/** @typedef {import("../Compiler")} Compiler */ - -class WebWorkerTemplatePlugin { - /** - * Apply the plugin - * @param {Compiler} compiler the compiler instance - * @returns {void} - */ - apply(compiler) { - compiler.options.output.chunkLoading = "import-scripts"; - new ArrayPushCallbackChunkFormatPlugin().apply(compiler); - new EnableChunkLoadingPlugin("import-scripts").apply(compiler); - } -} -module.exports = WebWorkerTemplatePlugin; From 2a7829a763afccfb253db8732110311712c089d7 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 24 Jan 2025 13:51:46 -0800 Subject: [PATCH 08/21] refactor(enhanced): improve EmbedFederationRuntimePlugin structure and logic --- .../runtime/EmbedFederationRuntimePlugin.ts | 109 +++++++----------- 1 file changed, 42 insertions(+), 67 deletions(-) diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index ee5fe6428f3..541b93df606 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -5,11 +5,6 @@ import type { Compiler, Chunk, Compilation } from 'webpack'; import { getFederationGlobalScope } from './utils'; import ContainerEntryDependency from '../ContainerEntryDependency'; import FederationRuntimeDependency from './FederationRuntimeDependency'; -import { - federationStartup, - generateEntryStartup, - generateESMEntryStartup, -} from '../../startup/StartupHelpers'; import { ConcatSource } from 'webpack-sources'; const { RuntimeGlobals } = require( @@ -26,6 +21,10 @@ interface EmbedFederationRuntimePluginOptions { enableForAllChunks?: boolean; } +/** + * Plugin that handles embedding of Module Federation runtime code into chunks. + * It ensures proper initialization of federated modules and manages runtime requirements. + */ class EmbedFederationRuntimePlugin { private readonly options: EmbedFederationRuntimePluginOptions; private readonly processedChunks = new WeakMap(); @@ -37,11 +36,30 @@ class EmbedFederationRuntimePlugin { }; } + /** + * Determines if runtime embedding should be enabled for a given chunk + */ + private isEnabledForChunk(chunk: Chunk): boolean { + if (this.options.enableForAllChunks) return true; + if (chunk.id === 'build time chunk') return false; + return chunk.hasRuntime(); + } + + /** + * Checks if a compilation hook has already been tapped by this plugin + */ + private isHookAlreadyTapped( + taps: Array<{ name: string }>, + hookName: string, + ): boolean { + return taps.some((tap) => tap.name === hookName); + } + apply(compiler: Compiler): void { - // Check if plugin is already applied + // Prevent double application of plugin const compilationTaps = compiler.hooks.thisCompilation.taps || []; if ( - compilationTaps.find((tap) => tap.name === 'EmbedFederationRuntimePlugin') + this.isHookAlreadyTapped(compilationTaps, 'EmbedFederationRuntimePlugin') ) { return; } @@ -54,11 +72,12 @@ class EmbedFederationRuntimePlugin { compilation, ); - // Check if renderStartup hook is already tapped + // Prevent double tapping of renderStartup hook const startupTaps = renderStartup.taps || []; if ( - startupTaps.find( - (tap) => tap.name === 'MfStartupChunkDependenciesPlugin', + this.isHookAlreadyTapped( + startupTaps, + 'MfStartupChunkDependenciesPlugin', ) ) { return; @@ -67,15 +86,9 @@ class EmbedFederationRuntimePlugin { renderStartup.tap( 'MfStartupChunkDependenciesPlugin', (startupSource, lastInlinedModule, renderContext) => { - const { chunk, chunkGraph, runtimeTemplate } = renderContext; - - const isEnabledForChunk = (chunk: Chunk) => { - if (this.options.enableForAllChunks) return true; - if (chunk.id === 'build time chunk') return false; - return chunk.hasRuntime(); - }; + const { chunk, chunkGraph } = renderContext; - if (!isEnabledForChunk(chunk)) { + if (!this.isEnabledForChunk(chunk)) { return startupSource; } @@ -92,44 +105,24 @@ class EmbedFederationRuntimePlugin { return startupSource; } - // Check if we've already processed this chunk + // Skip if chunk was already processed if (this.processedChunks.get(chunk)) { return startupSource; } - const entryModules = Array.from( - chunkGraph.getChunkEntryModulesWithChunkGroupIterable(chunk), - ); - - if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { - // Mark chunk as processed - this.processedChunks.set(chunk, true); - return new ConcatSource( - startupSource, - `${RuntimeGlobals.startup}();\n`, - ); - } - // Mark chunk as processed this.processedChunks.set(chunk, true); - const entryGeneration = runtimeTemplate.outputOptions.module - ? generateESMEntryStartup - : generateEntryStartup; - - return new compiler.webpack.sources.ConcatSource( - entryGeneration( - compilation, - chunkGraph, - runtimeTemplate, - entryModules, - chunk, - false, - ), + + // Add basic startup call + return new ConcatSource( + startupSource, + `${RuntimeGlobals.startup}();\n`, ); }, ); }, ); + compiler.hooks.thisCompilation.tap( 'EmbedFederationRuntimePlugin', (compilation: Compilation) => { @@ -145,31 +138,21 @@ class EmbedFederationRuntimePlugin { }, ); - const isEnabledForChunk = (chunk: Chunk) => { - if (this.options.enableForAllChunks) return true; - if (chunk.id === 'build time chunk') return false; - return chunk.hasRuntime(); - }; - const handleRuntimeRequirements = ( chunk: Chunk, runtimeRequirements: Set, ) => { - console.log(runtimeRequirements); - if (!isEnabledForChunk(chunk)) { + if (!this.isEnabledForChunk(chunk)) { return; } if (runtimeRequirements.has('embeddedFederationRuntime')) return; if (!runtimeRequirements.has(federationGlobal)) { return; } + runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); runtimeRequirements.add('embeddedFederationRuntime'); - if (runtimeRequirements.has(RuntimeGlobals.startup)) { - // debugger; - } else { - // debugger - } + const runtimeModule = new EmbedFederationRuntimeModule( containerEntrySet, ); @@ -179,17 +162,9 @@ class EmbedFederationRuntimePlugin { compilation.hooks.runtimeRequirementInTree .for(federationGlobal) .tap('EmbedFederationRuntimePlugin', handleRuntimeRequirements); - - // compilation.hooks.additionalTreeRuntimeRequirements.tap( - // 'EmbedFederationRuntimePlugin', - // (chunk, runtimeRequirements) => { - // if(!chunk.hasRuntime()) return; - // runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); - // runtimeRequirements.add(RuntimeGlobals.startup); - // }, - // ); }, ); } } + export default EmbedFederationRuntimePlugin; From 6094028ff26bf3b1c62bb3d55423976c25370f72 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 15:42:40 -0800 Subject: [PATCH 09/21] fix(enhanced): fix plugin naming and collision on startup --- .cursorignore | 2 ++ .../webpack-host/webpack.config.js | 1 + .../runtime/EmbedFederationRuntimePlugin.ts | 24 ++++++++----------- .../MfStartupChunkDependenciesPlugin.ts | 14 +++++------ 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.cursorignore b/.cursorignore index 2ab6da1ea8a..3fa058e7ede 100644 --- a/.cursorignore +++ b/.cursorignore @@ -52,6 +52,8 @@ tools/ .vscode/ .verdaccio/ +!apps/manifest-demo/*.ts + # Ignore specific files .cursorignore jest.preset.js diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index 835a911b453..90ee7a73e78 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -62,6 +62,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { config.devServer.client.overlay = false; config.devServer.devMiddleware.writeToDisk = true; } + config.devtool = false; config.entry = './src/index.tsx'; //Temporary workaround - https://github.com/nrwl/nx/issues/16983 config.experiments = { outputModule: false }; diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index 541b93df606..ac05409b446 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -11,6 +11,8 @@ const { RuntimeGlobals } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); +const PLUGIN_NAME = 'EmbedFederationRuntimePlugin'; + const federationGlobal = getFederationGlobalScope(RuntimeGlobals); interface EmbedFederationRuntimePluginOptions { @@ -58,15 +60,14 @@ class EmbedFederationRuntimePlugin { apply(compiler: Compiler): void { // Prevent double application of plugin const compilationTaps = compiler.hooks.thisCompilation.taps || []; - if ( - this.isHookAlreadyTapped(compilationTaps, 'EmbedFederationRuntimePlugin') - ) { + if (this.isHookAlreadyTapped(compilationTaps, PLUGIN_NAME)) { return; } compiler.hooks.thisCompilation.tap( - 'EmbedFederationRuntimePlugin', + PLUGIN_NAME, (compilation: Compilation) => { + debugger; const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, @@ -74,17 +75,12 @@ class EmbedFederationRuntimePlugin { // Prevent double tapping of renderStartup hook const startupTaps = renderStartup.taps || []; - if ( - this.isHookAlreadyTapped( - startupTaps, - 'MfStartupChunkDependenciesPlugin', - ) - ) { + if (this.isHookAlreadyTapped(startupTaps, PLUGIN_NAME)) { return; } renderStartup.tap( - 'MfStartupChunkDependenciesPlugin', + PLUGIN_NAME, (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph } = renderContext; @@ -124,7 +120,7 @@ class EmbedFederationRuntimePlugin { ); compiler.hooks.thisCompilation.tap( - 'EmbedFederationRuntimePlugin', + PLUGIN_NAME, (compilation: Compilation) => { const hooks = FederationModulesPlugin.getCompilationHooks(compilation); const containerEntrySet: Set< @@ -132,7 +128,7 @@ class EmbedFederationRuntimePlugin { > = new Set(); hooks.addFederationRuntimeModule.tap( - 'EmbedFederationRuntimePlugin', + PLUGIN_NAME, (dependency: FederationRuntimeDependency) => { containerEntrySet.add(dependency); }, @@ -161,7 +157,7 @@ class EmbedFederationRuntimePlugin { compilation.hooks.runtimeRequirementInTree .for(federationGlobal) - .tap('EmbedFederationRuntimePlugin', handleRuntimeRequirements); + .tap(PLUGIN_NAME, handleRuntimeRequirements); }, ); } diff --git a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts index 7628d03d674..31ae0959a16 100644 --- a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts +++ b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts @@ -43,13 +43,10 @@ class StartupChunkDependenciesPlugin { compiler.hooks.thisCompilation.tap( 'MfStartupChunkDependenciesPlugin', (compilation) => { - const isEnabledForChunk = (chunk: Chunk) => - this.isEnabledForChunk(chunk, compilation); - compilation.hooks.additionalTreeRuntimeRequirements.tap( 'StartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; + if (!this.isEnabledForChunk(chunk, compilation)) return; if (chunk.hasRuntime()) { set.add(RuntimeGlobals.startupEntrypoint); set.add(RuntimeGlobals.ensureChunk); @@ -61,7 +58,7 @@ class StartupChunkDependenciesPlugin { compilation.hooks.additionalChunkRuntimeRequirements.tap( 'MfStartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; + if (!this.isEnabledForChunk(chunk, compilation)) return; if (chunkGraph.getNumberOfEntryModules(chunk) === 0) return; set.add(federationStartup); }, @@ -72,7 +69,7 @@ class StartupChunkDependenciesPlugin { .tap( 'StartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { - if (!isEnabledForChunk(chunk)) return; + if (!this.isEnabledForChunk(chunk, compilation)) return; set.add(RuntimeGlobals.require); set.add(RuntimeGlobals.ensureChunk); set.add(RuntimeGlobals.ensureChunkIncludeEntries); @@ -93,10 +90,13 @@ class StartupChunkDependenciesPlugin { (startupSource, lastInlinedModule, renderContext) => { const { chunk, chunkGraph, runtimeTemplate } = renderContext; - if (!isEnabledForChunk(chunk)) { + if (!this.isEnabledForChunk(chunk, compilation)) { return startupSource; } + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) + return startupSource; + const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); const chunkRuntimeRequirements = From 2b7ba18f1936075484d4eb024580a93dd1d326e9 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Feb 2025 19:05:45 -0800 Subject: [PATCH 10/21] fix(enhanced): refactor hoisting plugins --- .../container/ContainerPlugin.check.ts | 1124 ++++++++-------- .../schemas/container/ContainerPlugin.json | 5 +- .../src/schemas/container/ContainerPlugin.ts | 12 +- .../container/ModuleFederationPlugin.check.ts | 1168 ++++++++--------- .../container/ModuleFederationPlugin.json | 14 +- .../container/ModuleFederationPlugin.ts | 14 +- 6 files changed, 1107 insertions(+), 1230 deletions(-) diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts index da07fadf34e..48bf217d60c 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.check.ts @@ -4,10 +4,10 @@ * This file was automatically generated. * DO NOT MODIFY BY HAND. */ -const e = /^(?:[A-Za-z]:[\\/]|\\\\|\/)/; +const t = /^(?:[A-Za-z]:[\\/]|\\\\|\/)/; export const validate = m; export default m; -const t = { +const e = { definitions: { RuntimePlugin: { type: 'array', items: { type: 'string' } }, AmdContainer: { type: 'string', minLength: 1 }, @@ -159,9 +159,7 @@ const t = { experiments: { type: 'object', properties: { - federationRuntime: { - anyOf: [{ type: 'boolean' }, { enum: ['hoisted'] }], - }, + asyncStartup: { type: 'boolean' }, externalRuntime: { anyOf: [{ type: 'boolean' }, { enum: ['provide'] }], }, @@ -173,245 +171,245 @@ const t = { }, r = { anyOf: [{ enum: [!1] }, { type: 'string', minLength: 1 }] }; function n( - e, + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, parentDataProperty: s, - rootData: o = e, + rootData: a = t, } = {}, ) { - if (!Array.isArray(e)) + if (!Array.isArray(t)) return (n.errors = [{ params: { type: 'array' } }]), !1; { - const t = e.length; - for (let r = 0; r < t; r++) { - let t = e[r]; + const e = t.length; + for (let r = 0; r < e; r++) { + let e = t[r]; const s = 0; - if ('string' != typeof t) + if ('string' != typeof e) return (n.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (n.errors = [{ params: {} }]), !1; + if (e.length < 1) return (n.errors = [{ params: {} }]), !1; if (0 !== s) break; } } return (n.errors = null), !0; } function s( - e, + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, - parentDataProperty: o, - rootData: a = e, + parentDataProperty: a, + rootData: o = t, } = {}, ) { let i = null, - l = 0; - if (0 === l) { - if (!e || 'object' != typeof e || Array.isArray(e)) + p = 0; + if (0 === p) { + if (!t || 'object' != typeof t || Array.isArray(t)) return (s.errors = [{ params: { type: 'object' } }]), !1; { let r; - if (void 0 === e.import && (r = 'import')) + if (void 0 === t.import && (r = 'import')) return (s.errors = [{ params: { missingProperty: r } }]), !1; { - const r = l; - for (const t in e) - if ('import' !== t && 'name' !== t) - return (s.errors = [{ params: { additionalProperty: t } }]), !1; - if (r === l) { - if (void 0 !== e.import) { - let r = e.import; - const o = l, - m = l; + const r = p; + for (const e in t) + if ('import' !== e && 'name' !== e) + return (s.errors = [{ params: { additionalProperty: e } }]), !1; + if (r === p) { + if (void 0 !== t.import) { + let r = t.import; + const a = p, + m = p; let u = !1; - const c = l; - if (l == l) + const c = p; + if (p == p) if ('string' == typeof r) { if (r.length < 1) { - const e = { params: {} }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: {} }; + null === i ? (i = [t]) : i.push(t), p++; } } else { - const e = { params: { type: 'string' } }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: { type: 'string' } }; + null === i ? (i = [t]) : i.push(t), p++; } - var p = c === l; - if (((u = u || p), !u)) { - const s = l; + var l = c === p; + if (((u = u || l), !u)) { + const s = p; n(r, { - instancePath: t + '/import', - parentData: e, + instancePath: e + '/import', + parentData: t, parentDataProperty: 'import', - rootData: a, + rootData: o, }) || ((i = null === i ? n.errors : i.concat(n.errors)), - (l = i.length)), - (p = s === l), - (u = u || p); + (p = i.length)), + (l = s === p), + (u = u || l); } if (!u) { - const e = { params: {} }; + const t = { params: {} }; return ( - null === i ? (i = [e]) : i.push(e), l++, (s.errors = i), !1 + null === i ? (i = [t]) : i.push(t), p++, (s.errors = i), !1 ); } - (l = m), null !== i && (m ? (i.length = m) : (i = null)); - var f = o === l; + (p = m), null !== i && (m ? (i.length = m) : (i = null)); + var f = a === p; } else f = !0; if (f) - if (void 0 !== e.name) { - const t = l; - if ('string' != typeof e.name) + if (void 0 !== t.name) { + const e = p; + if ('string' != typeof t.name) return (s.errors = [{ params: { type: 'string' } }]), !1; - f = t === l; + f = e === p; } else f = !0; } } } } - return (s.errors = i), 0 === l; + return (s.errors = i), 0 === p; } -function o( - e, +function a( + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, - parentDataProperty: a, - rootData: i = e, + parentDataProperty: o, + rootData: i = t, } = {}, ) { - let l = null, - p = 0; - if (0 === p) { - if (!e || 'object' != typeof e || Array.isArray(e)) - return (o.errors = [{ params: { type: 'object' } }]), !1; - for (const r in e) { - let a = e[r]; - const m = p, - u = p; + let p = null, + l = 0; + if (0 === l) { + if (!t || 'object' != typeof t || Array.isArray(t)) + return (a.errors = [{ params: { type: 'object' } }]), !1; + for (const r in t) { + let o = t[r]; + const m = l, + u = l; let c = !1; - const y = p; - s(a, { - instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), - parentData: e, + const y = l; + s(o, { + instancePath: e + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), + parentData: t, parentDataProperty: r, rootData: i, - }) || ((l = null === l ? s.errors : l.concat(s.errors)), (p = l.length)); - var f = y === p; + }) || ((p = null === p ? s.errors : p.concat(s.errors)), (l = p.length)); + var f = y === l; if (((c = c || f), !c)) { - const s = p; - if (p == p) - if ('string' == typeof a) { - if (a.length < 1) { - const e = { params: {} }; - null === l ? (l = [e]) : l.push(e), p++; + const s = l; + if (l == l) + if ('string' == typeof o) { + if (o.length < 1) { + const t = { params: {} }; + null === p ? (p = [t]) : p.push(t), l++; } } else { - const e = { params: { type: 'string' } }; - null === l ? (l = [e]) : l.push(e), p++; + const t = { params: { type: 'string' } }; + null === p ? (p = [t]) : p.push(t), l++; } - if (((f = s === p), (c = c || f), !c)) { - const s = p; - n(a, { - instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), - parentData: e, + if (((f = s === l), (c = c || f), !c)) { + const s = l; + n(o, { + instancePath: e + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), + parentData: t, parentDataProperty: r, rootData: i, }) || - ((l = null === l ? n.errors : l.concat(n.errors)), (p = l.length)), - (f = s === p), + ((p = null === p ? n.errors : p.concat(n.errors)), (l = p.length)), + (f = s === l), (c = c || f); } } if (!c) { - const e = { params: {} }; - return null === l ? (l = [e]) : l.push(e), p++, (o.errors = l), !1; + const t = { params: {} }; + return null === p ? (p = [t]) : p.push(t), l++, (a.errors = p), !1; } - if (((p = u), null !== l && (u ? (l.length = u) : (l = null)), m !== p)) + if (((l = u), null !== p && (u ? (p.length = u) : (p = null)), m !== l)) break; } } - return (o.errors = l), 0 === p; + return (a.errors = p), 0 === l; } -function a( - e, +function o( + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, parentDataProperty: n, - rootData: s = e, + rootData: s = t, } = {}, ) { let i = null, - l = 0; - const p = l; + p = 0; + const l = p; let f = !1; - const m = l; - if (l === m) - if (Array.isArray(e)) { - const r = e.length; + const m = p; + if (p === m) + if (Array.isArray(t)) { + const r = t.length; for (let n = 0; n < r; n++) { - let r = e[n]; - const a = l, - p = l; + let r = t[n]; + const o = p, + l = p; let f = !1; - const m = l; - if (l == l) + const m = p; + if (p == p) if ('string' == typeof r) { if (r.length < 1) { - const e = { params: {} }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: {} }; + null === i ? (i = [t]) : i.push(t), p++; } } else { - const e = { params: { type: 'string' } }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: { type: 'string' } }; + null === i ? (i = [t]) : i.push(t), p++; } - var u = m === l; + var u = m === p; if (((f = f || u), !f)) { - const a = l; - o(r, { - instancePath: t + '/' + n, - parentData: e, + const o = p; + a(r, { + instancePath: e + '/' + n, + parentData: t, parentDataProperty: n, rootData: s, }) || - ((i = null === i ? o.errors : i.concat(o.errors)), (l = i.length)), - (u = a === l), + ((i = null === i ? a.errors : i.concat(a.errors)), (p = i.length)), + (u = o === p), (f = f || u); } - if (f) (l = p), null !== i && (p ? (i.length = p) : (i = null)); + if (f) (p = l), null !== i && (l ? (i.length = l) : (i = null)); else { - const e = { params: {} }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: {} }; + null === i ? (i = [t]) : i.push(t), p++; } - if (a !== l) break; + if (o !== p) break; } } else { - const e = { params: { type: 'array' } }; - null === i ? (i = [e]) : i.push(e), l++; + const t = { params: { type: 'array' } }; + null === i ? (i = [t]) : i.push(t), p++; } - var c = m === l; + var c = m === p; if (((f = f || c), !f)) { - const a = l; - o(e, { - instancePath: t, + const o = p; + a(t, { + instancePath: e, parentData: r, parentDataProperty: n, rootData: s, - }) || ((i = null === i ? o.errors : i.concat(o.errors)), (l = i.length)), - (c = a === l), + }) || ((i = null === i ? a.errors : i.concat(a.errors)), (p = i.length)), + (c = o === p), (f = f || c); } if (!f) { - const e = { params: {} }; - return null === i ? (i = [e]) : i.push(e), l++, (a.errors = i), !1; + const t = { params: {} }; + return null === i ? (i = [t]) : i.push(t), p++, (o.errors = i), !1; } return ( - (l = p), - null !== i && (p ? (i.length = p) : (i = null)), - (a.errors = i), - 0 === l + (p = l), + null !== i && (l ? (i.length = l) : (i = null)), + (o.errors = i), + 0 === p ); } const i = { @@ -441,447 +439,447 @@ const i = { { type: 'string' }, ], }; -function l( - e, +function p( + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, parentDataProperty: n, - rootData: s = e, + rootData: s = t, } = {}, ) { - let o = null, - a = 0; - const i = a; - let p = !1; - const f = a; - if ('string' != typeof e) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + let a = null, + o = 0; + const i = o; + let l = !1; + const f = o; + if ('string' != typeof t) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var m = f === a; - if (((p = p || m), !p)) { - const t = a; - if (a == a) - if (e && 'object' == typeof e && !Array.isArray(e)) { - const t = a; - for (const t in e) + var m = f === o; + if (((l = l || m), !l)) { + const e = o; + if (o == o) + if (t && 'object' == typeof t && !Array.isArray(t)) { + const e = o; + for (const e in t) if ( - 'amd' !== t && - 'commonjs' !== t && - 'commonjs2' !== t && - 'root' !== t + 'amd' !== e && + 'commonjs' !== e && + 'commonjs2' !== e && + 'root' !== e ) { - const e = { params: { additionalProperty: t } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { additionalProperty: e } }; + null === a ? (a = [t]) : a.push(t), o++; break; } - if (t === a) { - if (void 0 !== e.amd) { - const t = a; - if ('string' != typeof e.amd) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + if (e === o) { + if (void 0 !== t.amd) { + const e = o; + if ('string' != typeof t.amd) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var u = t === a; + var u = e === o; } else u = !0; if (u) { - if (void 0 !== e.commonjs) { - const t = a; - if ('string' != typeof e.commonjs) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + if (void 0 !== t.commonjs) { + const e = o; + if ('string' != typeof t.commonjs) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - u = t === a; + u = e === o; } else u = !0; if (u) { - if (void 0 !== e.commonjs2) { - const t = a; - if ('string' != typeof e.commonjs2) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + if (void 0 !== t.commonjs2) { + const e = o; + if ('string' != typeof t.commonjs2) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - u = t === a; + u = e === o; } else u = !0; if (u) - if (void 0 !== e.root) { - const t = a; - if ('string' != typeof e.root) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + if (void 0 !== t.root) { + const e = o; + if ('string' != typeof t.root) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - u = t === a; + u = e === o; } else u = !0; } } } } else { - const e = { params: { type: 'object' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'object' } }; + null === a ? (a = [t]) : a.push(t), o++; } - (m = t === a), (p = p || m); + (m = e === o), (l = l || m); } - if (!p) { - const e = { params: {} }; - return null === o ? (o = [e]) : o.push(e), a++, (l.errors = o), !1; + if (!l) { + const t = { params: {} }; + return null === a ? (a = [t]) : a.push(t), o++, (p.errors = a), !1; } return ( - (a = i), - null !== o && (i ? (o.length = i) : (o = null)), - (l.errors = o), - 0 === a + (o = i), + null !== a && (i ? (a.length = i) : (a = null)), + (p.errors = a), + 0 === o ); } -function p( - e, +function l( + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, parentDataProperty: n, - rootData: s = e, + rootData: s = t, } = {}, ) { - let o = null, - a = 0; - const i = a; - let l = !1; - const f = a; - if (a === f) - if (Array.isArray(e)) - if (e.length < 1) { - const e = { params: { limit: 1 } }; - null === o ? (o = [e]) : o.push(e), a++; + let a = null, + o = 0; + const i = o; + let p = !1; + const f = o; + if (o === f) + if (Array.isArray(t)) + if (t.length < 1) { + const t = { params: { limit: 1 } }; + null === a ? (a = [t]) : a.push(t), o++; } else { - const t = e.length; - for (let r = 0; r < t; r++) { - let t = e[r]; - const n = a; - if (a === n) - if ('string' == typeof t) { - if (t.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const e = t.length; + for (let r = 0; r < e; r++) { + let e = t[r]; + const n = o; + if (o === n) + if ('string' == typeof e) { + if (e.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - if (n !== a) break; + if (n !== o) break; } } else { - const e = { params: { type: 'array' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'array' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var m = f === a; - if (((l = l || m), !l)) { - const t = a; - if (a === t) - if ('string' == typeof e) { - if (e.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + var m = f === o; + if (((p = p || m), !p)) { + const e = o; + if (o === e) + if ('string' == typeof t) { + if (t.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - if (((m = t === a), (l = l || m), !l)) { - const t = a; - if (a == a) - if (e && 'object' == typeof e && !Array.isArray(e)) { - const t = a; - for (const t in e) - if ('amd' !== t && 'commonjs' !== t && 'root' !== t) { - const e = { params: { additionalProperty: t } }; - null === o ? (o = [e]) : o.push(e), a++; + if (((m = e === o), (p = p || m), !p)) { + const e = o; + if (o == o) + if (t && 'object' == typeof t && !Array.isArray(t)) { + const e = o; + for (const e in t) + if ('amd' !== e && 'commonjs' !== e && 'root' !== e) { + const t = { params: { additionalProperty: e } }; + null === a ? (a = [t]) : a.push(t), o++; break; } - if (t === a) { - if (void 0 !== e.amd) { - let t = e.amd; - const r = a; - if (a === r) - if ('string' == typeof t) { - if (t.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + if (e === o) { + if (void 0 !== t.amd) { + let e = t.amd; + const r = o; + if (o === r) + if ('string' == typeof e) { + if (e.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var u = r === a; + var u = r === o; } else u = !0; if (u) { - if (void 0 !== e.commonjs) { - let t = e.commonjs; - const r = a; - if (a === r) - if ('string' == typeof t) { - if (t.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + if (void 0 !== t.commonjs) { + let e = t.commonjs; + const r = o; + if (o === r) + if ('string' == typeof e) { + if (e.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - u = r === a; + u = r === o; } else u = !0; if (u) - if (void 0 !== e.root) { - let t = e.root; - const r = a, - n = a; + if (void 0 !== t.root) { + let e = t.root; + const r = o, + n = o; let s = !1; - const i = a; - if (a === i) - if (Array.isArray(t)) { - const e = t.length; - for (let r = 0; r < e; r++) { - let e = t[r]; - const n = a; - if (a === n) - if ('string' == typeof e) { - if (e.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const i = o; + if (o === i) + if (Array.isArray(e)) { + const t = e.length; + for (let r = 0; r < t; r++) { + let t = e[r]; + const n = o; + if (o === n) + if ('string' == typeof t) { + if (t.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - if (n !== a) break; + if (n !== o) break; } } else { - const e = { params: { type: 'array' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'array' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var c = i === a; + var c = i === o; if (((s = s || c), !s)) { - const e = a; - if (a === e) - if ('string' == typeof t) { - if (t.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const t = o; + if (o === t) + if ('string' == typeof e) { + if (e.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - (c = e === a), (s = s || c); + (c = t === o), (s = s || c); } if (s) - (a = n), null !== o && (n ? (o.length = n) : (o = null)); + (o = n), null !== a && (n ? (a.length = n) : (a = null)); else { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } - u = r === a; + u = r === o; } else u = !0; } } } else { - const e = { params: { type: 'object' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'object' } }; + null === a ? (a = [t]) : a.push(t), o++; } - (m = t === a), (l = l || m); + (m = e === o), (p = p || m); } } - if (!l) { - const e = { params: {} }; - return null === o ? (o = [e]) : o.push(e), a++, (p.errors = o), !1; + if (!p) { + const t = { params: {} }; + return null === a ? (a = [t]) : a.push(t), o++, (l.errors = a), !1; } return ( - (a = i), - null !== o && (i ? (o.length = i) : (o = null)), - (p.errors = o), - 0 === a + (o = i), + null !== a && (i ? (a.length = i) : (a = null)), + (l.errors = a), + 0 === o ); } function f( - e, + t, { - instancePath: t = '', + instancePath: e = '', parentData: r, parentDataProperty: n, - rootData: s = e, + rootData: s = t, } = {}, ) { - let o = null, - a = 0; - if (0 === a) { - if (!e || 'object' != typeof e || Array.isArray(e)) + let a = null, + o = 0; + if (0 === o) { + if (!t || 'object' != typeof t || Array.isArray(t)) return (f.errors = [{ params: { type: 'object' } }]), !1; { let r; - if (void 0 === e.type && (r = 'type')) + if (void 0 === t.type && (r = 'type')) return (f.errors = [{ params: { missingProperty: r } }]), !1; { - const r = a; - for (const t in e) + const r = o; + for (const e in t) if ( - 'amdContainer' !== t && - 'auxiliaryComment' !== t && - 'export' !== t && - 'name' !== t && - 'type' !== t && - 'umdNamedDefine' !== t + 'amdContainer' !== e && + 'auxiliaryComment' !== e && + 'export' !== e && + 'name' !== e && + 'type' !== e && + 'umdNamedDefine' !== e ) - return (f.errors = [{ params: { additionalProperty: t } }]), !1; - if (r === a) { - if (void 0 !== e.amdContainer) { - let t = e.amdContainer; - const r = a; - if (a == a) { - if ('string' != typeof t) + return (f.errors = [{ params: { additionalProperty: e } }]), !1; + if (r === o) { + if (void 0 !== t.amdContainer) { + let e = t.amdContainer; + const r = o; + if (o == o) { + if ('string' != typeof e) return (f.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (f.errors = [{ params: {} }]), !1; + if (e.length < 1) return (f.errors = [{ params: {} }]), !1; } - var m = r === a; + var m = r === o; } else m = !0; if (m) { - if (void 0 !== e.auxiliaryComment) { - const r = a; - l(e.auxiliaryComment, { - instancePath: t + '/auxiliaryComment', - parentData: e, + if (void 0 !== t.auxiliaryComment) { + const r = o; + p(t.auxiliaryComment, { + instancePath: e + '/auxiliaryComment', + parentData: t, parentDataProperty: 'auxiliaryComment', rootData: s, }) || - ((o = null === o ? l.errors : o.concat(l.errors)), - (a = o.length)), - (m = r === a); + ((a = null === a ? p.errors : a.concat(p.errors)), + (o = a.length)), + (m = r === o); } else m = !0; if (m) { - if (void 0 !== e.export) { - let t = e.export; - const r = a, - n = a; + if (void 0 !== t.export) { + let e = t.export; + const r = o, + n = o; let s = !1; - const i = a; - if (a === i) - if (Array.isArray(t)) { - const e = t.length; - for (let r = 0; r < e; r++) { - let e = t[r]; - const n = a; - if (a === n) - if ('string' == typeof e) { - if (e.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const i = o; + if (o === i) + if (Array.isArray(e)) { + const t = e.length; + for (let r = 0; r < t; r++) { + let t = e[r]; + const n = o; + if (o === n) + if ('string' == typeof t) { + if (t.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - if (n !== a) break; + if (n !== o) break; } } else { - const e = { params: { type: 'array' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'array' } }; + null === a ? (a = [t]) : a.push(t), o++; } - var u = i === a; + var u = i === o; if (((s = s || u), !s)) { - const e = a; - if (a === e) - if ('string' == typeof t) { - if (t.length < 1) { - const e = { params: {} }; - null === o ? (o = [e]) : o.push(e), a++; + const t = o; + if (o === t) + if ('string' == typeof e) { + if (e.length < 1) { + const t = { params: {} }; + null === a ? (a = [t]) : a.push(t), o++; } } else { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - (u = e === a), (s = s || u); + (u = t === o), (s = s || u); } if (!s) { - const e = { params: {} }; + const t = { params: {} }; return ( - null === o ? (o = [e]) : o.push(e), a++, (f.errors = o), !1 + null === a ? (a = [t]) : a.push(t), o++, (f.errors = a), !1 ); } - (a = n), - null !== o && (n ? (o.length = n) : (o = null)), - (m = r === a); + (o = n), + null !== a && (n ? (a.length = n) : (a = null)), + (m = r === o); } else m = !0; if (m) { - if (void 0 !== e.name) { - const r = a; - p(e.name, { - instancePath: t + '/name', - parentData: e, + if (void 0 !== t.name) { + const r = o; + l(t.name, { + instancePath: e + '/name', + parentData: t, parentDataProperty: 'name', rootData: s, }) || - ((o = null === o ? p.errors : o.concat(p.errors)), - (a = o.length)), - (m = r === a); + ((a = null === a ? l.errors : a.concat(l.errors)), + (o = a.length)), + (m = r === o); } else m = !0; if (m) { - if (void 0 !== e.type) { - let t = e.type; - const r = a, - n = a; + if (void 0 !== t.type) { + let e = t.type; + const r = o, + n = o; let s = !1; - const l = a; + const p = o; if ( - 'var' !== t && - 'module' !== t && - 'assign' !== t && - 'assign-properties' !== t && - 'this' !== t && - 'window' !== t && - 'self' !== t && - 'global' !== t && - 'commonjs' !== t && - 'commonjs2' !== t && - 'commonjs-module' !== t && - 'commonjs-static' !== t && - 'amd' !== t && - 'amd-require' !== t && - 'umd' !== t && - 'umd2' !== t && - 'jsonp' !== t && - 'system' !== t + 'var' !== e && + 'module' !== e && + 'assign' !== e && + 'assign-properties' !== e && + 'this' !== e && + 'window' !== e && + 'self' !== e && + 'global' !== e && + 'commonjs' !== e && + 'commonjs2' !== e && + 'commonjs-module' !== e && + 'commonjs-static' !== e && + 'amd' !== e && + 'amd-require' !== e && + 'umd' !== e && + 'umd2' !== e && + 'jsonp' !== e && + 'system' !== e ) { - const e = { params: { allowedValues: i.anyOf[0].enum } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = { params: { allowedValues: i.anyOf[0].enum } }; + null === a ? (a = [t]) : a.push(t), o++; } - var c = l === a; + var c = p === o; if (((s = s || c), !s)) { - const e = a; - if ('string' != typeof t) { - const e = { params: { type: 'string' } }; - null === o ? (o = [e]) : o.push(e), a++; + const t = o; + if ('string' != typeof e) { + const t = { params: { type: 'string' } }; + null === a ? (a = [t]) : a.push(t), o++; } - (c = e === a), (s = s || c); + (c = t === o), (s = s || c); } if (!s) { - const e = { params: {} }; + const t = { params: {} }; return ( - null === o ? (o = [e]) : o.push(e), - a++, - (f.errors = o), + null === a ? (a = [t]) : a.push(t), + o++, + (f.errors = a), !1 ); } - (a = n), - null !== o && (n ? (o.length = n) : (o = null)), - (m = r === a); + (o = n), + null !== a && (n ? (a.length = n) : (a = null)), + (m = r === o); } else m = !0; if (m) - if (void 0 !== e.umdNamedDefine) { - const t = a; - if ('boolean' != typeof e.umdNamedDefine) + if (void 0 !== t.umdNamedDefine) { + const e = o; + if ('boolean' != typeof t.umdNamedDefine) return ( (f.errors = [{ params: { type: 'boolean' } }]), !1 ); - m = t === a; + m = e === o; } else m = !0; } } @@ -891,274 +889,246 @@ function f( } } } - return (f.errors = o), 0 === a; + return (f.errors = a), 0 === o; } function m( n, { instancePath: s = '', - parentData: o, + parentData: a, parentDataProperty: i, - rootData: l = n, + rootData: p = n, } = {}, ) { - let p = null, + let l = null, u = 0; if (0 === u) { if (!n || 'object' != typeof n || Array.isArray(n)) return (m.errors = [{ params: { type: 'object' } }]), !1; { - let o; + let a; if ( - (void 0 === n.name && (o = 'name')) || - (void 0 === n.exposes && (o = 'exposes')) + (void 0 === n.name && (a = 'name')) || + (void 0 === n.exposes && (a = 'exposes')) ) - return (m.errors = [{ params: { missingProperty: o } }]), !1; + return (m.errors = [{ params: { missingProperty: a } }]), !1; { - const o = u; - for (const e in n) + const a = u; + for (const t in n) if ( - 'exposes' !== e && - 'filename' !== e && - 'library' !== e && - 'name' !== e && - 'runtime' !== e && - 'runtimePlugins' !== e && - 'shareScope' !== e && - 'experiments' !== e + 'exposes' !== t && + 'filename' !== t && + 'library' !== t && + 'name' !== t && + 'runtime' !== t && + 'runtimePlugins' !== t && + 'shareScope' !== t && + 'experiments' !== t ) - return (m.errors = [{ params: { additionalProperty: e } }]), !1; - if (o === u) { + return (m.errors = [{ params: { additionalProperty: t } }]), !1; + if (a === u) { if (void 0 !== n.exposes) { - const e = u; - a(n.exposes, { + const t = u; + o(n.exposes, { instancePath: s + '/exposes', parentData: n, parentDataProperty: 'exposes', - rootData: l, + rootData: p, }) || - ((p = null === p ? a.errors : p.concat(a.errors)), - (u = p.length)); - var c = e === u; + ((l = null === l ? o.errors : l.concat(o.errors)), + (u = l.length)); + var c = t === u; } else c = !0; if (c) { if (void 0 !== n.filename) { - let t = n.filename; + let e = n.filename; const r = u; if (u === r) { - if ('string' != typeof t) + if ('string' != typeof e) return (m.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (m.errors = [{ params: {} }]), !1; - if (t.includes('!') || !1 !== e.test(t)) + if (e.length < 1) return (m.errors = [{ params: {} }]), !1; + if (e.includes('!') || !1 !== t.test(e)) return (m.errors = [{ params: {} }]), !1; } c = r === u; } else c = !0; if (c) { if (void 0 !== n.library) { - const e = u; + const t = u; f(n.library, { instancePath: s + '/library', parentData: n, parentDataProperty: 'library', - rootData: l, + rootData: p, }) || - ((p = null === p ? f.errors : p.concat(f.errors)), - (u = p.length)), - (c = e === u); + ((l = null === l ? f.errors : l.concat(f.errors)), + (u = l.length)), + (c = t === u); } else c = !0; if (c) { if (void 0 !== n.name) { - let e = n.name; - const t = u; - if (u === t) { - if ('string' != typeof e) + let t = n.name; + const e = u; + if (u === e) { + if ('string' != typeof t) return (m.errors = [{ params: { type: 'string' } }]), !1; - if (e.length < 1) return (m.errors = [{ params: {} }]), !1; + if (t.length < 1) return (m.errors = [{ params: {} }]), !1; } - c = t === u; + c = e === u; } else c = !0; if (c) { if (void 0 !== n.runtime) { - let e = n.runtime; - const t = u, + let t = n.runtime; + const e = u, s = u; - let o = !1; - const a = u; - if (!1 !== e) { - const e = { params: { allowedValues: r.anyOf[0].enum } }; - null === p ? (p = [e]) : p.push(e), u++; + let a = !1; + const o = u; + if (!1 !== t) { + const t = { params: { allowedValues: r.anyOf[0].enum } }; + null === l ? (l = [t]) : l.push(t), u++; } - var y = a === u; - if (((o = o || y), !o)) { - const t = u; - if (u === t) - if ('string' == typeof e) { - if (e.length < 1) { - const e = { params: {} }; - null === p ? (p = [e]) : p.push(e), u++; + var y = o === u; + if (((a = a || y), !a)) { + const e = u; + if (u === e) + if ('string' == typeof t) { + if (t.length < 1) { + const t = { params: {} }; + null === l ? (l = [t]) : l.push(t), u++; } } else { - const e = { params: { type: 'string' } }; - null === p ? (p = [e]) : p.push(e), u++; + const t = { params: { type: 'string' } }; + null === l ? (l = [t]) : l.push(t), u++; } - (y = t === u), (o = o || y); + (y = e === u), (a = a || y); } - if (!o) { - const e = { params: {} }; + if (!a) { + const t = { params: {} }; return ( - null === p ? (p = [e]) : p.push(e), + null === l ? (l = [t]) : l.push(t), u++, - (m.errors = p), + (m.errors = l), !1 ); } (u = s), - null !== p && (s ? (p.length = s) : (p = null)), - (c = t === u); + null !== l && (s ? (l.length = s) : (l = null)), + (c = e === u); } else c = !0; if (c) { if (void 0 !== n.runtimePlugins) { - let e = n.runtimePlugins; - const t = u; + let t = n.runtimePlugins; + const e = u; if (u == u) { - if (!Array.isArray(e)) + if (!Array.isArray(t)) return ( (m.errors = [{ params: { type: 'array' } }]), !1 ); { - const t = e.length; - for (let r = 0; r < t; r++) { - const t = u; - if ('string' != typeof e[r]) + const e = t.length; + for (let r = 0; r < e; r++) { + const e = u; + if ('string' != typeof t[r]) return ( (m.errors = [{ params: { type: 'string' } }]), !1 ); - if (t !== u) break; + if (e !== u) break; } } } - c = t === u; + c = e === u; } else c = !0; if (c) { if (void 0 !== n.shareScope) { - let e = n.shareScope; - const t = u; - if (u === t) { - if ('string' != typeof e) + let t = n.shareScope; + const e = u; + if (u === e) { + if ('string' != typeof t) return ( (m.errors = [{ params: { type: 'string' } }]), !1 ); - if (e.length < 1) + if (t.length < 1) return (m.errors = [{ params: {} }]), !1; } - c = t === u; + c = e === u; } else c = !0; if (c) if (void 0 !== n.experiments) { - let e = n.experiments; + let t = n.experiments; const r = u; if (u === r) { - if (!e || 'object' != typeof e || Array.isArray(e)) + if (!t || 'object' != typeof t || Array.isArray(t)) return ( (m.errors = [{ params: { type: 'object' } }]), !1 ); { const r = u; - for (const t in e) + for (const e in t) if ( - 'federationRuntime' !== t && - 'externalRuntime' !== t + 'asyncStartup' !== e && + 'externalRuntime' !== e ) return ( (m.errors = [ - { params: { additionalProperty: t } }, + { params: { additionalProperty: e } }, ]), !1 ); if (r === u) { - if (void 0 !== e.federationRuntime) { - let r = e.federationRuntime; - const n = u, - s = u; - let o = !1; - const a = u; - if ('boolean' != typeof r) { - const e = { params: { type: 'boolean' } }; - null === p ? (p = [e]) : p.push(e), u++; - } - var g = a === u; - if (((o = o || g), !o)) { - const e = u; - if ('hoisted' !== r) { - const e = { - params: { - allowedValues: - t.properties.experiments.properties - .federationRuntime.anyOf[1].enum, - }, - }; - null === p ? (p = [e]) : p.push(e), u++; - } - (g = e === u), (o = o || g); - } - if (!o) { - const e = { params: {} }; + if (void 0 !== t.asyncStartup) { + const e = u; + if ('boolean' != typeof t.asyncStartup) return ( - null === p ? (p = [e]) : p.push(e), - u++, - (m.errors = p), + (m.errors = [ + { params: { type: 'boolean' } }, + ]), !1 ); - } - (u = s), - null !== p && - (s ? (p.length = s) : (p = null)); - var d = n === u; - } else d = !0; - if (d) - if (void 0 !== e.externalRuntime) { - let r = e.externalRuntime; + var g = e === u; + } else g = !0; + if (g) + if (void 0 !== t.externalRuntime) { + let r = t.externalRuntime; const n = u, s = u; - let o = !1; - const a = u; + let a = !1; + const o = u; if ('boolean' != typeof r) { - const e = { params: { type: 'boolean' } }; - null === p ? (p = [e]) : p.push(e), u++; + const t = { params: { type: 'boolean' } }; + null === l ? (l = [t]) : l.push(t), u++; } - var h = a === u; - if (((o = o || h), !o)) { - const e = u; + var d = o === u; + if (((a = a || d), !a)) { + const t = u; if ('provide' !== r) { - const e = { + const t = { params: { allowedValues: - t.properties.experiments + e.properties.experiments .properties.externalRuntime .anyOf[1].enum, }, }; - null === p ? (p = [e]) : p.push(e), u++; + null === l ? (l = [t]) : l.push(t), u++; } - (h = e === u), (o = o || h); + (d = t === u), (a = a || d); } - if (!o) { - const e = { params: {} }; + if (!a) { + const t = { params: {} }; return ( - null === p ? (p = [e]) : p.push(e), + null === l ? (l = [t]) : l.push(t), u++, - (m.errors = p), + (m.errors = l), !1 ); } (u = s), - null !== p && - (s ? (p.length = s) : (p = null)), - (d = n === u); - } else d = !0; + null !== l && + (s ? (l.length = s) : (l = null)), + (g = n === u); + } else g = !0; } } } @@ -1174,5 +1144,5 @@ function m( } } } - return (m.errors = p), 0 === u; + return (m.errors = l), 0 === u; } diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.json b/packages/enhanced/src/schemas/container/ContainerPlugin.json index 671b32a38e4..85966159285 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.json +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.json @@ -302,8 +302,9 @@ "experiments": { "type": "object", "properties": { - "federationRuntime": { - "anyOf": [{ "type": "boolean" }, { "enum": ["hoisted"] }] + "asyncStartup": { + "description": "Enable async startup for the container", + "type": "boolean" }, "externalRuntime": { "anyOf": [{ "type": "boolean" }, { "enum": ["provide"] }] diff --git a/packages/enhanced/src/schemas/container/ContainerPlugin.ts b/packages/enhanced/src/schemas/container/ContainerPlugin.ts index 3676e30fbf6..bcb49871786 100644 --- a/packages/enhanced/src/schemas/container/ContainerPlugin.ts +++ b/packages/enhanced/src/schemas/container/ContainerPlugin.ts @@ -327,15 +327,9 @@ export default { experiments: { type: 'object', properties: { - federationRuntime: { - anyOf: [ - { - type: 'boolean', - }, - { - enum: ['hoisted'], - }, - ], + asyncStartup: { + description: 'Enable async startup for the container', + type: 'boolean', }, externalRuntime: { anyOf: [ diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts index 431631aaf66..9a0d37bcef7 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts @@ -360,12 +360,7 @@ const t = { experiments: { type: 'object', properties: { - federationRuntime: { - anyOf: [ - { type: 'boolean', enum: [!1] }, - { type: 'string', enum: ['hoisted'] }, - ], - }, + asyncStartup: { type: 'boolean' }, externalRuntime: { type: 'boolean' }, provideExternalRuntime: { type: 'boolean' }, }, @@ -455,9 +450,9 @@ function a( if (void 0 !== e.import) { let r = e.import; const n = l, - m = l; - let y = !1; - const u = l; + y = l; + let c = !1; + const m = l; if (l == l) if ('string' == typeof r) { if (r.length < 1) { @@ -468,8 +463,8 @@ function a( const e = { params: { type: 'string' } }; null === i ? (i = [e]) : i.push(e), l++; } - var p = u === l; - if (((y = y || p), !y)) { + var p = m === l; + if (((c = c || p), !c)) { const n = l; o(r, { instancePath: t + '/import', @@ -480,15 +475,15 @@ function a( ((i = null === i ? o.errors : i.concat(o.errors)), (l = i.length)), (p = n === l), - (y = y || p); + (c = c || p); } - if (!y) { + if (!c) { const e = { params: {} }; return ( null === i ? (i = [e]) : i.push(e), l++, (a.errors = i), !1 ); } - (l = m), null !== i && (m ? (i.length = m) : (i = null)); + (l = y), null !== i && (y ? (i.length = y) : (i = null)); var f = n === l; } else f = !0; if (f) @@ -520,18 +515,18 @@ function i( return (i.errors = [{ params: { type: 'object' } }]), !1; for (const r in e) { let n = e[r]; - const m = p, - y = p; - let u = !1; - const c = p; + const y = p, + c = p; + let m = !1; + const u = p; a(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: e, parentDataProperty: r, rootData: s, }) || ((l = null === l ? a.errors : l.concat(a.errors)), (p = l.length)); - var f = c === p; - if (((u = u || f), !u)) { + var f = u === p; + if (((m = m || f), !m)) { const a = p; if (p == p) if ('string' == typeof n) { @@ -543,7 +538,7 @@ function i( const e = { params: { type: 'string' } }; null === l ? (l = [e]) : l.push(e), p++; } - if (((f = a === p), (u = u || f), !u)) { + if (((f = a === p), (m = m || f), !m)) { const a = p; o(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), @@ -553,14 +548,14 @@ function i( }) || ((l = null === l ? o.errors : l.concat(o.errors)), (p = l.length)), (f = a === p), - (u = u || f); + (m = m || f); } } - if (!u) { + if (!m) { const e = { params: {} }; return null === l ? (l = [e]) : l.push(e), p++, (i.errors = l), !1; } - if (((p = y), null !== l && (y ? (l.length = y) : (l = null)), m !== p)) + if (((p = c), null !== l && (c ? (l.length = c) : (l = null)), y !== p)) break; } } @@ -579,8 +574,8 @@ function l( a = 0; const p = a; let f = !1; - const m = a; - if (a === m) + const y = a; + if (a === y) if (Array.isArray(e)) { const r = e.length; for (let n = 0; n < r; n++) { @@ -588,7 +583,7 @@ function l( const l = a, p = a; let f = !1; - const m = a; + const y = a; if (a == a) if ('string' == typeof r) { if (r.length < 1) { @@ -599,8 +594,8 @@ function l( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var y = m === a; - if (((f = f || y), !f)) { + var c = y === a; + if (((f = f || c), !f)) { const l = a; i(r, { instancePath: t + '/' + n, @@ -609,8 +604,8 @@ function l( rootData: s, }) || ((o = null === o ? i.errors : o.concat(i.errors)), (a = o.length)), - (y = l === a), - (f = f || y); + (c = l === a), + (f = f || c); } if (f) (a = p), null !== o && (p ? (o.length = p) : (o = null)); else { @@ -623,8 +618,8 @@ function l( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = m === a; - if (((f = f || u), !f)) { + var m = y === a; + if (((f = f || m), !f)) { const l = a; i(e, { instancePath: t, @@ -632,8 +627,8 @@ function l( parentDataProperty: n, rootData: s, }) || ((o = null === o ? i.errors : o.concat(i.errors)), (a = o.length)), - (u = l === a), - (f = f || u); + (m = l === a), + (f = f || m); } if (!f) { const e = { params: {} }; @@ -691,8 +686,8 @@ function f( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var m = p === a; - if (((l = l || m), !l)) { + var y = p === a; + if (((l = l || y), !l)) { const t = a; if (a == a) if (e && 'object' == typeof e && !Array.isArray(e)) { @@ -715,35 +710,35 @@ function f( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var y = t === a; - } else y = !0; - if (y) { + var c = t === a; + } else c = !0; + if (c) { if (void 0 !== e.commonjs) { const t = a; if ('string' != typeof e.commonjs) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - y = t === a; - } else y = !0; - if (y) { + c = t === a; + } else c = !0; + if (c) { if (void 0 !== e.commonjs2) { const t = a; if ('string' != typeof e.commonjs2) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - y = t === a; - } else y = !0; - if (y) + c = t === a; + } else c = !0; + if (c) if (void 0 !== e.root) { const t = a; if ('string' != typeof e.root) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - y = t === a; - } else y = !0; + c = t === a; + } else c = !0; } } } @@ -751,7 +746,7 @@ function f( const e = { params: { type: 'object' } }; null === o ? (o = [e]) : o.push(e), a++; } - (m = t === a), (l = l || m); + (y = t === a), (l = l || y); } if (!l) { const e = { params: {} }; @@ -764,7 +759,7 @@ function f( 0 === a ); } -function m( +function y( e, { instancePath: t = '', @@ -843,9 +838,9 @@ function m( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var y = r === a; - } else y = !0; - if (y) { + var c = r === a; + } else c = !0; + if (c) { if (void 0 !== e.commonjs) { let t = e.commonjs; const r = a; @@ -859,9 +854,9 @@ function m( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - y = r === a; - } else y = !0; - if (y) + c = r === a; + } else c = !0; + if (c) if (void 0 !== e.root) { let t = e.root; const r = a, @@ -890,8 +885,8 @@ function m( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = i === a; - if (((s = s || u), !s)) { + var m = i === a; + if (((s = s || m), !s)) { const e = a; if (a === e) if ('string' == typeof t) { @@ -903,7 +898,7 @@ function m( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - (u = e === a), (s = s || u); + (m = e === a), (s = s || m); } if (s) (a = n), null !== o && (n ? (o.length = n) : (o = null)); @@ -911,8 +906,8 @@ function m( const e = { params: {} }; null === o ? (o = [e]) : o.push(e), a++; } - y = r === a; - } else y = !0; + c = r === a; + } else c = !0; } } } else { @@ -924,16 +919,16 @@ function m( } if (!l) { const e = { params: {} }; - return null === o ? (o = [e]) : o.push(e), a++, (m.errors = o), !1; + return null === o ? (o = [e]) : o.push(e), a++, (y.errors = o), !1; } return ( (a = i), null !== o && (i ? (o.length = i) : (o = null)), - (m.errors = o), + (y.errors = o), 0 === a ); } -function y( +function c( e, { instancePath: t = '', @@ -946,11 +941,11 @@ function y( a = 0; if (0 === a) { if (!e || 'object' != typeof e || Array.isArray(e)) - return (y.errors = [{ params: { type: 'object' } }]), !1; + return (c.errors = [{ params: { type: 'object' } }]), !1; { let r; if (void 0 === e.type && (r = 'type')) - return (y.errors = [{ params: { missingProperty: r } }]), !1; + return (c.errors = [{ params: { missingProperty: r } }]), !1; { const r = a; for (const t in e) @@ -962,15 +957,15 @@ function y( 'type' !== t && 'umdNamedDefine' !== t ) - return (y.errors = [{ params: { additionalProperty: t } }]), !1; + return (c.errors = [{ params: { additionalProperty: t } }]), !1; if (r === a) { if (void 0 !== e.amdContainer) { let t = e.amdContainer; const r = a; if (a == a) { if ('string' != typeof t) - return (y.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (y.errors = [{ params: {} }]), !1; + return (c.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (c.errors = [{ params: {} }]), !1; } var i = r === a; } else i = !0; @@ -1034,7 +1029,7 @@ function y( if (!s) { const e = { params: {} }; return ( - null === o ? (o = [e]) : o.push(e), a++, (y.errors = o), !1 + null === o ? (o = [e]) : o.push(e), a++, (c.errors = o), !1 ); } (a = n), @@ -1044,13 +1039,13 @@ function y( if (i) { if (void 0 !== e.name) { const r = a; - m(e.name, { + y(e.name, { instancePath: t + '/name', parentData: e, parentDataProperty: 'name', rootData: s, }) || - ((o = null === o ? m.errors : o.concat(m.errors)), + ((o = null === o ? y.errors : o.concat(y.errors)), (a = o.length)), (i = r === a); } else i = !0; @@ -1084,21 +1079,21 @@ function y( const e = { params: { allowedValues: p.anyOf[0].enum } }; null === o ? (o = [e]) : o.push(e), a++; } - var u = l === a; - if (((s = s || u), !s)) { + var m = l === a; + if (((s = s || m), !s)) { const e = a; if ('string' != typeof t) { const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - (u = e === a), (s = s || u); + (m = e === a), (s = s || m); } if (!s) { const e = { params: {} }; return ( null === o ? (o = [e]) : o.push(e), a++, - (y.errors = o), + (c.errors = o), !1 ); } @@ -1111,7 +1106,7 @@ function y( const t = a; if ('boolean' != typeof e.umdNamedDefine) return ( - (y.errors = [{ params: { type: 'boolean' } }]), !1 + (c.errors = [{ params: { type: 'boolean' } }]), !1 ); i = t === a; } else i = !0; @@ -1123,9 +1118,9 @@ function y( } } } - return (y.errors = o), 0 === a; + return (c.errors = o), 0 === a; } -function u( +function m( e, { instancePath: t = '', @@ -1135,21 +1130,21 @@ function u( } = {}, ) { if (!Array.isArray(e)) - return (u.errors = [{ params: { type: 'array' } }]), !1; + return (m.errors = [{ params: { type: 'array' } }]), !1; { const t = e.length; for (let r = 0; r < t; r++) { let t = e[r]; const n = 0; if ('string' != typeof t) - return (u.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (u.errors = [{ params: {} }]), !1; + return (m.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (m.errors = [{ params: {} }]), !1; if (0 !== n) break; } } - return (u.errors = null), !0; + return (m.errors = null), !0; } -function c( +function u( e, { instancePath: t = '', @@ -1162,23 +1157,23 @@ function c( a = 0; if (0 === a) { if (!e || 'object' != typeof e || Array.isArray(e)) - return (c.errors = [{ params: { type: 'object' } }]), !1; + return (u.errors = [{ params: { type: 'object' } }]), !1; { let r; if (void 0 === e.external && (r = 'external')) - return (c.errors = [{ params: { missingProperty: r } }]), !1; + return (u.errors = [{ params: { missingProperty: r } }]), !1; { const r = a; for (const t in e) if ('external' !== t && 'shareScope' !== t) - return (c.errors = [{ params: { additionalProperty: t } }]), !1; + return (u.errors = [{ params: { additionalProperty: t } }]), !1; if (r === a) { if (void 0 !== e.external) { let r = e.external; const n = a, p = a; let f = !1; - const m = a; + const y = a; if (a == a) if ('string' == typeof r) { if (r.length < 1) { @@ -1189,16 +1184,16 @@ function c( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var i = m === a; + var i = y === a; if (((f = f || i), !f)) { const n = a; - u(r, { + m(r, { instancePath: t + '/external', parentData: e, parentDataProperty: 'external', rootData: s, }) || - ((o = null === o ? u.errors : o.concat(u.errors)), + ((o = null === o ? m.errors : o.concat(m.errors)), (a = o.length)), (i = n === a), (f = f || i); @@ -1206,7 +1201,7 @@ function c( if (!f) { const e = { params: {} }; return ( - null === o ? (o = [e]) : o.push(e), a++, (c.errors = o), !1 + null === o ? (o = [e]) : o.push(e), a++, (u.errors = o), !1 ); } (a = p), null !== o && (p ? (o.length = p) : (o = null)); @@ -1218,8 +1213,8 @@ function c( const r = a; if (a === r) { if ('string' != typeof t) - return (c.errors = [{ params: { type: 'string' } }]), !1; - if (t.length < 1) return (c.errors = [{ params: {} }]), !1; + return (u.errors = [{ params: { type: 'string' } }]), !1; + if (t.length < 1) return (u.errors = [{ params: {} }]), !1; } l = r === a; } else l = !0; @@ -1227,7 +1222,7 @@ function c( } } } - return (c.errors = o), 0 === a; + return (u.errors = o), 0 === a; } function d( e, @@ -1248,14 +1243,14 @@ function d( const l = a, p = a; let f = !1; - const m = a; - c(n, { + const y = a; + u(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: e, parentDataProperty: r, rootData: s, - }) || ((o = null === o ? c.errors : o.concat(c.errors)), (a = o.length)); - var i = m === a; + }) || ((o = null === o ? u.errors : o.concat(u.errors)), (a = o.length)); + var i = y === a; if (((f = f || i), !f)) { const l = a; if (a == a) @@ -1270,13 +1265,13 @@ function d( } if (((i = l === a), (f = f || i), !f)) { const l = a; - u(n, { + m(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: e, parentDataProperty: r, rootData: s, }) || - ((o = null === o ? u.errors : o.concat(u.errors)), (a = o.length)), + ((o = null === o ? m.errors : o.concat(m.errors)), (a = o.length)), (i = l === a), (f = f || i); } @@ -1313,7 +1308,7 @@ function g( const i = a, l = a; let p = !1; - const m = a; + const y = a; if (a == a) if ('string' == typeof r) { if (r.length < 1) { @@ -1324,7 +1319,7 @@ function g( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var f = m === a; + var f = y === a; if (((p = p || f), !p)) { const i = a; d(r, { @@ -1348,8 +1343,8 @@ function g( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var m = p === a; - if (((l = l || m), !l)) { + var y = p === a; + if (((l = l || y), !l)) { const i = a; d(e, { instancePath: t, @@ -1357,8 +1352,8 @@ function g( parentDataProperty: n, rootData: s, }) || ((o = null === o ? d.errors : o.concat(d.errors)), (a = o.length)), - (m = i === a), - (l = l || m); + (y = i === a), + (l = l || y); } if (!l) { const e = { params: {} }; @@ -1572,14 +1567,14 @@ function b( }; null === a ? (a = [e]) : a.push(e), i++; } - var m = o === i; - if (((s = s || m), !s)) { + var y = o === i; + if (((s = s || y), !s)) { const e = i; if ('string' != typeof t) { const e = { params: { type: 'string' } }; null === a ? (a = [e]) : a.push(e), i++; } - (m = e === i), (s = s || m); + (y = e === i), (s = s || y); } if (!s) { const e = { params: {} }; @@ -1626,14 +1621,14 @@ function v( const l = a, p = a; let f = !1; - const m = a; + const y = a; b(n, { instancePath: t + '/' + r.replace(/~/g, '~0').replace(/\//g, '~1'), parentData: e, parentDataProperty: r, rootData: s, }) || ((o = null === o ? b.errors : o.concat(b.errors)), (a = o.length)); - var i = m === a; + var i = y === a; if (((f = f || i), !f)) { const e = a; if (a == a) @@ -1680,7 +1675,7 @@ function P( const i = a, l = a; let p = !1; - const m = a; + const y = a; if (a == a) if ('string' == typeof r) { if (r.length < 1) { @@ -1691,7 +1686,7 @@ function P( const e = { params: { type: 'string' } }; null === o ? (o = [e]) : o.push(e), a++; } - var f = m === a; + var f = y === a; if (((p = p || f), !p)) { const i = a; v(r, { @@ -1715,8 +1710,8 @@ function P( const e = { params: { type: 'array' } }; null === o ? (o = [e]) : o.push(e), a++; } - var m = p === a; - if (((l = l || m), !l)) { + var y = p === a; + if (((l = l || y), !l)) { const i = a; v(e, { instancePath: t, @@ -1724,8 +1719,8 @@ function P( parentDataProperty: n, rootData: s, }) || ((o = null === o ? v.errors : o.concat(v.errors)), (a = o.length)), - (m = i === a), - (l = l || m); + (y = i === a), + (l = l || y); } if (!l) { const e = { params: {} }; @@ -1747,166 +1742,166 @@ function j( rootData: f = o, } = {}, ) { - let m = null, - u = 0; - if (0 === u) { + let y = null, + m = 0; + if (0 === m) { if (!o || 'object' != typeof o || Array.isArray(o)) return (j.errors = [{ params: { type: 'object' } }]), !1; { - const i = u; + const i = m; for (const e in o) if (!s.call(t.properties, e)) return (j.errors = [{ params: { additionalProperty: e } }]), !1; - if (i === u) { + if (i === m) { if (void 0 !== o.dataPrefetch) { - const e = u; + const e = m; if ('boolean' != typeof o.dataPrefetch) return (j.errors = [{ params: { type: 'boolean' } }]), !1; - var c = e === u; - } else c = !0; - if (c) { + var u = e === m; + } else u = !0; + if (u) { if (void 0 !== o.exposes) { - const e = u; + const e = m; l(o.exposes, { instancePath: a + '/exposes', parentData: o, parentDataProperty: 'exposes', rootData: f, }) || - ((m = null === m ? l.errors : m.concat(l.errors)), - (u = m.length)), - (c = e === u); - } else c = !0; - if (c) { + ((y = null === y ? l.errors : y.concat(l.errors)), + (m = y.length)), + (u = e === m); + } else u = !0; + if (u) { if (void 0 !== o.filename) { let t = o.filename; - const r = u; - if (u === r) { + const r = m; + if (m === r) { if ('string' != typeof t) return (j.errors = [{ params: { type: 'string' } }]), !1; if (t.includes('!') || !1 !== e.test(t)) return (j.errors = [{ params: {} }]), !1; } - c = r === u; - } else c = !0; - if (c) { + u = r === m; + } else u = !0; + if (u) { if (void 0 !== o.getPublicPath) { - const e = u; + const e = m; if ('string' != typeof o.getPublicPath) return (j.errors = [{ params: { type: 'string' } }]), !1; - c = e === u; - } else c = !0; - if (c) { + u = e === m; + } else u = !0; + if (u) { if (void 0 !== o.implementation) { - const e = u; + const e = m; if ('string' != typeof o.implementation) return (j.errors = [{ params: { type: 'string' } }]), !1; - c = e === u; - } else c = !0; - if (c) { + u = e === m; + } else u = !0; + if (u) { if (void 0 !== o.library) { - const e = u; - y(o.library, { + const e = m; + c(o.library, { instancePath: a + '/library', parentData: o, parentDataProperty: 'library', rootData: f, }) || - ((m = null === m ? y.errors : m.concat(y.errors)), - (u = m.length)), - (c = e === u); - } else c = !0; - if (c) { + ((y = null === y ? c.errors : y.concat(c.errors)), + (m = y.length)), + (u = e === m); + } else u = !0; + if (u) { if (void 0 !== o.manifest) { let e = o.manifest; - const t = u, - r = u; + const t = m, + r = m; let n = !1; - const s = u; + const s = m; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - var d = s === u; + var d = s === m; if (((n = n || d), !n)) { - const t = u; - if (u === t) + const t = m; + if (m === t) if (e && 'object' == typeof e && !Array.isArray(e)) { if (void 0 !== e.filePath) { - const t = u; + const t = m; if ('string' != typeof e.filePath) { const e = { params: { type: 'string' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - var h = t === u; + var h = t === m; } else h = !0; if (h) { if (void 0 !== e.disableAssetsAnalyze) { - const t = u; + const t = m; if ( 'boolean' != typeof e.disableAssetsAnalyze ) { const e = { params: { type: 'boolean' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - h = t === u; + h = t === m; } else h = !0; if (h) { if (void 0 !== e.fileName) { - const t = u; + const t = m; if ('string' != typeof e.fileName) { const e = { params: { type: 'string' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - h = t === u; + h = t === m; } else h = !0; if (h) if (void 0 !== e.additionalData) { - const t = u; + const t = m; if ('string' != typeof e.additionalData) { const e = { params: { type: 'string' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - h = t === u; + h = t === m; } else h = !0; } } } else { const e = { params: { type: 'object' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - (d = t === u), (n = n || d); + (d = t === m), (n = n || d); } if (!n) { const e = { params: {} }; return ( - null === m ? (m = [e]) : m.push(e), - u++, - (j.errors = m), + null === y ? (y = [e]) : y.push(e), + m++, + (j.errors = y), !1 ); } - (u = r), - null !== m && (r ? (m.length = r) : (m = null)), - (c = t === u); - } else c = !0; - if (c) { + (m = r), + null !== y && (r ? (y.length = r) : (y = null)), + (u = t === m); + } else u = !0; + if (u) { if (void 0 !== o.name) { - const e = u; + const e = m; if ('string' != typeof o.name) return ( (j.errors = [{ params: { type: 'string' } }]), !1 ); - c = e === u; - } else c = !0; - if (c) { + u = e === m; + } else u = !0; + if (u) { if (void 0 !== o.remoteType) { let e = o.remoteType; - const t = u, - n = u; + const t = m, + n = m; let s = !1, a = null; - const i = u; + const i = m; if ( 'var' !== e && 'module' !== e && @@ -1932,80 +1927,80 @@ function j( 'node-commonjs' !== e ) { const e = { params: { allowedValues: r.enum } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - if ((i === u && ((s = !0), (a = 0)), !s)) { + if ((i === m && ((s = !0), (a = 0)), !s)) { const e = { params: { passingSchemas: a } }; return ( - null === m ? (m = [e]) : m.push(e), - u++, - (j.errors = m), + null === y ? (y = [e]) : y.push(e), + m++, + (j.errors = y), !1 ); } - (u = n), - null !== m && (n ? (m.length = n) : (m = null)), - (c = t === u); - } else c = !0; - if (c) { + (m = n), + null !== y && (n ? (y.length = n) : (y = null)), + (u = t === m); + } else u = !0; + if (u) { if (void 0 !== o.remotes) { - const e = u; + const e = m; g(o.remotes, { instancePath: a + '/remotes', parentData: o, parentDataProperty: 'remotes', rootData: f, }) || - ((m = null === m ? g.errors : m.concat(g.errors)), - (u = m.length)), - (c = e === u); - } else c = !0; - if (c) { + ((y = null === y ? g.errors : y.concat(g.errors)), + (m = y.length)), + (u = e === m); + } else u = !0; + if (u) { if (void 0 !== o.runtime) { let e = o.runtime; - const t = u, - r = u; + const t = m, + r = m; let s = !1; - const a = u; + const a = m; if (!1 !== e) { const e = { params: { allowedValues: n.anyOf[0].enum }, }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - var b = a === u; + var b = a === m; if (((s = s || b), !s)) { - const t = u; - if (u === t) + const t = m; + if (m === t) if ('string' == typeof e) { if (e.length < 1) { const e = { params: {} }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } } else { const e = { params: { type: 'string' } }; - null === m ? (m = [e]) : m.push(e), u++; + null === y ? (y = [e]) : y.push(e), m++; } - (b = t === u), (s = s || b); + (b = t === m), (s = s || b); } if (!s) { const e = { params: {} }; return ( - null === m ? (m = [e]) : m.push(e), - u++, - (j.errors = m), + null === y ? (y = [e]) : y.push(e), + m++, + (j.errors = y), !1 ); } - (u = r), - null !== m && (r ? (m.length = r) : (m = null)), - (c = t === u); - } else c = !0; - if (c) { + (m = r), + null !== y && (r ? (y.length = r) : (y = null)), + (u = t === m); + } else u = !0; + if (u) { if (void 0 !== o.runtimePlugins) { let e = o.runtimePlugins; - const t = u; - if (u === t) { + const t = m; + if (m === t) { if (!Array.isArray(e)) return ( (j.errors = [ @@ -2016,7 +2011,7 @@ function j( { const t = e.length; for (let r = 0; r < t; r++) { - const t = u; + const t = m; if ('string' != typeof e[r]) return ( (j.errors = [ @@ -2024,17 +2019,17 @@ function j( ]), !1 ); - if (t !== u) break; + if (t !== m) break; } } } - c = t === u; - } else c = !0; - if (c) { + u = t === m; + } else u = !0; + if (u) { if (void 0 !== o.shareScope) { let e = o.shareScope; - const t = u; - if (u === t) { + const t = m; + if (m === t) { if ('string' != typeof e) return ( (j.errors = [ @@ -2045,12 +2040,12 @@ function j( if (e.length < 1) return (j.errors = [{ params: {} }]), !1; } - c = t === u; - } else c = !0; - if (c) { + u = t === m; + } else u = !0; + if (u) { if (void 0 !== o.shareStrategy) { let e = o.shareStrategy; - const r = u; + const r = m; if ( 'version-first' !== e && 'loaded-first' !== e @@ -2066,27 +2061,27 @@ function j( ]), !1 ); - c = r === u; - } else c = !0; - if (c) { + u = r === m; + } else u = !0; + if (u) { if (void 0 !== o.shared) { - const e = u; + const e = m; P(o.shared, { instancePath: a + '/shared', parentData: o, parentDataProperty: 'shared', rootData: f, }) || - ((m = - null === m + ((y = + null === y ? P.errors - : m.concat(P.errors)), - (u = m.length)), - (c = e === u); - } else c = !0; - if (c) { + : y.concat(P.errors)), + (m = y.length)), + (u = e === m); + } else u = !0; + if (u) { if (void 0 !== o.virtualRuntimeEntry) { - const e = u; + const e = m; if ( 'boolean' != typeof o.virtualRuntimeEntry @@ -2097,26 +2092,26 @@ function j( ]), !1 ); - c = e === u; - } else c = !0; - if (c) { + u = e === m; + } else u = !0; + if (u) { if (void 0 !== o.dev) { let e = o.dev; - const t = u, - r = u; + const t = m, + r = m; let n = !1; - const s = u; + const s = m; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' }, }; - null === m ? (m = [e]) : m.push(e), - u++; + null === y ? (y = [e]) : y.push(e), + m++; } - var v = s === u; + var v = s === m; if (((n = n || v), !n)) { - const t = u; - if (u === t) + const t = m; + if (m === t) if ( e && 'object' == typeof e && @@ -2125,7 +2120,7 @@ function j( if ( void 0 !== e.disableLiveReload ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.disableLiveReload @@ -2135,19 +2130,19 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var D = t === u; + var D = t === m; } else D = !0; if (D) { if ( void 0 !== e.disableHotTypesReload ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.disableHotTypesReload @@ -2157,19 +2152,19 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - D = t === u; + D = t === m; } else D = !0; if (D) if ( void 0 !== e.disableDynamicRemoteTypeHints ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.disableDynamicRemoteTypeHints @@ -2179,61 +2174,61 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - D = t === u; + D = t === m; } else D = !0; } } else { const e = { params: { type: 'object' }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - (v = t === u), (n = n || v); + (v = t === m), (n = n || v); } if (!n) { const e = { params: {} }; return ( - null === m - ? (m = [e]) - : m.push(e), - u++, - (j.errors = m), + null === y + ? (y = [e]) + : y.push(e), + m++, + (j.errors = y), !1 ); } - (u = r), - null !== m && - (r ? (m.length = r) : (m = null)), - (c = t === u); - } else c = !0; - if (c) { + (m = r), + null !== y && + (r ? (y.length = r) : (y = null)), + (u = t === m); + } else u = !0; + if (u) { if (void 0 !== o.dts) { let e = o.dts; - const r = u, - n = u; + const r = m, + n = m; let s = !1; - const a = u; + const a = m; if ('boolean' != typeof e) { const e = { params: { type: 'boolean' }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var A = a === u; + var A = a === m; if (((s = s || A), !s)) { - const r = u; - if (u === r) + const r = m; + if (m === r) if ( e && 'object' == typeof e && @@ -2243,25 +2238,25 @@ function j( void 0 !== e.generateTypes ) { let r = e.generateTypes; - const n = u, - s = u; + const n = m, + s = m; let o = !1; - const a = u; + const a = m; if ('boolean' != typeof r) { const e = { params: { type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var x = a === u; + var x = a === m; if (((o = o || x), !o)) { - const e = u; - if (u === e) + const e = m; + if (m === e) if ( r && 'object' == @@ -2272,7 +2267,7 @@ function j( void 0 !== r.tsConfigPath ) { - const e = u; + const e = m; if ( 'string' != typeof r.tsConfigPath @@ -2282,19 +2277,19 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var O = e === u; + var O = e === m; } else O = !0; if (O) { if ( void 0 !== r.typesFolder ) { - const e = u; + const e = m; if ( 'string' != typeof r.typesFolder @@ -2304,19 +2299,19 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - O = e === u; + O = e === m; } else O = !0; if (O) { if ( void 0 !== r.compiledTypesFolder ) { - const e = u; + const e = m; if ( 'string' != typeof r.compiledTypesFolder @@ -2326,19 +2321,19 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - O = e === u; + O = e === m; } else O = !0; if (O) { if ( void 0 !== r.deleteTypesFolder ) { - const e = u; + const e = m; if ( 'boolean' != typeof r.deleteTypesFolder @@ -2348,16 +2343,16 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [ + null === y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } - O = e === u; + O = e === m; } else O = !0; if (O) { if ( @@ -2366,8 +2361,8 @@ function j( ) { let e = r.additionalFilesToCompile; - const t = u; - if (u === t) + const t = m; + if (m === t) if ( Array.isArray( e, @@ -2381,7 +2376,7 @@ function j( r++ ) { const t = - u; + m; if ( 'string' != typeof e[ @@ -2396,19 +2391,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } if ( t !== - u + m ) break; } @@ -2421,17 +2416,17 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } - O = t === u; + O = t === m; } else O = !0; if (O) { if ( @@ -2439,7 +2434,7 @@ function j( r.compileInChildProcess ) { const e = - u; + m; if ( 'boolean' != typeof r.compileInChildProcess @@ -2452,18 +2447,18 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = - e === u; + e === m; } else O = !0; if (O) { @@ -2474,7 +2469,7 @@ function j( let e = r.compilerInstance; const n = - u; + m; if ( 'tsc' !== e && @@ -2499,19 +2494,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = n === - u; + m; } else O = !0; if (O) { @@ -2520,7 +2515,7 @@ function j( r.generateAPITypes ) { const e = - u; + m; if ( 'boolean' != typeof r.generateAPITypes @@ -2533,19 +2528,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = e === - u; + m; } else O = !0; @@ -2555,7 +2550,7 @@ function j( r.extractThirdParty ) { const e = - u; + m; if ( 'boolean' != typeof r.extractThirdParty @@ -2568,19 +2563,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = e === - u; + m; } else O = !0; @@ -2592,7 +2587,7 @@ function j( r.extractRemoteTypes ) { const e = - u; + m; if ( 'boolean' != typeof r.extractRemoteTypes @@ -2605,19 +2600,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = e === - u; + m; } else O = !0; @@ -2629,7 +2624,7 @@ function j( r.abortOnError ) { const e = - u; + m; if ( 'boolean' != typeof r.abortOnError @@ -2642,19 +2637,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } O = e === - u; + m; } else O = !0; @@ -2673,38 +2668,38 @@ function j( type: 'object', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - (x = e === u), + (x = e === m), (o = o || x); } if (o) - (u = s), - null !== m && + (m = s), + null !== y && (s - ? (m.length = s) - : (m = null)); + ? (y.length = s) + : (y = null)); else { const e = { params: {} }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var T = n === u; + var T = n === m; } else T = !0; if (T) { if ( void 0 !== e.consumeTypes ) { let t = e.consumeTypes; - const r = u, - n = u; + const r = m, + n = m; let s = !1; - const o = u; + const o = m; if ( 'boolean' != typeof t ) { @@ -2713,15 +2708,15 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var R = o === u; + var R = o === m; if (((s = s || R), !s)) { - const e = u; - if (u === e) + const e = m; + if (m === e) if ( t && 'object' == @@ -2732,7 +2727,7 @@ function j( void 0 !== t.typesFolder ) { - const e = u; + const e = m; if ( 'string' != typeof t.typesFolder @@ -2742,19 +2737,19 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - var $ = e === u; + var $ = e === m; } else $ = !0; if ($) { if ( void 0 !== t.abortOnError ) { - const e = u; + const e = m; if ( 'boolean' != typeof t.abortOnError @@ -2764,19 +2759,19 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - $ = e === u; + $ = e === m; } else $ = !0; if ($) { if ( void 0 !== t.remoteTypesFolder ) { - const e = u; + const e = m; if ( 'string' != typeof t.remoteTypesFolder @@ -2786,23 +2781,23 @@ function j( type: 'string', }, }; - null === m - ? (m = [ + null === y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } - $ = e === u; + $ = e === m; } else $ = !0; if ($) { if ( void 0 !== t.deleteTypesFolder ) { - const e = u; + const e = m; if ( 'boolean' != typeof t.deleteTypesFolder @@ -2814,16 +2809,16 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [ + null === y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } - $ = e === u; + $ = e === m; } else $ = !0; if ($) { if ( @@ -2831,7 +2826,7 @@ function j( t.maxRetries ) { const e = - u; + m; if ( 'number' != typeof t.maxRetries @@ -2844,18 +2839,18 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } $ = - e === u; + e === m; } else $ = !0; if ($) { @@ -2864,7 +2859,7 @@ function j( t.consumeAPITypes ) { const e = - u; + m; if ( 'boolean' != typeof t.consumeAPITypes @@ -2877,19 +2872,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } $ = e === - u; + m; } else $ = !0; if ($) @@ -2900,9 +2895,9 @@ function j( let e = t.runtimePkgs; const r = - u; + m; if ( - u === + m === r ) if ( @@ -2919,7 +2914,7 @@ function j( r++ ) { const t = - u; + m; if ( 'string' != typeof e[ @@ -2934,19 +2929,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } if ( t !== - u + m ) break; } @@ -2959,19 +2954,19 @@ function j( }, }; null === - m - ? (m = + y + ? (y = [ e, ]) - : m.push( + : y.push( e, ), - u++; + m++; } $ = r === - u; + m; } else $ = !0; @@ -2986,37 +2981,37 @@ function j( type: 'object', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - (R = e === u), + (R = e === m), (s = s || R); } if (s) - (u = n), - null !== m && + (m = n), + null !== y && (n - ? (m.length = n) - : (m = null)); + ? (y.length = n) + : (y = null)); else { const e = { params: {}, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = r === u; + T = r === m; } else T = !0; if (T) { if ( void 0 !== e.tsConfigPath ) { - const t = u; + const t = m; if ( 'string' != typeof e.tsConfigPath @@ -3026,12 +3021,12 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = t === u; + T = t === m; } else T = !0; if (T) { if ( @@ -3040,7 +3035,7 @@ function j( ) { let t = e.extraOptions; - const r = u; + const r = m; if ( !t || 'object' != @@ -3052,19 +3047,19 @@ function j( type: 'object', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = r === u; + T = r === m; } else T = !0; if (T) { if ( void 0 !== e.implementation ) { - const t = u; + const t = m; if ( 'string' != typeof e.implementation @@ -3074,18 +3069,18 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = t === u; + T = t === m; } else T = !0; if (T) { if ( void 0 !== e.cwd ) { - const t = u; + const t = m; if ( 'string' != typeof e.cwd @@ -3095,19 +3090,19 @@ function j( type: 'string', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = t === u; + T = t === m; } else T = !0; if (T) if ( void 0 !== e.displayErrorInTerminal ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.displayErrorInTerminal @@ -3117,12 +3112,12 @@ function j( type: 'boolean', }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - T = t === u; + T = t === m; } else T = !0; } } @@ -3133,36 +3128,36 @@ function j( const e = { params: { type: 'object' }, }; - null === m - ? (m = [e]) - : m.push(e), - u++; + null === y + ? (y = [e]) + : y.push(e), + m++; } - (A = r === u), (s = s || A); + (A = r === m), (s = s || A); } if (!s) { const e = { params: {} }; return ( - null === m - ? (m = [e]) - : m.push(e), - u++, - (j.errors = m), + null === y + ? (y = [e]) + : y.push(e), + m++, + (j.errors = y), !1 ); } - (u = n), - null !== m && + (m = n), + null !== y && (n - ? (m.length = n) - : (m = null)), - (c = r === u); - } else c = !0; - if (c) { + ? (y.length = n) + : (y = null)), + (u = r === m); + } else u = !0; + if (u) { if (void 0 !== o.experiments) { let e = o.experiments; - const r = u; - if (u === r) { + const t = m; + if (m === t) { if ( !e || 'object' != typeof e || @@ -3178,96 +3173,29 @@ function j( ]), !1 ); - if ( - void 0 !== e.federationRuntime - ) { - let r = e.federationRuntime; - const n = u, - s = u; - let o = !1; - const a = u; - if ('boolean' != typeof r) { - const e = { - params: { - type: 'boolean', - }, - }; - null === m - ? (m = [e]) - : m.push(e), - u++; - } - if (!1 !== r) { - const e = { - params: { - allowedValues: - t.properties - .experiments - .properties - .federationRuntime - .anyOf[0].enum, - }, - }; - null === m - ? (m = [e]) - : m.push(e), - u++; - } - var C = a === u; - if (((o = o || C), !o)) { - const e = u; - if ('string' != typeof r) { - const e = { - params: { - type: 'string', - }, - }; - null === m - ? (m = [e]) - : m.push(e), - u++; - } - if ('hoisted' !== r) { - const e = { - params: { - allowedValues: - t.properties - .experiments - .properties - .federationRuntime - .anyOf[1].enum, - }, - }; - null === m - ? (m = [e]) - : m.push(e), - u++; - } - (C = e === u), (o = o || C); - } - if (!o) { - const e = { params: {} }; + if (void 0 !== e.asyncStartup) { + const t = m; + if ( + 'boolean' != + typeof e.asyncStartup + ) return ( - null === m - ? (m = [e]) - : m.push(e), - u++, - (j.errors = m), + (j.errors = [ + { + params: { + type: 'boolean', + }, + }, + ]), !1 ); - } - (u = s), - null !== m && - (s - ? (m.length = s) - : (m = null)); - var I = n === u; - } else I = !0; - if (I) { + var C = t === m; + } else C = !0; + if (C) { if ( void 0 !== e.externalRuntime ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.externalRuntime @@ -3282,14 +3210,14 @@ function j( ]), !1 ); - I = t === u; - } else I = !0; - if (I) + C = t === m; + } else C = !0; + if (C) if ( void 0 !== e.provideExternalRuntime ) { - const t = u; + const t = m; if ( 'boolean' != typeof e.provideExternalRuntime @@ -3304,17 +3232,17 @@ function j( ]), !1 ); - I = t === u; - } else I = !0; + C = t === m; + } else C = !0; } } - c = r === u; - } else c = !0; - if (c) + u = t === m; + } else u = !0; + if (u) if (void 0 !== o.bridge) { let e = o.bridge; - const t = u; - if (u === t) { + const t = m; + if (m === t) { if ( !e || 'object' != typeof e || @@ -3346,8 +3274,8 @@ function j( !1 ); } - c = t === u; - } else c = !0; + u = t === m; + } else u = !0; } } } @@ -3369,5 +3297,5 @@ function j( } } } - return (j.errors = m), 0 === u; + return (j.errors = y), 0 === m; } diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json index 788d70e7eb3..3b32041b7ec 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json @@ -712,17 +712,9 @@ "experiments": { "type": "object", "properties": { - "federationRuntime": { - "anyOf": [ - { - "type": "boolean", - "enum": [false] - }, - { - "type": "string", - "enum": ["hoisted"] - } - ] + "asyncStartup": { + "description": "Enable async startup for the container", + "type": "boolean" }, "externalRuntime": { "type": "boolean" diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts index 7244749a134..e076dad6734 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts @@ -759,17 +759,9 @@ export default { experiments: { type: 'object', properties: { - federationRuntime: { - anyOf: [ - { - type: 'boolean', - enum: [false], - }, - { - type: 'string', - enum: ['hoisted'], - }, - ], + asyncStartup: { + description: 'Enable async startup for the container', + type: 'boolean', }, externalRuntime: { type: 'boolean', From 89d35b06960a300af7b4baceb8fe0faad6583295 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:01:39 -0800 Subject: [PATCH 11/21] fix(enhanced): refactor hoisting plugins (#3488) --- .github/workflows/e2e-next-dev.yml | 1 + .../3009-webpack-provider/webpack.config.js | 6 ++ .../webpack-host/webpack.config.js | 1 + apps/modernjs/modern.config.ts | 1 + apps/modernjs/project.json | 24 ++--- apps/node-host/src/bootstrap.js | 93 ------------------ apps/node-host/src/main.js | 94 ++++++++++++++++++- apps/node-host/webpack.config.js | 6 +- .../src/lib/container/ContainerPlugin.ts | 4 +- .../runtime/EmbedFederationRuntimeModule.ts | 9 +- .../runtime/EmbedFederationRuntimePlugin.ts | 85 +++++++++-------- .../MfStartupChunkDependenciesPlugin.ts | 8 +- .../src/lib/startup/StartupHelpers.ts | 1 + .../test/ConfigTestCases.basictest.js | 2 + .../enhanced/test/ConfigTestCases.template.js | 4 +- .../exposed-overridables/webpack.config.js | 2 +- .../module-federation/webpack.config.js | 4 +- .../InvertedContainerRuntimeModule.ts | 46 ++++++--- 18 files changed, 215 insertions(+), 176 deletions(-) diff --git a/.github/workflows/e2e-next-dev.yml b/.github/workflows/e2e-next-dev.yml index 00f4d9e69fe..5bfb31f25e4 100644 --- a/.github/workflows/e2e-next-dev.yml +++ b/.github/workflows/e2e-next-dev.yml @@ -48,6 +48,7 @@ jobs: - name: E2E Test for Next.js Dev if: steps.check-ci.outcome == 'success' run: | + npx kill-port 3000,3001,3002 && pnpm run app:next:dev & sleep 1 && npx wait-on tcp:3001 && diff --git a/apps/manifest-demo/3009-webpack-provider/webpack.config.js b/apps/manifest-demo/3009-webpack-provider/webpack.config.js index 6df53bd0864..0a0c221eb0e 100644 --- a/apps/manifest-demo/3009-webpack-provider/webpack.config.js +++ b/apps/manifest-demo/3009-webpack-provider/webpack.config.js @@ -15,6 +15,12 @@ module.exports = composePlugins( config.watchOptions = { ignored: ['**/node_modules/**', '**/@mf-types/**'], }; + config.devServer = { + ...config.devServer, + devMiddleware: { + writeToDisk: true, + }, + }; // publicPath must be specific url config.output.publicPath = 'auto'; config.plugins.push( diff --git a/apps/manifest-demo/webpack-host/webpack.config.js b/apps/manifest-demo/webpack-host/webpack.config.js index 90ee7a73e78..825d4649285 100644 --- a/apps/manifest-demo/webpack-host/webpack.config.js +++ b/apps/manifest-demo/webpack-host/webpack.config.js @@ -13,6 +13,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { }; config.plugins.push( new ModuleFederationPlugin({ + runtime: false, name: 'manifest_host', remotes: { remote1: 'webpack_provider@http://localhost:3009/mf-manifest.json', diff --git a/apps/modernjs/modern.config.ts b/apps/modernjs/modern.config.ts index 6d4c62fc090..66aaf2d73a3 100644 --- a/apps/modernjs/modern.config.ts +++ b/apps/modernjs/modern.config.ts @@ -35,6 +35,7 @@ export default defineConfig({ appendPlugins([ new ModuleFederationPlugin({ + runtime: false, name: 'app1', exposes: { './thing': './src/test.ts', diff --git a/apps/modernjs/project.json b/apps/modernjs/project.json index d9433da6bf2..953912b3513 100644 --- a/apps/modernjs/project.json +++ b/apps/modernjs/project.json @@ -8,13 +8,13 @@ "targets": { "build": { "executor": "nx:run-commands", + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ], "options": { - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ], "commands": [ { "command": "cd apps/modernjs; pnpm run build", @@ -25,13 +25,13 @@ }, "serve": { "executor": "nx:run-commands", + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ], "options": { - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ], "commands": [ { "command": "cd apps/modernjs; pnpm run dev", diff --git a/apps/node-host/src/bootstrap.js b/apps/node-host/src/bootstrap.js index 02b82f673fc..e69de29bb2d 100644 --- a/apps/node-host/src/bootstrap.js +++ b/apps/node-host/src/bootstrap.js @@ -1,93 +0,0 @@ -/** - * This is not a production server yet! - * This is only a minimal backend to get started. - */ - -import express from 'express'; -import * as path from 'path'; -import node_local_remote from 'node_local_remote/test'; -import { registerRemotes, loadRemote } from '@module-federation/runtime'; - -registerRemotes([ - { - name: 'node_dynamic_remote', - entry: 'http://localhost:3026/remoteEntry.js', - }, -]); - -const getMemoryUsage = () => { - const formatSize = (bytes) => `${(bytes / 1024 / 1024).toFixed(2)} MB`; - - const memory = process.memoryUsage(); - return `Time: ${new Date()}\nheap total: ${formatSize( - memory.heapTotal, - )} heapUsed: ${formatSize(memory.heapUsed)} rss: ${formatSize(memory.rss)}`; -}; - -const remoteMsg = import('node_remote/test').then((m) => { - console.log('\x1b[32m%s\x1b[0m', m.default || m); - return m.default || m; -}); -console.log('\x1b[32m%s\x1b[0m', node_local_remote); - -const app = express(); - -app.use('/assets', express.static(path.join(__dirname, 'assets'))); - -app.get('/api', async (req, res) => { - res.send({ - message: 'Welcome to node-host!', - remotes: { - // node_remote: await remoteMsg, - // node_local_remote, - }, - }); -}); - -app.get('/dynamic-remote', async (req, res) => { - const dynamicRemote = await loadRemote('node_dynamic_remote/test-with-axios'); - - res.send({ - message: 'dynamic remote', - dynamicRemote: dynamicRemote(), - }); -}); - -app.get('/upgrade-remote', async (req, res) => { - registerRemotes( - [ - { - name: 'node_dynamic_remote', - entry: 'http://localhost:3027/remoteEntry.js', - }, - ], - { force: true }, - ); - - res.send({ - message: 'Upgrade success!', - }); -}); - -app.get('/memory-cache', async (req, res) => { - const memoryUsage = getMemoryUsage(); - console.log(memoryUsage); - res.send({ - message: 'memory-cache', - memoryUsage: memoryUsage, - }); -}); - -app.get('/federation-info', async (req, res) => { - console.log(global.__FEDERATION__); - console.log(global.__FEDERATION__.__INSTANCES__[0].moduleCache); - res.send({ - message: 'federation info will be output in terminal !', - }); -}); - -const port = process.env.PORT || 3333; -const server = app.listen(port, () => { - console.log(`Listening at http://localhost:${port}/api`); -}); -server.on('error', console.error); diff --git a/apps/node-host/src/main.js b/apps/node-host/src/main.js index b93c7a0268a..39af3d1da44 100644 --- a/apps/node-host/src/main.js +++ b/apps/node-host/src/main.js @@ -1 +1,93 @@ -import('./bootstrap'); +/** + * This is not a production server yet! + * This is only a minimal backend to get started. + */ + +import express from 'express'; +import * as path from 'path'; +import node_local_remote from 'node_local_remote/test'; +import { registerRemotes, loadRemote } from '@module-federation/runtime'; + +registerRemotes([ + { + name: 'node_dynamic_remote', + entry: 'http://localhost:3026/remoteEntry.js', + }, +]); + +const getMemoryUsage = () => { + const formatSize = (bytes) => `${(bytes / 1024 / 1024).toFixed(2)} MB`; + + const memory = process.memoryUsage(); + return `Time: ${new Date()}\nheap total: ${formatSize( + memory.heapTotal, + )} heapUsed: ${formatSize(memory.heapUsed)} rss: ${formatSize(memory.rss)}`; +}; + +const remoteMsg = import('node_remote/test').then((m) => { + console.log('\x1b[32m%s\x1b[0m', m.default || m); + return m.default || m; +}); +console.log('\x1b[32m%s\x1b[0m', node_local_remote); + +const app = express(); + +app.use('/assets', express.static(path.join(__dirname, 'assets'))); + +app.get('/api', async (req, res) => { + res.send({ + message: 'Welcome to node-host!', + remotes: { + node_remote: await remoteMsg, + node_local_remote, + }, + }); +}); + +app.get('/dynamic-remote', async (req, res) => { + const dynamicRemote = await loadRemote('node_dynamic_remote/test-with-axios'); + + res.send({ + message: 'dynamic remote', + dynamicRemote: dynamicRemote(), + }); +}); + +app.get('/upgrade-remote', async (req, res) => { + registerRemotes( + [ + { + name: 'node_dynamic_remote', + entry: 'http://localhost:3027/remoteEntry.js', + }, + ], + { force: true }, + ); + + res.send({ + message: 'Upgrade success!', + }); +}); + +app.get('/memory-cache', async (req, res) => { + const memoryUsage = getMemoryUsage(); + console.log(memoryUsage); + res.send({ + message: 'memory-cache', + memoryUsage: memoryUsage, + }); +}); + +app.get('/federation-info', async (req, res) => { + console.log(global.__FEDERATION__); + console.log(global.__FEDERATION__.__INSTANCES__[0].moduleCache); + res.send({ + message: 'federation info will be output in terminal !', + }); +}); + +const port = process.env.PORT || 3333; +const server = app.listen(port, () => { + console.log(`Listening at http://localhost:${port}/api`); +}); +server.on('error', console.error); diff --git a/apps/node-host/webpack.config.js b/apps/node-host/webpack.config.js index 53f5f4e452e..77626757c55 100644 --- a/apps/node-host/webpack.config.js +++ b/apps/node-host/webpack.config.js @@ -20,12 +20,12 @@ module.exports = composePlugins(withNx(), async (config) => { runtimePlugins: [ require.resolve('@module-federation/node/runtimePlugin'), ], + experiments: { + asyncStartup: true, + }, remotes: { node_local_remote: 'commonjs ../../node-local-remote/dist/remoteEntry.js', - // node_local_remote: '__webpack_require__.federation.instance.moduleCache.get("node_local_remote")', - // node_remote: - // '__webpack_require__.federation.instance.moduleCache.get("node_remote")@http://localhost:3002/remoteEntry.js', node_remote: 'node_remote@http://localhost:3022/remoteEntry.js', }, }), diff --git a/packages/enhanced/src/lib/container/ContainerPlugin.ts b/packages/enhanced/src/lib/container/ContainerPlugin.ts index 25a3db206aa..4fa8ab3f79e 100644 --- a/packages/enhanced/src/lib/container/ContainerPlugin.ts +++ b/packages/enhanced/src/lib/container/ContainerPlugin.ts @@ -193,8 +193,6 @@ class ContainerPlugin { compilation: Compilation, callback: (error?: WebpackError | null | undefined) => void, ) => { - const hasSingleRuntimeChunk = - compilation.options?.optimization?.runtimeChunk; const hooks = FederationModulesPlugin.getCompilationHooks(compilation); const federationRuntimeDependency = federationRuntimePluginInstance.getDependency(compiler); @@ -215,7 +213,7 @@ class ContainerPlugin { { name, filename, - runtime: hasSingleRuntimeChunk ? false : runtime, + runtime, library, }, (error: WebpackError | null | undefined) => { diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts index 93a5ae96d4f..ddbf900d619 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimeModule.ts @@ -63,9 +63,16 @@ class EmbedFederationRuntimeModule extends RuntimeModule { const result = Template.asString([ `var oldStartup = ${RuntimeGlobals.startup};`, + `var hasRun = false;`, `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction( '', - [`${initRuntimeModuleGetter};`, `return oldStartup();`], + [ + `if (!hasRun) {`, + ` hasRun = true;`, + ` ${initRuntimeModuleGetter};`, + `}`, + `return oldStartup();`, + ], )};`, ]); this._cachedGeneratedCode = result; diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index ac05409b446..8ed75bb2698 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -17,14 +17,14 @@ const federationGlobal = getFederationGlobalScope(RuntimeGlobals); interface EmbedFederationRuntimePluginOptions { /** - * Whether to enable runtime module embedding for all chunks - * If false, will only embed for chunks that explicitly require it + * Whether to enable runtime module embedding for all chunks. + * If false, only chunks that explicitly require it will be embedded. */ enableForAllChunks?: boolean; } /** - * Plugin that handles embedding of Module Federation runtime code into chunks. + * Plugin that embeds Module Federation runtime code into chunks. * It ensures proper initialization of federated modules and manages runtime requirements. */ class EmbedFederationRuntimePlugin { @@ -39,16 +39,16 @@ class EmbedFederationRuntimePlugin { } /** - * Determines if runtime embedding should be enabled for a given chunk + * Determines if runtime embedding should be enabled for a given chunk. */ private isEnabledForChunk(chunk: Chunk): boolean { - if (this.options.enableForAllChunks) return true; + // Disable for our special "build time chunk" if (chunk.id === 'build time chunk') return false; - return chunk.hasRuntime(); + return this.options.enableForAllChunks || chunk.hasRuntime(); } /** - * Checks if a compilation hook has already been tapped by this plugin + * Checks if a hook has already been tapped by this plugin. */ private isHookAlreadyTapped( taps: Array<{ name: string }>, @@ -58,82 +58,81 @@ class EmbedFederationRuntimePlugin { } apply(compiler: Compiler): void { - // Prevent double application of plugin + // Prevent double application of the plugin. const compilationTaps = compiler.hooks.thisCompilation.taps || []; if (this.isHookAlreadyTapped(compilationTaps, PLUGIN_NAME)) { return; } + // Tap into the compilation to modify renderStartup and runtime requirements. compiler.hooks.thisCompilation.tap( PLUGIN_NAME, (compilation: Compilation) => { - debugger; + // --- Part 1: Modify renderStartup to append a startup call when none is added automatically --- const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, ); - // Prevent double tapping of renderStartup hook - const startupTaps = renderStartup.taps || []; - if (this.isHookAlreadyTapped(startupTaps, PLUGIN_NAME)) { - return; - } - renderStartup.tap( PLUGIN_NAME, - (startupSource, lastInlinedModule, renderContext) => { + (startupSource, _lastInlinedModule, renderContext) => { const { chunk, chunkGraph } = renderContext; if (!this.isEnabledForChunk(chunk)) { return startupSource; } - const treeRuntimeRequirements = + const runtimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); - const chunkRuntimeRequirements = - chunkGraph.getChunkRuntimeRequirements(chunk); - - const federation = - chunkRuntimeRequirements.has(federationGlobal) || - treeRuntimeRequirements.has(federationGlobal); - - if (!federation) { - return startupSource; - } - - // Skip if chunk was already processed - if (this.processedChunks.get(chunk)) { + const entryModuleCount = chunkGraph.getNumberOfEntryModules(chunk); + + // The default renderBootstrap automatically pushes a startup call when either: + // - There is at least one entry module, OR + // - runtimeRequirements.has(RuntimeGlobals.startupNoDefault) is true. + if ( + entryModuleCount > 0 || + runtimeRequirements.has(RuntimeGlobals.startupNoDefault) + ) { return startupSource; } - // Mark chunk as processed - this.processedChunks.set(chunk, true); - - // Add basic startup call + // Otherwise, append a startup call. return new ConcatSource( startupSource, + '\n// Custom hook: appended startup call because none was added automatically\n', `${RuntimeGlobals.startup}();\n`, ); }, ); - }, - ); - compiler.hooks.thisCompilation.tap( - PLUGIN_NAME, - (compilation: Compilation) => { - const hooks = FederationModulesPlugin.getCompilationHooks(compilation); + // --- Part 2: Embed Federation Runtime Module and adjust runtime requirements --- + const federationHooks = + FederationModulesPlugin.getCompilationHooks(compilation); const containerEntrySet: Set< ContainerEntryDependency | FederationRuntimeDependency > = new Set(); - hooks.addFederationRuntimeModule.tap( + // Proactively add startupOnlyBefore target chunks. + compilation.hooks.additionalChunkRuntimeRequirements.tap( + PLUGIN_NAME, + (chunk: Chunk, runtimeRequirements: Set) => { + if (!this.isEnabledForChunk(chunk)) { + return; + } + runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + }, + ); + + // Collect federation runtime dependencies. + federationHooks.addFederationRuntimeModule.tap( PLUGIN_NAME, (dependency: FederationRuntimeDependency) => { containerEntrySet.add(dependency); }, ); + // Handle additional runtime requirements when federation is enabled. const handleRuntimeRequirements = ( chunk: Chunk, runtimeRequirements: Set, @@ -141,14 +140,14 @@ class EmbedFederationRuntimePlugin { if (!this.isEnabledForChunk(chunk)) { return; } + // Skip if already processed or if not a federation chunk. if (runtimeRequirements.has('embeddedFederationRuntime')) return; if (!runtimeRequirements.has(federationGlobal)) { return; } - runtimeRequirements.add(RuntimeGlobals.startupOnlyBefore); + // Mark as embedded and add the runtime module. runtimeRequirements.add('embeddedFederationRuntime'); - const runtimeModule = new EmbedFederationRuntimeModule( containerEntrySet, ); diff --git a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts index 31ae0959a16..46b8b563d81 100644 --- a/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts +++ b/packages/enhanced/src/lib/startup/MfStartupChunkDependenciesPlugin.ts @@ -12,7 +12,6 @@ import ContainerEntryModule from '../container/ContainerEntryModule'; const { RuntimeGlobals } = require( normalizeWebpackPath('webpack'), ) as typeof import('webpack'); - const StartupEntrypointRuntimeModule = require( normalizeWebpackPath('webpack/lib/runtime/StartupEntrypointRuntimeModule'), ) as typeof import('webpack/lib/runtime/StartupEntrypointRuntimeModule'); @@ -43,6 +42,7 @@ class StartupChunkDependenciesPlugin { compiler.hooks.thisCompilation.tap( 'MfStartupChunkDependenciesPlugin', (compilation) => { + // Add additional runtime requirements at the tree level. compilation.hooks.additionalTreeRuntimeRequirements.tap( 'StartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -55,6 +55,7 @@ class StartupChunkDependenciesPlugin { }, ); + // Add additional runtime requirements at the chunk level if there are entry modules. compilation.hooks.additionalChunkRuntimeRequirements.tap( 'MfStartupChunkDependenciesPlugin', (chunk, set, { chunkGraph }) => { @@ -64,6 +65,7 @@ class StartupChunkDependenciesPlugin { }, ); + // When the startupEntrypoint requirement is present, add extra keys and a runtime module. compilation.hooks.runtimeRequirementInTree .for(RuntimeGlobals.startupEntrypoint) .tap( @@ -80,6 +82,7 @@ class StartupChunkDependenciesPlugin { }, ); + // Replace the generated startup with a custom version if entry modules exist. const { renderStartup } = compiler.webpack.javascript.JavascriptModulesPlugin.getCompilationHooks( compilation, @@ -94,8 +97,9 @@ class StartupChunkDependenciesPlugin { return startupSource; } - if (chunkGraph.getNumberOfEntryModules(chunk) === 0) + if (chunkGraph.getNumberOfEntryModules(chunk) === 0) { return startupSource; + } const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); diff --git a/packages/enhanced/src/lib/startup/StartupHelpers.ts b/packages/enhanced/src/lib/startup/StartupHelpers.ts index e43b28dd3a2..223671e4b6b 100644 --- a/packages/enhanced/src/lib/startup/StartupHelpers.ts +++ b/packages/enhanced/src/lib/startup/StartupHelpers.ts @@ -49,6 +49,7 @@ export const generateEntryStartup = ( '', '\n', 'var promises = [];', + '__webpack_require__.x();', ]; const treeRuntimeRequirements = chunkGraph.getTreeRuntimeRequirements(chunk); diff --git a/packages/enhanced/test/ConfigTestCases.basictest.js b/packages/enhanced/test/ConfigTestCases.basictest.js index ce08fc3df27..b57936c2c6e 100644 --- a/packages/enhanced/test/ConfigTestCases.basictest.js +++ b/packages/enhanced/test/ConfigTestCases.basictest.js @@ -14,3 +14,5 @@ const { describeCases } = require('./ConfigTestCases.template'); describeCases({ name: 'ConfigTestCases', }); + +describe('ConfigTestCases', () => {}); diff --git a/packages/enhanced/test/ConfigTestCases.template.js b/packages/enhanced/test/ConfigTestCases.template.js index 0e70c968312..30c6dd68946 100644 --- a/packages/enhanced/test/ConfigTestCases.template.js +++ b/packages/enhanced/test/ConfigTestCases.template.js @@ -95,9 +95,9 @@ const describeCases = (config) => { if (!mfp._options.experiments) { mfp._options.experiments = {}; } - if (config.federation?.federationRuntime) { + if (config.federation?.asyncStartup) { // dont override if explicitly set - if ('federationRuntime' in mfp._options.experiments) { + if ('asyncStartup' in mfp._options.experiments) { } else { Object.assign( mfp._options.experiments, diff --git a/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js b/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js index 4e71aca7e88..c1ea5c79f39 100644 --- a/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js +++ b/packages/enhanced/test/configCases/container/exposed-overridables/webpack.config.js @@ -8,7 +8,7 @@ module.exports = { exposes: { './Button': './Button', }, - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, shared: { react: { eager: true, diff --git a/packages/enhanced/test/configCases/container/module-federation/webpack.config.js b/packages/enhanced/test/configCases/container/module-federation/webpack.config.js index 313470f8eda..b1670b4db6c 100644 --- a/packages/enhanced/test/configCases/container/module-federation/webpack.config.js +++ b/packages/enhanced/test/configCases/container/module-federation/webpack.config.js @@ -11,7 +11,7 @@ function createConfig() { filename: 'container.js', library: { type: 'system' }, exposes: ['./other', './self', './dep'], - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, remotes: { abc: 'ABC', def: 'DEF', @@ -24,7 +24,7 @@ function createConfig() { filename: 'container2.js', library: { type: 'system' }, exposes: ['./other', './self', './dep'], - experiments: { federationRuntime: false }, + experiments: { asyncStartup: false }, remotes: { abc: 'ABC', def: 'DEF', diff --git a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts index b87d149bd55..f10204fa43b 100644 --- a/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts +++ b/packages/nextjs-mf/src/plugins/container/InvertedContainerRuntimeModule.ts @@ -1,6 +1,4 @@ import { normalizeWebpackPath } from '@module-federation/sdk/normalize-webpack-path'; -import type { Module } from 'webpack'; -import { container } from '@module-federation/enhanced'; import type ContainerEntryModule from '@module-federation/enhanced/src/lib/container/ContainerEntryModule'; const { RuntimeModule, Template, RuntimeGlobals } = require( normalizeWebpackPath('webpack'), @@ -27,6 +25,15 @@ class InvertedContainerRuntimeModule extends RuntimeModule { if (chunk.runtime === 'webpack-api-runtime') { return ''; } + + const runtimeChunk = compilation.options.optimization?.runtimeChunk; + if (runtimeChunk === 'single' || typeof runtimeChunk === 'object') { + const logger = compilation.getLogger('InvertedContainerRuntimeModule'); + logger.info( + 'Runtime chunk is set to single. Consider adding runtime: false to your ModuleFederationPlugin configuration to prevent runtime conflicts.', + ); + } + let containerEntryModule; for (const containerDep of this.options.containers) { const mod = compilation.moduleGraph.getModule(containerDep); @@ -41,7 +48,7 @@ class InvertedContainerRuntimeModule extends RuntimeModule { if ( compilation.chunkGraph.isEntryModuleInChunk(containerEntryModule, chunk) ) { - // dont apply to remote entry itself + // Don't apply to the remote entry itself return ''; } const initRuntimeModuleGetter = compilation.runtimeTemplate.moduleRaw({ @@ -55,16 +62,29 @@ class InvertedContainerRuntimeModule extends RuntimeModule { const nameJSON = JSON.stringify(containerEntryModule._name); return Template.asString([ - `var innerRemote;`, - `function attachRemote () {`, - Template.indent([ - `innerRemote = ${initRuntimeModuleGetter};`, - `var gs = ${RuntimeGlobals.global} || globalThis`, - `gs[${nameJSON}] = innerRemote`, - `return innerRemote;`, - ]), - `};`, - `attachRemote();`, + `var prevStartup = ${RuntimeGlobals.startup};`, + `var hasRun = false;`, + `var cachedRemote;`, + `${RuntimeGlobals.startup} = ${compilation.runtimeTemplate.basicFunction( + '', + Template.asString([ + `if (!hasRun) {`, + Template.indent( + Template.asString([ + `hasRun = true;`, + `if (typeof prevStartup === 'function') {`, + Template.indent(Template.asString([`prevStartup();`])), + `}`, + `cachedRemote = ${initRuntimeModuleGetter};`, + `var gs = ${RuntimeGlobals.global} || globalThis;`, + `gs[${nameJSON}] = cachedRemote;`, + ]), + ), + `} else if (typeof prevStartup === 'function') {`, + Template.indent(`prevStartup();`), + `}`, + ]), + )};`, ]); } } From 254993594232ede066eeb6061b9fb4d0d1d30e71 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 17 Feb 2025 13:49:05 -0800 Subject: [PATCH 12/21] chore: update docs on experiments for asyncStartup --- .../docs/en/blog/hoisted-runtime.mdx | 58 +++++++------------ .../docs/en/configure/experiments.mdx | 43 ++++---------- .../docs/zh/configure/experiments.mdx | 47 ++++----------- 3 files changed, 40 insertions(+), 108 deletions(-) diff --git a/apps/website-new/docs/en/blog/hoisted-runtime.mdx b/apps/website-new/docs/en/blog/hoisted-runtime.mdx index dcdfafdbb2e..82f500dc919 100644 --- a/apps/website-new/docs/en/blog/hoisted-runtime.mdx +++ b/apps/website-new/docs/en/blog/hoisted-runtime.mdx @@ -1,21 +1,25 @@ import {Tabs, Tab} from '@theme' -# Hoisted Runtime +# Async Startup in Module Federation > Sep 5, 2024 -We’re excited to announce a new experimental feature: `experiments.federationRuntime = 'hoisted'`. - -This release introduces **runtime hoisting**, unlocking several powerful capabilities for Module Federation. +We're excited to announce a new experimental feature: `experiments.asyncStartup = true`. +This release introduces **asynchronous startup**, enabling automatic async initialization for Module Federation entrypoints. ## 🚀 Key Features -### No More Dynamic Imports! +### Automatic Async Initialization + +With async startup enabled, **dynamic imports (`import()`) at the top of your app are no longer required**. All entrypoints will now initialize asynchronously by default. This means: +- No more eager errors +- No need for "async boundaries" in userland code +- Automatic handling of async initialization -With runtime hoisting enabled, **dynamic imports (`import()`) at the top of your app are no longer required**. All entrypoints will now initialize asynchronously by default. This means: -- No more eager errors. -- No need for "async boundaries" in userland code. +:::note +This only affects entry point initialization. You can still use dynamic imports normally throughout your application, including for importing remote modules. This feature doesn't change or limit how dynamic imports work in your code. +::: :::tip **Note:** Asynchronous entrypoints will always export a promise. This is particularly relevant if you're manually requiring a bundled entrypoint or exposing something like a UMD library to the global scope. @@ -23,7 +27,7 @@ With runtime hoisting enabled, **dynamic imports (`import()`) at the top of your #### Before & After Comparison -See how the new hoisting behavior simplifies entrypoints. Click on the "Before" and "After" tabs to see the difference. +See how the new async behavior simplifies entrypoints. Click on the "Before" and "After" tabs to see the difference. @@ -61,35 +65,13 @@ See how the new hoisting behavior simplifies entrypoints. Click on the "Before" +### Important Notes -### Support for `runtimeChunk: single` - -In the past, using **single runtime chunks** could lead to problems with Module Federation, as federation code and remote modules were bundled in the entrypoint instead of the runtime. - -With **hoisted runtime**, all necessary modules are moved into the runtime chunks, eliminating these issues and making Federation work smoothly with single runtime chunks. This also resolves long-standing issues from Module Federation V1. - - -### Performance Improvements - -While we plan to focus on further **bundle size optimization** in future updates, this is a solid first step. - -- **Duplicate code removal**: Hoisting removes redundant code in entrypoints that resulted from injecting the runtime into the entrypoints instead of the runtime chunk. -- **Size reduction**: For projects using `runtimeChunk: single`, you can expect a reduction of approximately **100 KB** per entrypoint. - - -### Eager Runtime Access - -A more **low-level enhancement** is the immediate initialization of the federation runtime as a **Webpack `RuntimeModule`**. - -In the past, federation would start with the user’s entrypoint, leading to issues with chunk loading. Now, federation begins with Webpack's runtime, preventing "chicken and egg" issues with **split chunks**. - - -### Improved Split Chunks Support - -Previously, using `splitChunks: all` could lead to issues with Module Federation. With the new hoisting feature, **split chunk support** is significantly improved, and such issues should no longer arise. - - -### Preparing for Shareable Runtime +When using async startup: +- All entrypoints initialize asynchronously by default +- Exports from entrypoints will be promises that resolve to the actual exports +- No manual async boundaries or dynamic imports needed +- Eager consumption errors are automatically prevented through async initialization -While this feature isn’t included in the current release, hoisting lays the groundwork for a future enhancement: **shareable federation runtimes**. This will allow you to rely on the host’s copy of `module-federation/runtime`, reducing the payload size of remote entry files. +This feature simplifies Module Federation setup by removing the need for manual async handling while ensuring proper initialization of federated modules. diff --git a/apps/website-new/docs/en/configure/experiments.mdx b/apps/website-new/docs/en/configure/experiments.mdx index dc4c1fad936..190302a504d 100644 --- a/apps/website-new/docs/en/configure/experiments.mdx +++ b/apps/website-new/docs/en/configure/experiments.mdx @@ -8,7 +8,9 @@ The `experiments` configuration is used to enable experimental capabilities in t new ModuleFederationPlugin({ name: '@demo/host', experiments: { - federationRuntime: 'hoisted' + asyncStartup: true, + externalRuntime: false, + provideExternalRuntime: false }, shared: { react: { @@ -22,47 +24,22 @@ new ModuleFederationPlugin({ }); ``` -## federationRuntime +## asyncStartup -- Type: `enum` +- Type: `boolean` - Required: No - Default: `false` -- Options: `false | "hoisted"` - -### `Hoisted` Runtime - -When the runtime is `hoisted`, the following occurs: - -These configurations are useful for: - -- Setting or using`runtimeChunk: 'single'`. -- Avoiding the `import()` at the top of the app or in the entry point of user code to prevent eager consumption errors. In the past, this was often mandatory and commonly seen as `import('./bootstrap.js')` in example apps. -- Moving the Module Federation runtime packages into runtime chunks, keeping them out of the entry point - reducing code duplication - -1) Optimizations -The `module-federation/runtime` is patched onto entrypoints. This causes multiple copies to exist in some builds. -It also causes some "eager consumption" errors when chunk splitting is configured in certain ways. +When `asyncStartup` is enabled, all Module Federation entrypoints will initialize asynchronously by default. This means: -This resolves a common issue with runtimeChunk: single - -2) Async startup +- No need for manual `import()` statements at the top of your app (e.g., `import('./bootstrap')`) +- All entrypoints automatically handle async initialization +- Prevention of "eager consumption" errors through automatic async handling :::warning -This mode allows for async startup. When exporting a UMD library, it can return a promise resolving to the export. -If you manually require() an entrypoint in Node, it sets module.exports to Promise.resolve(exports). +When using this mode, all entrypoints will initialize asynchronously. If you're manually requiring a bundled entrypoint or exposing a UMD library, it will return a promise resolving to the exports. ::: -Entrypoint startup will switch to "active" initialization and use async dependency startup. - -You will no longer need a mandatory `import('./bootsrtap')` dynamic import in order for consumers / hosts to function. -There should be no "eager consumption" errors possible, as the initialization of the files themselves behave as async chunks. - -3) Eager Runtime Access - -Instead of federation runtime initilizing in the entrypoint code, it is initialized in a RuntimeModule, within the webpack runtime. -This allows module federation to be avaliable ahead of time, thus enabling "Async Startup" capabilities etc. - ## externalRuntime - Type: `boolean` diff --git a/apps/website-new/docs/zh/configure/experiments.mdx b/apps/website-new/docs/zh/configure/experiments.mdx index 5a88eb2d766..d582c5e08da 100644 --- a/apps/website-new/docs/zh/configure/experiments.mdx +++ b/apps/website-new/docs/zh/configure/experiments.mdx @@ -8,7 +8,9 @@ new ModuleFederationPlugin({ name: '@demo/host', experiments: { - federationRuntime: 'hoisted' + asyncStartup: true, + externalRuntime: false, + provideExternalRuntime: false }, shared: { react: { @@ -22,51 +24,22 @@ new ModuleFederationPlugin({ }); ``` -## federationRuntime +## asyncStartup -- Type: `enum` +- Type: `boolean` - Required: No - Default: `false` -- Options: `false | "hoisted"` - -### `Hoisted` Runtime - -当设置 `federationRuntime: 'hoisted'` 时,会发生以下情况: - -这些配置可用于下列场景: - -- 设置 `runtimeChunk: 'single'`。 -- 避免在应用的顶部或用户代码的入口点使用 import(),以防止 `eager` 的错误。在过去,这通常是强制性的,并且常见的示例应用中会看到 `import('./bootstrap.js')`。 -- 将模块联邦运行时包移入运行时代码块,将它们从入口点中移出,从而减少代码重复。 - -下面会详细解释对应的场景。 - -1)优化 -原先 `module-federation/runtime` 会被加入到编译入口,这意味着在多 entry 的情况下会打包多次 mf runtime,会增大产物体积。 -当设置了某些特定的分包策略,还会导致共享依赖 `eager consumption` 的问题。 +启用 `asyncStartup` 后,所有 Module Federation 入口将默认以异步方式初始化。这意味着: -并且 `federationRuntime: 'hoisted'` 还允许设置 `runtimeChunk: "single"`。 - -2)异步启动 +- 无需在应用顶部手动添加 `import()` 语句(例如 `import('./bootstrap')`) +- 所有入口都会自动处理异步初始化 +- 通过自动的异步处理机制防止 "eager consumption" 错误 :::warning -此模式仍然允许设置异步入口。导出 UMD 库时,它会返回 Promise resolve 导出。 -如果您手动 require() Node 中的入口点,它将 module.exports 设置为 Promise.resolve(exports)。 +使用此模式时,所有入口都将异步初始化。如果你手动 require 一个打包后的入口或导出 UMD 库,它将返回一个 promise 来解析导出内容。 ::: -入口启动将切换到“主动”初始化并使用异步依赖项启动。 - -你将不再需要强制的设置异步入口:`“import('./bootsrtap')”`。 - -不再出现共享依赖 `eager consumption` 错误,因为文件本身的初始化表现为异步块。 - -3)提升 MF 运行时访问优先级 - -与原先在入口挂载 MF runtime 不同,现在将会提升到 bundler runtime 初始化阶段挂载 MF runtime 。 - -这允许 MF runtime 提前可用,从而支持 “异步启动”(不再需要异步入口) 功能等。 - ## externalRuntime - Type: `boolean` From 54b752152469c9122b45f257292ba63eb09acaa3 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 17 Feb 2025 13:52:34 -0800 Subject: [PATCH 13/21] chore: update docs on experiments for asyncStartup --- .changeset/ai-eager-lion.md | 13 +++++++++++++ .changeset/ai-happy-bear.md | 6 ++++++ .changeset/ai-lazy-dog.md | 7 +++++++ 3 files changed, 26 insertions(+) create mode 100644 .changeset/ai-eager-lion.md create mode 100644 .changeset/ai-happy-bear.md create mode 100644 .changeset/ai-lazy-dog.md diff --git a/.changeset/ai-eager-lion.md b/.changeset/ai-eager-lion.md new file mode 100644 index 00000000000..4455e562919 --- /dev/null +++ b/.changeset/ai-eager-lion.md @@ -0,0 +1,13 @@ +--- +"@module-federation/enhanced": minor +--- + +Enhanced module federation plugin to remove the `federationRuntime` experiment and replace it with `asyncStartup`. + +- Dropped support for `federationRuntime` experiment and introduced `asyncStartup` to enable asynchronous container startup. +- Refactored EmbedFederationRuntimePlugin for improved runtime embedding and startup management. + - Added options to enable runtime embedding for all chunks. + - Integrated measures to ensure proper initialization and avoid duplicate hooks. +- Simplified constructor and class dependencies by removing the `experiments` parameter. +- Revised schema and validation definitions to accommodate new asynchronous startup configurations. +- Updated test cases to reflect the change from `federationRuntime` to `asyncStartup`. diff --git a/.changeset/ai-happy-bear.md b/.changeset/ai-happy-bear.md new file mode 100644 index 00000000000..124783e19f5 --- /dev/null +++ b/.changeset/ai-happy-bear.md @@ -0,0 +1,6 @@ +--- +"@module-federation/nextjs-mf": patch +--- + +- Changed experiment configuration from `federationRuntime: 'hoisted'` to `asyncStartup: true`. +- Improved initialization logic in the runtime module to enable better caching and startup handling. diff --git a/.changeset/ai-lazy-dog.md b/.changeset/ai-lazy-dog.md new file mode 100644 index 00000000000..0fcc679bc31 --- /dev/null +++ b/.changeset/ai-lazy-dog.md @@ -0,0 +1,7 @@ +--- +"@module-federation/sdk": minor +--- + +- Removed `federationRuntime` option from `ContainerPluginOptions`'s `experiments`. +- Removed `federationRuntime` option from `ModuleFederationPluginOptions`'s `experiments`. +- Added `asyncStartup` option to `ModuleFederationPluginOptions`'s `experiments`. From 1f56bb93e5752339de4c6a0cabd6aa2125811db2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Mar 2025 13:03:58 -0800 Subject: [PATCH 14/21] fix(enhanced): add webpack sources to package --- packages/enhanced/package.json | 3 +- pnpm-lock.yaml | 333 ++++++++++++++++----------------- 2 files changed, 159 insertions(+), 177 deletions(-) diff --git a/packages/enhanced/package.json b/packages/enhanced/package.json index a3105521f38..27c987f7628 100644 --- a/packages/enhanced/package.json +++ b/packages/enhanced/package.json @@ -97,6 +97,7 @@ "@module-federation/runtime-tools": "workspace:*", "@module-federation/sdk": "workspace:*", "btoa": "^1.2.1", - "upath": "2.0.1" + "upath": "2.0.1", + "webpack-sources": "^3.2.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87f2570922b..b602210767a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2227,6 +2227,9 @@ importers: webpack: specifier: ^5.0.0 version: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) + webpack-sources: + specifier: ^3.2.3 + version: 3.2.3 devDependencies: '@module-federation/webpack-bundler-runtime': specifier: workspace:* @@ -3045,10 +3048,10 @@ packages: '@babel/helpers': 7.26.0 '@babel/parser': 7.26.2 '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 convert-source-map: 1.9.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 lodash: 4.17.21 @@ -3093,10 +3096,10 @@ packages: '@babel/helpers': 7.26.0 '@babel/parser': 7.26.2 '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3116,10 +3119,10 @@ packages: '@babel/helpers': 7.26.0 '@babel/parser': 7.26.2 '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3139,10 +3142,10 @@ packages: '@babel/helpers': 7.26.0 '@babel/parser': 7.26.2 '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3164,7 +3167,7 @@ packages: '@babel/traverse': 7.26.9 '@babel/types': 7.26.9 convert-source-map: 2.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3264,7 +3267,7 @@ packages: resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3312,7 +3315,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.7 '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.2) '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3330,7 +3333,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.7 '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.7) '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3348,7 +3351,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.7 '@babel/helper-replace-supers': 7.25.7(@babel/core@7.26.0) '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3365,7 +3368,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.25.7) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3383,7 +3386,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3400,7 +3403,7 @@ packages: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.9) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3484,7 +3487,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3499,7 +3502,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3513,7 +3516,7 @@ packages: '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -3524,7 +3527,7 @@ packages: resolution: {integrity: sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3533,7 +3536,7 @@ packages: resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3542,16 +3545,7 @@ packages: resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - transitivePeerDependencies: - - supports-color - - /@babel/helper-module-imports@7.25.9: - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3575,7 +3569,7 @@ packages: '@babel/helper-module-imports': 7.25.7 '@babel/helper-simple-access': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3586,9 +3580,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.12.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3600,9 +3594,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.25.7 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3614,9 +3608,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.25.8 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3628,9 +3622,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3641,9 +3635,9 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3677,7 +3671,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3691,7 +3685,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3704,7 +3698,7 @@ packages: '@babel/core': 7.26.9 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3718,7 +3712,7 @@ packages: '@babel/core': 7.25.2 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3732,7 +3726,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3746,7 +3740,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3759,7 +3753,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3773,7 +3767,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3786,7 +3780,7 @@ packages: '@babel/core': 7.26.9 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3795,7 +3789,7 @@ packages: resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3804,7 +3798,7 @@ packages: resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3814,7 +3808,7 @@ packages: resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3823,7 +3817,7 @@ packages: resolution: {integrity: sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3832,7 +3826,7 @@ packages: resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3870,7 +3864,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -3945,7 +3939,7 @@ packages: dependencies: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -3958,7 +3952,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -3970,7 +3964,7 @@ packages: dependencies: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -4082,7 +4076,7 @@ packages: dependencies: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -4095,7 +4089,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -4107,7 +4101,7 @@ packages: dependencies: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -4939,7 +4933,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.25.7) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -4953,7 +4947,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -4966,7 +4960,7 @@ packages: '@babel/core': 7.26.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.9) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -4978,7 +4972,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.25.7 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.25.7) transitivePeerDependencies: @@ -4992,7 +4986,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: @@ -5005,7 +4999,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.9) transitivePeerDependencies: @@ -5170,7 +5164,7 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.25.7) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5187,7 +5181,7 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5203,7 +5197,7 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.9) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5528,7 +5522,7 @@ packages: '@babel/core': 7.25.7 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -5542,7 +5536,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5555,7 +5549,7 @@ packages: '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -5779,7 +5773,7 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.7) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -5794,7 +5788,7 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -5808,7 +5802,7 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.9) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -6436,7 +6430,7 @@ packages: dependencies: '@babel/core': 7.25.2 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.2) '@babel/types': 7.26.0 @@ -6452,7 +6446,7 @@ packages: dependencies: '@babel/core': 7.25.7 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.7) '@babel/types': 7.26.0 @@ -6468,7 +6462,7 @@ packages: dependencies: '@babel/core': 7.25.8 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.8) '@babel/types': 7.26.0 @@ -6484,7 +6478,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) '@babel/types': 7.26.0 @@ -6645,7 +6639,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.25.7 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.7) babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.7) @@ -6662,7 +6656,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0) babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) @@ -7501,21 +7495,7 @@ packages: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.4.0(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - /@babel/traverse@7.25.9: - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/template': 7.25.9 - '@babel/types': 7.26.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7543,7 +7523,7 @@ packages: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -8202,7 +8182,7 @@ packages: /@emotion/babel-plugin@11.12.0: resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} dependencies: - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/runtime': 7.26.0 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 @@ -9556,7 +9536,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.2 @@ -9632,7 +9612,7 @@ packages: deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -10583,7 +10563,7 @@ packages: optional: true dependencies: '@babel/parser': 7.26.2 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@modern-js/core': 2.64.0 '@modern-js/node-bundle-require': 2.64.0 @@ -10653,7 +10633,7 @@ packages: optional: true dependencies: '@babel/parser': 7.26.2 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@modern-js/core': 2.64.0 '@modern-js/node-bundle-require': 2.64.0 @@ -12163,7 +12143,7 @@ packages: typescript: 5.5.2 upath: 2.0.1 vue-tsc: 2.1.6(typescript@5.5.2) - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) transitivePeerDependencies: - bufferutil - debug @@ -12929,7 +12909,7 @@ packages: '@open-draft/until': 1.0.3 '@types/debug': 4.1.12 '@xmldom/xmldom': 0.8.10 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) headers-polyfill: 3.2.5 outvariant: 1.4.3 strict-event-emitter: 0.2.8 @@ -16175,7 +16155,7 @@ packages: optional: true dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@rollup/pluginutils': 5.1.3(rollup@4.24.0) rollup: 4.24.0 transitivePeerDependencies: @@ -18600,7 +18580,7 @@ packages: conventional-changelog-writer: 8.0.1 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.1.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) import-from-esm: 2.0.0 lodash-es: 4.17.21 micromatch: 4.0.8 @@ -18694,7 +18674,7 @@ packages: '@octokit/plugin-throttling': 9.4.0(@octokit/core@6.1.4) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) dir-glob: 3.0.1 globby: 14.1.0 http-proxy-agent: 7.0.2 @@ -18763,7 +18743,7 @@ packages: conventional-changelog-writer: 8.0.1 conventional-commits-filter: 5.0.0 conventional-commits-parser: 6.1.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) get-stream: 7.0.1 import-from-esm: 2.0.0 into-stream: 7.0.0 @@ -19783,7 +19763,7 @@ packages: dependencies: '@babel/generator': 7.26.2 '@babel/parser': 7.26.2 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@storybook/csf': 0.1.11 '@storybook/types': 7.6.20 @@ -19799,7 +19779,7 @@ packages: dependencies: '@babel/generator': 7.26.2 '@babel/parser': 7.26.2 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@storybook/csf': 0.1.11 '@storybook/types': 8.1.11 @@ -20095,7 +20075,7 @@ packages: typescript: '>= 3.x' webpack: '>= 4' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) endent: 2.1.0 find-cache-dir: 3.3.2 flat-cache: 3.2.0 @@ -20114,7 +20094,7 @@ packages: typescript: '>= 4.x' webpack: '>= 4' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) endent: 2.1.0 find-cache-dir: 3.3.2 flat-cache: 3.2.0 @@ -20133,7 +20113,7 @@ packages: typescript: '>= 4.x' webpack: '>= 4' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) endent: 2.1.0 find-cache-dir: 3.3.2 flat-cache: 3.2.0 @@ -21868,7 +21848,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 @@ -21920,7 +21900,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 typescript: 5.0.4 transitivePeerDependencies: @@ -21941,7 +21921,7 @@ packages: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.2) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 typescript: 5.5.2 transitivePeerDependencies: @@ -22021,7 +22001,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.0.4) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 @@ -22041,7 +22021,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.2) '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.5.2) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 ts-api-utils: 1.3.0(typescript@5.5.2) typescript: 5.5.2 @@ -22060,7 +22040,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.5.2) '@typescript-eslint/utils': 8.8.0(eslint@8.57.1)(typescript@5.5.2) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) ts-api-utils: 1.3.0(typescript@5.5.2) typescript: 5.5.2 transitivePeerDependencies: @@ -22104,7 +22084,7 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.3 @@ -22125,7 +22105,7 @@ packages: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -22147,7 +22127,7 @@ packages: dependencies: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 @@ -22169,7 +22149,7 @@ packages: dependencies: '@typescript-eslint/types': 8.14.0 '@typescript-eslint/visitor-keys': 8.14.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -22191,7 +22171,7 @@ packages: dependencies: '@typescript-eslint/types': 8.8.0 '@typescript-eslint/visitor-keys': 8.8.0 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -22805,11 +22785,11 @@ packages: optional: true dependencies: '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.2) '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@vue/babel-helper-vue-transform-on': 1.2.5 '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.25.2) @@ -22826,7 +22806,7 @@ packages: dependencies: '@babel/code-frame': 7.26.2 '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/parser': 7.26.2 '@vue/compiler-sfc': 3.5.10 @@ -23559,7 +23539,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -23567,7 +23547,7 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -24532,7 +24512,7 @@ packages: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -24551,7 +24531,7 @@ packages: /babel-plugin-import@1.13.5: resolution: {integrity: sha512-IkqnoV+ov1hdJVofly9pXRJmeDm9EtROfrc5i6eII0Hix2xMs5FEm8FG3ExMvazbnZBbgHIt6qdO8And6lCloQ==} dependencies: - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -24559,7 +24539,7 @@ packages: /babel-plugin-import@1.13.8: resolution: {integrity: sha512-36babpjra5m3gca44V6tSTomeBlPA7cHUynrE2WiQIm3rEGD9xy28MKsx5IdO45EbnpJY7Jrgd00C6Dwt/l/2Q==} dependencies: - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -24714,7 +24694,7 @@ packages: styled-components: '>= 2' dependencies: '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-module-imports': 7.25.9 + '@babel/helper-module-imports': 7.25.9(supports-color@5.5.0) babel-plugin-syntax-jsx: 6.18.0 lodash: 4.17.21 styled-components: 6.1.15(react-dom@18.3.1)(react@18.3.1) @@ -24756,7 +24736,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) /babel-preset-current-node-syntax@1.1.0(@babel/core@7.25.2): resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} @@ -26241,7 +26221,7 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /copy-webpack-plugin@11.0.0(webpack@5.93.0): resolution: {integrity: sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==} @@ -26616,7 +26596,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-value-parser: 4.2.0 semver: 7.6.3 - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /css-loader@6.11.0(@rspack/core@1.1.1)(webpack@5.93.0): resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} @@ -27391,6 +27371,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 + dev: true /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -27637,7 +27618,7 @@ packages: hasBin: true dependencies: address: 1.2.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -28334,7 +28315,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) esbuild: 0.17.19 transitivePeerDependencies: - supports-color @@ -28345,7 +28326,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -28703,7 +28684,7 @@ packages: optional: true dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) @@ -29133,7 +29114,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -29210,7 +29191,7 @@ packages: resolution: {integrity: sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg==} engines: {node: '>=8.3.0'} dependencies: - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 c8: 7.14.0 transitivePeerDependencies: @@ -30088,7 +30069,7 @@ packages: debug: optional: true dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -31672,7 +31653,7 @@ packages: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -31682,7 +31663,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -31710,7 +31691,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@types/http-proxy': 1.17.15 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) http-proxy: 1.18.1(debug@4.4.0) is-glob: 4.0.3 is-plain-object: 5.0.0 @@ -31790,7 +31771,7 @@ packages: engines: {node: '>= 6.0.0'} dependencies: agent-base: 5.1.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -31800,7 +31781,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -31809,7 +31790,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -31819,7 +31800,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -31975,7 +31956,7 @@ packages: resolution: {integrity: sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==} engines: {node: '>=18.20'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) import-meta-resolve: 4.1.0 transitivePeerDependencies: - supports-color @@ -32813,7 +32794,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -32825,7 +32806,7 @@ packages: engines: {node: '>=10'} dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -33880,7 +33861,7 @@ packages: content-disposition: 0.5.4 content-type: 1.0.5 cookies: 0.9.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) delegates: 1.0.0 depd: 2.0.0 destroy: 1.2.0 @@ -33910,7 +33891,7 @@ packages: content-disposition: 0.5.4 content-type: 1.0.5 cookies: 0.9.1 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) delegates: 1.0.0 depd: 2.0.0 destroy: 1.2.0 @@ -34065,7 +34046,7 @@ packages: webpack-sources: optional: true dependencies: - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) webpack-sources: 3.2.3 /lilconfig@2.1.0: @@ -35244,7 +35225,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -35713,7 +35694,7 @@ packages: resolution: {integrity: sha512-E2Rxk6ADpyaGeuFJQ/x9HHW+pS8Vn/0KLbXpiDkoZXcOSToW+/dz1WPHFaZFUnyoe+JRbj8PvxAhcfKbQOw7UQ==} dependencies: '@vercel/nft': 0.27.3(encoding@0.1.13) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) fs-extra: 11.2.0 mlly: 1.6.1 pkg-types: 1.2.1 @@ -38943,7 +38924,7 @@ packages: engines: {node: '>=8.16.0'} dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -40629,7 +40610,7 @@ packages: engines: {node: '>=16.14.0'} dependencies: '@babel/core': 7.26.0 - '@babel/traverse': 7.25.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) '@babel/types': 7.26.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 @@ -42494,7 +42475,7 @@ packages: klona: 2.0.6 neo-async: 2.6.2 sass: 1.79.4 - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /sass-loader@13.3.3(webpack@5.93.0): resolution: {integrity: sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==} @@ -42633,7 +42614,7 @@ packages: '@semantic-release/release-notes-generator': 14.0.3(semantic-release@24.2.3) aggregate-error: 5.0.0 cosmiconfig: 9.0.0(typescript@5.5.2) - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) env-ci: 11.1.0 execa: 9.5.2 figures: 6.1.0 @@ -42744,7 +42725,7 @@ packages: resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} engines: {node: '>= 18'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) destroy: 1.2.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -43228,7 +43209,7 @@ packages: /spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -43241,7 +43222,7 @@ packages: resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} engines: {node: '>=6.0.0'} dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -43565,7 +43546,7 @@ packages: engines: {node: '>=8.0'} dependencies: date-format: 4.0.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -43817,7 +43798,7 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /style-to-object@0.3.0: resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==} @@ -43989,7 +43970,7 @@ packages: hasBin: true dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) glob: 10.4.5 sax: 1.4.1 source-map: 0.7.4 @@ -46278,7 +46259,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.1.1 vite: 5.2.14(@types/node@20.12.14)(less@4.2.2)(stylus@0.64.0) @@ -46299,7 +46280,7 @@ packages: hasBin: true dependencies: cac: 6.7.14 - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) pathe: 1.1.2 picocolors: 1.1.1 vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) @@ -46652,7 +46633,7 @@ packages: peerDependencies: eslint: '>=6.0.0' dependencies: - debug: 4.4.0(supports-color@8.1.1) + debug: 4.4.0(supports-color@5.5.0) eslint: 8.57.1 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -46872,7 +46853,7 @@ packages: on-finished: 2.4.1 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /webpack-dev-server@5.0.4(webpack@5.93.0): resolution: {integrity: sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==} @@ -47025,7 +47006,7 @@ packages: dependencies: html-webpack-plugin: 5.6.2(@rspack/core@1.0.8)(webpack@5.93.0) typed-assert: 1.0.9 - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) + webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3)(webpack@5.97.1): resolution: {integrity: sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==} From c7687ead062f09173632e583bd56b966dd21311e Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Mar 2025 17:21:11 -0800 Subject: [PATCH 15/21] fix(enhanced): add webpack sources to package --- pnpm-lock.yaml | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0decb7d6f31..3a0101c7ce4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2227,6 +2227,9 @@ importers: webpack: specifier: ^5.0.0 version: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) + webpack-sources: + specifier: ^3.2.3 + version: 3.2.3 devDependencies: '@module-federation/webpack-bundler-runtime': specifier: workspace:* @@ -3574,8 +3577,8 @@ packages: resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/traverse': 7.26.9(supports-color@5.5.0) - '@babel/types': 7.26.9 + '@babel/traverse': 7.25.9(supports-color@5.5.0) + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -4107,7 +4110,7 @@ packages: dependencies: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color dev: true @@ -4120,7 +4123,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color @@ -5218,7 +5221,7 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.25.7) - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5235,7 +5238,7 @@ packages: '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7600,6 +7603,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/traverse@7.25.9(supports-color@5.5.0): + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.9 + '@babel/template': 7.25.9 + '@babel/types': 7.26.9 + debug: 4.4.0(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + /@babel/traverse@7.26.9: resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} engines: {node: '>=6.9.0'} @@ -34353,6 +34370,8 @@ packages: peerDependenciesMeta: webpack: optional: true + webpack-sources: + optional: true dependencies: webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) webpack-sources: 3.2.3 From c63e26d7a9b88307fb97d69eaa4d5145169f23b5 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 5 Mar 2025 22:24:22 -0800 Subject: [PATCH 16/21] fix(enhanced): use webpack sources from internal webpack obj --- packages/enhanced/package.json | 3 +- .../runtime/EmbedFederationRuntimePlugin.ts | 3 +- pnpm-lock.yaml | 61 +++++++++---------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/packages/enhanced/package.json b/packages/enhanced/package.json index e14885eb3b8..aade1f20ae4 100644 --- a/packages/enhanced/package.json +++ b/packages/enhanced/package.json @@ -97,7 +97,6 @@ "@module-federation/runtime-tools": "workspace:*", "@module-federation/sdk": "workspace:*", "btoa": "^1.2.1", - "upath": "2.0.1", - "webpack-sources": "^3.2.3" + "upath": "2.0.1" } } diff --git a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts index 8ed75bb2698..66a919ac503 100644 --- a/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts +++ b/packages/enhanced/src/lib/container/runtime/EmbedFederationRuntimePlugin.ts @@ -5,7 +5,6 @@ import type { Compiler, Chunk, Compilation } from 'webpack'; import { getFederationGlobalScope } from './utils'; import ContainerEntryDependency from '../ContainerEntryDependency'; import FederationRuntimeDependency from './FederationRuntimeDependency'; -import { ConcatSource } from 'webpack-sources'; const { RuntimeGlobals } = require( normalizeWebpackPath('webpack'), @@ -98,7 +97,7 @@ class EmbedFederationRuntimePlugin { } // Otherwise, append a startup call. - return new ConcatSource( + return new compiler.webpack.sources.ConcatSource( startupSource, '\n// Custom hook: appended startup call because none was added automatically\n', `${RuntimeGlobals.startup}();\n`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a0101c7ce4..71da6086c33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2227,9 +2227,6 @@ importers: webpack: specifier: ^5.0.0 version: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) - webpack-sources: - specifier: ^3.2.3 - version: 3.2.3 devDependencies: '@module-federation/webpack-bundler-runtime': specifier: workspace:* @@ -3076,7 +3073,7 @@ packages: '@babel/traverse': 7.25.7 '@babel/types': 7.25.7 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3973,7 +3970,7 @@ packages: dependencies: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color dev: true @@ -3986,7 +3983,7 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color @@ -5830,7 +5827,7 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.25.7) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color dev: true @@ -5845,7 +5842,7 @@ packages: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color @@ -6506,7 +6503,7 @@ packages: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.7) - '@babel/types': 7.26.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color dev: true @@ -6522,7 +6519,7 @@ packages: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.25.8) - '@babel/types': 7.26.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color dev: true @@ -6538,7 +6535,7 @@ packages: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/types': 7.26.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color @@ -6553,7 +6550,7 @@ packages: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.9) - '@babel/types': 7.26.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color dev: true @@ -18716,7 +18713,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 @@ -18733,7 +18730,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) dir-glob: 3.0.1 execa: 5.1.1 lodash: 4.17.21 @@ -18756,7 +18753,7 @@ packages: '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) dir-glob: 3.0.1 globby: 14.0.2 http-proxy-agent: 7.0.2 @@ -20711,7 +20708,7 @@ packages: '@swc-node/sourcemap-support': 0.5.1 '@swc/core': 1.7.26(@swc/helpers@0.5.13) colorette: 2.0.20 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) oxc-resolver: 1.12.0 pirates: 4.0.6 tslib: 2.6.3 @@ -22166,7 +22163,7 @@ packages: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.2) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) eslint: 8.57.1 typescript: 5.5.2 transitivePeerDependencies: @@ -22772,7 +22769,7 @@ packages: peerDependencies: vitest: 1.6.0 dependencies: - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -22793,7 +22790,7 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -24560,7 +24557,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.24.0 - caniuse-lite: 1.0.30001695 + caniuse-lite: 1.0.30001668 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -25612,9 +25609,6 @@ packages: /caniuse-lite@1.0.30001668: resolution: {integrity: sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==} - /caniuse-lite@1.0.30001695: - resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} - /caniuse-lite@1.0.30001700: resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} @@ -27638,7 +27632,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 - dev: true /debug@4.3.7(supports-color@9.3.1): resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} @@ -27651,6 +27644,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 9.3.1 + dev: true /debug@4.4.0(supports-color@5.5.0): resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} @@ -34676,7 +34670,7 @@ packages: engines: {node: '>=8.0'} dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) flatted: 3.3.1 rfdc: 1.4.1 streamroller: 3.1.5 @@ -44697,6 +44691,7 @@ packages: /supports-color@9.3.1: resolution: {integrity: sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q==} engines: {node: '>=12'} + dev: true /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} @@ -45946,7 +45941,7 @@ packages: bundle-require: 4.2.1(esbuild@0.18.20) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.18.20 execa: 5.1.1 globby: 11.1.0 @@ -45988,7 +45983,7 @@ packages: cac: 6.7.14 chokidar: 3.6.0 consola: 3.2.3 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) esbuild: 0.23.0 execa: 5.1.1 joycon: 3.1.1 @@ -46802,7 +46797,7 @@ packages: compression: 1.7.4 cookies: 0.9.1 cors: 2.8.5 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) envinfo: 7.11.0 express: 4.18.2 express-rate-limit: 5.5.1 @@ -46968,7 +46963,7 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.12 @@ -46995,7 +46990,7 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.12 @@ -47015,7 +47010,7 @@ packages: vite: optional: true dependencies: - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 2.1.2(typescript@5.5.2) vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) @@ -47185,7 +47180,7 @@ packages: acorn-walk: 8.3.4 cac: 6.7.14 chai: 4.5.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 @@ -47242,7 +47237,7 @@ packages: '@vitest/utils': 1.6.0 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.3.7(supports-color@9.3.1) + debug: 4.3.7(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 From 26845a3f7163a44c0f013a2f18e0eddd40c0d65a Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Mar 2025 11:35:48 -0800 Subject: [PATCH 17/21] fix(enhanced): use webpack sources from internal webpack obj --- compare-build-sizes.sh | 105 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 compare-build-sizes.sh diff --git a/compare-build-sizes.sh b/compare-build-sizes.sh new file mode 100644 index 00000000000..5e5469dcad5 --- /dev/null +++ b/compare-build-sizes.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# Exit on error +set -e + +# Function to get current branch name +get_current_branch() { + git rev-parse --abbrev-ref HEAD +} + +# Function to get size of a directory +get_dir_size() { + du -sb "$1" | cut -f1 +} + +# Function to build and measure +build_and_measure() { + local branch=$1 + local output_dir="build_output_${branch}" + + echo "Building on branch: $branch" + + # Checkout branch + git checkout "$branch" + + # Clean install dependencies + echo "Installing dependencies..." + pnpm install + + # Build packages + echo "Building packages..." + pnpm build:pkg + + # Build apps + echo "Building apps..." + pnpm app:next:build + + # Create output directory + mkdir -p "$output_dir" + + # Find all dist directories and measure their sizes + echo "Measuring build sizes..." + find . -type d -name "dist" | while read -r dist_dir; do + local size=$(get_dir_size "$dist_dir") + local relative_path=$(realpath --relative-to=. "$dist_dir") + echo "$relative_path: $size bytes" >> "$output_dir/sizes.txt" + done + + echo "Build and measurement complete for $branch" +} + +# Store current branch +CURRENT_BRANCH=$(get_current_branch) +MAIN_BRANCH="main" + +# Create temporary directory for results +TEMP_DIR=$(mktemp -d) +echo "Created temporary directory: $TEMP_DIR" + +# Build and measure on main branch +build_and_measure "$MAIN_BRANCH" + +# Build and measure on current branch +build_and_measure "$CURRENT_BRANCH" + +# Compare results +echo -e "\n=== Size Comparison ===" +echo "Comparing builds between $MAIN_BRANCH and $CURRENT_BRANCH" +echo "----------------------------------------" + +# Create a combined report +echo "Build Size Comparison Report" > "$TEMP_DIR/comparison.txt" +echo "Generated on: $(date)" >> "$TEMP_DIR/comparison.txt" +echo "----------------------------------------" >> "$TEMP_DIR/comparison.txt" +echo "" >> "$TEMP_DIR/comparison.txt" + +# Compare each dist directory +while IFS=: read -r path size; do + main_size=$(grep "^$path:" "build_output_$MAIN_BRANCH/sizes.txt" | cut -d' ' -f2 || echo "0") + current_size=$(grep "^$path:" "build_output_$CURRENT_BRANCH/sizes.txt" | cut -d' ' -f2 || echo "0") + + if [ "$main_size" != "0" ] || [ "$current_size" != "0" ]; then + diff=$((current_size - main_size)) + diff_percent=$((diff * 100 / main_size)) + + echo "$path:" >> "$TEMP_DIR/comparison.txt" + echo " Main branch: $(numfmt --to=iec $main_size)" >> "$TEMP_DIR/comparison.txt" + echo " Current branch: $(numfmt --to=iec $current_size)" >> "$TEMP_DIR/comparison.txt" + echo " Difference: $(numfmt --to=iec $diff) ($diff_percent%)" >> "$TEMP_DIR/comparison.txt" + echo "" >> "$TEMP_DIR/comparison.txt" + fi +done < "build_output_$CURRENT_BRANCH/sizes.txt" + +# Display results +cat "$TEMP_DIR/comparison.txt" + +# Cleanup +echo -e "\nCleaning up..." +rm -rf "build_output_$MAIN_BRANCH" "build_output_$CURRENT_BRANCH" +rm -rf "$TEMP_DIR" + +# Return to original branch +git checkout "$CURRENT_BRANCH" + +echo -e "\nComparison complete! Results have been displayed above." From 72e647569ef7e25946fc4642e4e0c7d84a0a392f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Mar 2025 11:36:34 -0800 Subject: [PATCH 18/21] fix(enhanced): use webpack sources from internal webpack obj --- compare-build-sizes.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 compare-build-sizes.sh diff --git a/compare-build-sizes.sh b/compare-build-sizes.sh old mode 100644 new mode 100755 From 4e007233cef7fa963dcd471c80eed3abd2fc8e9f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Mar 2025 12:17:56 -0800 Subject: [PATCH 19/21] fix(enhanced): use webpack sources from internal webpack obj --- .github/workflows/bundle-size.yml | 84 ++++++ compare-build-sizes.sh | 105 ------- pnpm-lock.yaml | 450 ++++++------------------------ 3 files changed, 172 insertions(+), 467 deletions(-) create mode 100644 .github/workflows/bundle-size.yml delete mode 100755 compare-build-sizes.sh diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml new file mode 100644 index 00000000000..63999c7116f --- /dev/null +++ b/.github/workflows/bundle-size.yml @@ -0,0 +1,84 @@ +name: Bundle Size Check + +on: + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.11.0 + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v3 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Build and compare sizes + uses: preactjs/compressed-size-action@v2 + with: + build-script: 'build:pkg && app:next:build' + pattern: '{./packages/**/dist/**/*.{js,mjs,cjs},./apps/**/dist/**/*.{js,mjs,cjs}}' + exclude: '{**/*.map,**/node_modules/**}' + clean-script: 'rm -rf packages/*/dist apps/*/dist' + compression: 'gzip' + save-stats: true + stats-path: 'bundle-stats.json' + + - name: Generate detailed report + uses: NejcZdovc/bundle-size-diff@v1 + with: + base_path: './bundle-stats.json' + pr_path: './bundle-stats.json' + excluded_assets: ".*\\.map$" + + - name: Comment PR + uses: actions/github-script@v7 + with: + script: | + const stats = require('./bundle-stats.json'); + const comment = `## Bundle Size Report + + ### Package Builds + ${Object.entries(stats.packages || {}) + .map(([name, size]) => `- ${name}: ${size}`) + .join('\n')} + + ### App Builds + ${Object.entries(stats.apps || {}) + .map(([name, size]) => `- ${name}: ${size}`) + .join('\n')} + + ### Total Changes + - Total Size: ${stats.total} + - Gzipped Size: ${stats.gzipped} + `; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/compare-build-sizes.sh b/compare-build-sizes.sh deleted file mode 100755 index 5e5469dcad5..00000000000 --- a/compare-build-sizes.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Function to get current branch name -get_current_branch() { - git rev-parse --abbrev-ref HEAD -} - -# Function to get size of a directory -get_dir_size() { - du -sb "$1" | cut -f1 -} - -# Function to build and measure -build_and_measure() { - local branch=$1 - local output_dir="build_output_${branch}" - - echo "Building on branch: $branch" - - # Checkout branch - git checkout "$branch" - - # Clean install dependencies - echo "Installing dependencies..." - pnpm install - - # Build packages - echo "Building packages..." - pnpm build:pkg - - # Build apps - echo "Building apps..." - pnpm app:next:build - - # Create output directory - mkdir -p "$output_dir" - - # Find all dist directories and measure their sizes - echo "Measuring build sizes..." - find . -type d -name "dist" | while read -r dist_dir; do - local size=$(get_dir_size "$dist_dir") - local relative_path=$(realpath --relative-to=. "$dist_dir") - echo "$relative_path: $size bytes" >> "$output_dir/sizes.txt" - done - - echo "Build and measurement complete for $branch" -} - -# Store current branch -CURRENT_BRANCH=$(get_current_branch) -MAIN_BRANCH="main" - -# Create temporary directory for results -TEMP_DIR=$(mktemp -d) -echo "Created temporary directory: $TEMP_DIR" - -# Build and measure on main branch -build_and_measure "$MAIN_BRANCH" - -# Build and measure on current branch -build_and_measure "$CURRENT_BRANCH" - -# Compare results -echo -e "\n=== Size Comparison ===" -echo "Comparing builds between $MAIN_BRANCH and $CURRENT_BRANCH" -echo "----------------------------------------" - -# Create a combined report -echo "Build Size Comparison Report" > "$TEMP_DIR/comparison.txt" -echo "Generated on: $(date)" >> "$TEMP_DIR/comparison.txt" -echo "----------------------------------------" >> "$TEMP_DIR/comparison.txt" -echo "" >> "$TEMP_DIR/comparison.txt" - -# Compare each dist directory -while IFS=: read -r path size; do - main_size=$(grep "^$path:" "build_output_$MAIN_BRANCH/sizes.txt" | cut -d' ' -f2 || echo "0") - current_size=$(grep "^$path:" "build_output_$CURRENT_BRANCH/sizes.txt" | cut -d' ' -f2 || echo "0") - - if [ "$main_size" != "0" ] || [ "$current_size" != "0" ]; then - diff=$((current_size - main_size)) - diff_percent=$((diff * 100 / main_size)) - - echo "$path:" >> "$TEMP_DIR/comparison.txt" - echo " Main branch: $(numfmt --to=iec $main_size)" >> "$TEMP_DIR/comparison.txt" - echo " Current branch: $(numfmt --to=iec $current_size)" >> "$TEMP_DIR/comparison.txt" - echo " Difference: $(numfmt --to=iec $diff) ($diff_percent%)" >> "$TEMP_DIR/comparison.txt" - echo "" >> "$TEMP_DIR/comparison.txt" - fi -done < "build_output_$CURRENT_BRANCH/sizes.txt" - -# Display results -cat "$TEMP_DIR/comparison.txt" - -# Cleanup -echo -e "\nCleaning up..." -rm -rf "build_output_$MAIN_BRANCH" "build_output_$CURRENT_BRANCH" -rm -rf "$TEMP_DIR" - -# Return to original branch -git checkout "$CURRENT_BRANCH" - -echo -e "\nComparison complete! Results have been displayed above." diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71da6086c33..85cfd7023f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,7 +163,7 @@ importers: version: 20.1.4(@babel/core@7.25.2)(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@types/node@18.16.9)(nx@20.1.4)(typescript@5.5.2)(verdaccio@5.29.2) '@nx/rspack': specifier: 20.1.4 - version: 20.1.4(@module-federation/enhanced@0.6.16)(@module-federation/node@2.5.21)(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@swc/helpers@0.5.13)(@types/express@4.17.21)(@types/node@18.16.9)(html-webpack-plugin@5.6.2)(less@4.2.2)(nx@20.1.4)(react-refresh@0.14.2)(typescript@5.5.2)(verdaccio@5.29.2)(webpack@5.93.0) + version: 20.1.4(@module-federation/enhanced@0.6.11)(@module-federation/node@2.5.21)(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@swc/helpers@0.5.13)(@types/express@4.17.21)(@types/node@18.16.9)(html-webpack-plugin@5.6.2)(less@4.2.2)(nx@20.1.4)(react-refresh@0.14.2)(typescript@5.5.2)(verdaccio@5.29.2)(webpack@5.93.0) '@nx/storybook': specifier: 20.1.4 version: 20.1.4(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@types/node@18.16.9)(cypress@13.15.0)(eslint@8.57.1)(nx@20.1.4)(typescript@5.5.2)(verdaccio@5.29.2) @@ -1840,10 +1840,10 @@ importers: version: 4.3.3(vite@5.2.14) '@vitejs/plugin-vue': specifier: ^5.0.4 - version: 5.1.4(vite@5.2.14)(vue@3.5.13) + version: 5.1.4(vite@5.2.14)(vue@3.5.10) '@vitejs/plugin-vue-jsx': specifier: ^4.0.0 - version: 4.0.1(vite@5.2.14)(vue@3.5.13) + version: 4.0.1(vite@5.2.14)(vue@3.5.10) jsdom: specifier: ^24.1.0 version: 24.1.3 @@ -1895,10 +1895,10 @@ importers: version: 16.11.68 '@vitejs/plugin-vue': specifier: ^5.0.4 - version: 5.1.4(vite@5.2.14)(vue@3.5.13) + version: 5.1.4(vite@5.2.14)(vue@3.5.10) '@vitejs/plugin-vue-jsx': specifier: ^4.0.0 - version: 4.0.1(vite@5.2.14)(vue@3.5.13) + version: 4.0.1(vite@5.2.14)(vue@3.5.10) typescript: specifier: ^5.2.2 version: 5.5.2 @@ -3073,7 +3073,7 @@ packages: '@babel/traverse': 7.25.7 '@babel/types': 7.25.7 convert-source-map: 2.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3237,7 +3237,7 @@ packages: dependencies: '@babel/parser': 7.26.9 '@babel/types': 7.26.9 - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 @@ -5574,9 +5574,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.25.7 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color dev: true @@ -5588,9 +5588,9 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color @@ -7971,7 +7971,7 @@ packages: '@commitlint/ensure': 19.5.0 '@commitlint/load': 19.5.0(@types/node@18.16.9)(typescript@5.5.2) '@commitlint/types': 19.5.0 - chalk: 5.3.0 + chalk: 5.4.1 commitizen: 4.3.1(@types/node@18.16.9)(typescript@5.5.2) inquirer: 9.3.7 lodash.isplainobject: 4.0.6 @@ -8003,7 +8003,7 @@ packages: engines: {node: '>=v18'} dependencies: '@commitlint/types': 19.5.0 - chalk: 5.3.0 + chalk: 5.4.1 dev: true /@commitlint/is-ignored@19.5.0: @@ -8032,7 +8032,7 @@ packages: '@commitlint/execute-rule': 19.5.0 '@commitlint/resolve-extends': 19.5.0 '@commitlint/types': 19.5.0 - chalk: 5.3.0 + chalk: 5.4.1 cosmiconfig: 9.0.0(typescript@5.5.2) cosmiconfig-typescript-loader: 5.0.0(@types/node@18.16.9)(cosmiconfig@9.0.0)(typescript@5.5.2) lodash.isplainobject: 4.0.6 @@ -8128,7 +8128,7 @@ packages: engines: {node: '>=v18'} dependencies: '@types/conventional-commits-parser': 5.0.0 - chalk: 5.3.0 + chalk: 5.4.1 dev: true /@cspotcode/source-map-support@0.8.1: @@ -8227,7 +8227,7 @@ packages: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.0 + form-data: 4.0.1 http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 @@ -10181,7 +10181,7 @@ packages: /@jridgewell/source-map@0.3.6: resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 /@jridgewell/sourcemap-codec@1.5.0: @@ -10889,7 +10889,7 @@ packages: '@modern-js/types': 2.46.1 '@modern-js/utils': 2.46.1 '@swc/helpers': 0.5.3 - acorn: 8.12.1 + acorn: 8.14.0 caniuse-lite: 1.0.30001700 css-minimizer-webpack-plugin: 5.0.1(esbuild@0.18.20)(webpack@5.93.0) cssnano: 6.0.1(postcss@8.4.31) @@ -11988,13 +11988,6 @@ packages: '@module-federation/sdk': 0.6.11 '@types/semver': 7.5.8 semver: 7.6.3 - - /@module-federation/bridge-react-webpack-plugin@0.6.16: - resolution: {integrity: sha512-AQj20lUL5fmdz4un56W3VF8naZaRDmztczl+/j4Qa69JAaUbbZK6zZJ3NEjx0cNzpiq/mGmG9Vik3V4rI/4BUA==} - dependencies: - '@module-federation/sdk': 0.6.16 - '@types/semver': 7.5.8 - semver: 7.6.3 dev: true /@module-federation/bridge-react-webpack-plugin@0.6.9: @@ -12032,18 +12025,6 @@ packages: fs-extra: 9.1.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - - /@module-federation/data-prefetch@0.6.16(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-m5SNKlAkB2FFCs2cl6LWqo6s2NZ7HuCrp6QrrMzuKjB6EddvKojVQxOzrWdcMLs1vESy6fyU4M4U7PxSojw6Ww==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - dependencies: - '@module-federation/runtime': 0.6.16 - '@module-federation/sdk': 0.6.16 - fs-extra: 9.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) dev: true /@module-federation/data-prefetch@0.6.9(react-dom@18.3.1)(react@18.3.1): @@ -12107,40 +12088,7 @@ packages: lodash.clonedeepwith: 4.5.0 log4js: 6.9.1 node-schedule: 2.1.1 - rambda: 9.4.2 - typescript: 5.5.2 - vue-tsc: 2.1.6(typescript@5.5.2) - ws: 8.18.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - - /@module-federation/dts-plugin@0.6.16(typescript@5.5.2)(vue-tsc@2.1.6): - resolution: {integrity: sha512-XM6+EYVrS2Q/ZW0u9cH0sJT5t5SQHRjzmW7JWdPv0+wKGCA15WtRMc55boM4Wan7jXJZf+JeD5QLXWiSjaJdnw==} - peerDependencies: - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - peerDependenciesMeta: - vue-tsc: - optional: true - dependencies: - '@module-federation/error-codes': 0.6.14 - '@module-federation/managers': 0.6.16 - '@module-federation/sdk': 0.6.16 - '@module-federation/third-party-dts-extractor': 0.6.16 - adm-zip: 0.5.16 - ansi-colors: 4.1.3 - axios: 1.7.7 - chalk: 3.0.0 - fs-extra: 9.1.0 - isomorphic-ws: 5.0.0(ws@8.18.0) - koa: 2.15.3 - lodash.clonedeepwith: 4.5.0 - log4js: 6.9.1 - node-schedule: 2.1.1 - rambda: 9.4.2 + rambda: 9.3.0 typescript: 5.5.2 vue-tsc: 2.1.6(typescript@5.5.2) ws: 8.18.0 @@ -12278,41 +12226,6 @@ packages: typescript: 5.5.2 upath: 2.0.1 vue-tsc: 2.1.6(typescript@5.5.2) - webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) - transitivePeerDependencies: - - bufferutil - - debug - - react - - react-dom - - supports-color - - utf-8-validate - - /@module-federation/enhanced@0.6.16(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0): - resolution: {integrity: sha512-5MqA35WGvPmCScT/xNnheR4RBa2oYHkLpeVjOA0xg0PeUTC7aSfGRLsntzFeyzLITSjbVTupK2YwmjiZr3Z0LQ==} - peerDependencies: - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - webpack: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - vue-tsc: - optional: true - webpack: - optional: true - dependencies: - '@module-federation/bridge-react-webpack-plugin': 0.6.16 - '@module-federation/data-prefetch': 0.6.16(react-dom@18.3.1)(react@18.3.1) - '@module-federation/dts-plugin': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/managers': 0.6.16 - '@module-federation/manifest': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/rspack': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/runtime-tools': 0.6.16 - '@module-federation/sdk': 0.6.16 - btoa: 1.2.1 - typescript: 5.5.2 - upath: 2.0.1 - vue-tsc: 2.1.6(typescript@5.5.2) webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) transitivePeerDependencies: - bufferutil @@ -12431,10 +12344,6 @@ packages: - utf-8-validate dev: true - /@module-federation/error-codes@0.6.14: - resolution: {integrity: sha512-ik+ezloFkxmE5atqTUG9lRr9xV5EcKDjH+MZba2IJQT5cZIM6o2ThTC45E013N4SCleaGxBtIGoPLZJzT4xa0Q==} - dev: true - /@module-federation/error-codes@0.7.6: resolution: {integrity: sha512-XVzX/sRFj1h5JvOOVMoFppxq0t1t3o/AlEICHgWX+dybIwJgz9g4gihZOWVZfz5/xsKGcUwdH5X7Z2nkuYhJEw==} dev: true @@ -12453,13 +12362,6 @@ packages: '@module-federation/sdk': 0.6.11 find-pkg: 2.0.0 fs-extra: 9.1.0 - - /@module-federation/managers@0.6.16: - resolution: {integrity: sha512-9oqJT0F61GhaFE4EFgJjVyQlD8ohXxMJBS9UGCKC6nHd3+PI4NBWGN2D+alBOwvwtt3LhtssbVH8H8HZEM1GnQ==} - dependencies: - '@module-federation/sdk': 0.6.16 - find-pkg: 2.0.0 - fs-extra: 9.1.0 dev: true /@module-federation/managers@0.6.9: @@ -12501,22 +12403,6 @@ packages: - typescript - utf-8-validate - vue-tsc - - /@module-federation/manifest@0.6.16(typescript@5.5.2)(vue-tsc@2.1.6): - resolution: {integrity: sha512-YjOk+1uR6E5qIEWiy35IrMyEy+rDGI5nJd+6MQobkXG40DK94mdPxJ7TSCozj/bpZ9SadCxXRCkMiE/gTkryAQ==} - dependencies: - '@module-federation/dts-plugin': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/managers': 0.6.16 - '@module-federation/sdk': 0.6.16 - chalk: 3.0.0 - find-pkg: 2.0.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - typescript - - utf-8-validate - - vue-tsc dev: true /@module-federation/manifest@0.6.9(typescript@5.5.2)(vue-tsc@2.1.6): @@ -12629,31 +12515,6 @@ packages: - debug - supports-color - utf-8-validate - - /@module-federation/rspack@0.6.16(typescript@5.5.2)(vue-tsc@2.1.6): - resolution: {integrity: sha512-9nQAyw7QvgXJYPTQseyQ31qQtSlo0VsppQOyFLstLITzgWWugN7cN8cGAriUKYBI78THuX+lp1mdgsNTBvxJPA==} - peerDependencies: - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - peerDependenciesMeta: - typescript: - optional: true - vue-tsc: - optional: true - dependencies: - '@module-federation/bridge-react-webpack-plugin': 0.6.16 - '@module-federation/dts-plugin': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/managers': 0.6.16 - '@module-federation/manifest': 0.6.16(typescript@5.5.2)(vue-tsc@2.1.6) - '@module-federation/runtime-tools': 0.6.16 - '@module-federation/sdk': 0.6.16 - typescript: 5.5.2 - vue-tsc: 2.1.6(typescript@5.5.2) - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate dev: true /@module-federation/rspack@0.6.9(typescript@5.5.2)(vue-tsc@2.1.6): @@ -12759,12 +12620,6 @@ packages: dependencies: '@module-federation/runtime': 0.6.11 '@module-federation/webpack-bundler-runtime': 0.6.11 - - /@module-federation/runtime-tools@0.6.16: - resolution: {integrity: sha512-AIaxnx99tVYppYCgdJQz43mrGZ2pPJtC7YEIjuQV+UnSORj+d/GOIqF88MDx3i7siFcQ4zrT5BVtEWhXcJdv0g==} - dependencies: - '@module-federation/runtime': 0.6.16 - '@module-federation/webpack-bundler-runtime': 0.6.16 dev: true /@module-federation/runtime-tools@0.6.9: @@ -12816,12 +12671,6 @@ packages: resolution: {integrity: sha512-UTuavwCybLftAe4VT7cCqj+BVNlZwda/xmqIPAeYX14o7gkYFyA6zkxOQqfNCaDnTMR/KBk6EvE49yA6/ht9UQ==} dependencies: '@module-federation/sdk': 0.6.11 - - /@module-federation/runtime@0.6.16: - resolution: {integrity: sha512-3oFDRkolGwiXuQz+wzX3YzBWI9so0+K05YRf0TEdJguj3W/v/AMrBCz7W4c4O/wSK45Kuqd4lHKhCyKWRPyhOw==} - dependencies: - '@module-federation/error-codes': 0.6.14 - '@module-federation/sdk': 0.6.16 dev: true /@module-federation/runtime@0.6.9: @@ -12865,12 +12714,6 @@ packages: /@module-federation/sdk@0.6.11: resolution: {integrity: sha512-Fj2ws9yL6mGAki9GdurcrIhdSg0L2Kfw7L6Dej/DidzAU4bwa3MT0s+87BPuOYFgm2UTMN3g+UrElC2NhsuulQ==} - /@module-federation/sdk@0.6.16: - resolution: {integrity: sha512-rzQH/v9bVc032lzV4j1IGYRc5gszwzBevYBBDJf3oNLwkY2kIDUJ99OWvq3aaPJoE0jEWPVe3K5iNc+dZe4tMQ==} - dependencies: - isomorphic-rslog: 0.0.5 - dev: true - /@module-federation/sdk@0.6.9: resolution: {integrity: sha512-xmTxb9LgncxPGsBrN6AT/+aHnFGv8swbeNl0PcSeVbXTGLu3Gp7j+5J+AhJoWNB++SLguRwBd8LjB1d8mNKLDg==} dev: false @@ -12899,13 +12742,6 @@ packages: find-pkg: 2.0.0 fs-extra: 9.1.0 resolve: 1.22.8 - - /@module-federation/third-party-dts-extractor@0.6.16: - resolution: {integrity: sha512-F4W8QBlPLNY22TGjUWA+FyFYN6wVgGKhefd170A8BOqv2gB1yhm6OIEmDnO6TwfDfQQebVCcAu23AzLzgS5eCg==} - dependencies: - find-pkg: 2.0.0 - fs-extra: 9.1.0 - resolve: 1.22.8 dev: true /@module-federation/third-party-dts-extractor@0.6.9: @@ -12979,12 +12815,6 @@ packages: dependencies: '@module-federation/runtime': 0.6.11 '@module-federation/sdk': 0.6.11 - - /@module-federation/webpack-bundler-runtime@0.6.16: - resolution: {integrity: sha512-Tpi251DApEaQ62KCaJCh1RU1SZTUcVh8lx2zotn/YOMZdw83IzYu3PYYA1V0Eg5jVe6I2GmGH52pJPCtwbgjqA==} - dependencies: - '@module-federation/runtime': 0.6.16 - '@module-federation/sdk': 0.6.16 dev: true /@module-federation/webpack-bundler-runtime@0.6.9: @@ -14351,13 +14181,13 @@ packages: - verdaccio dev: true - /@nx/rspack@20.1.4(@module-federation/enhanced@0.6.16)(@module-federation/node@2.5.21)(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@swc/helpers@0.5.13)(@types/express@4.17.21)(@types/node@18.16.9)(html-webpack-plugin@5.6.2)(less@4.2.2)(nx@20.1.4)(react-refresh@0.14.2)(typescript@5.5.2)(verdaccio@5.29.2)(webpack@5.93.0): + /@nx/rspack@20.1.4(@module-federation/enhanced@0.6.11)(@module-federation/node@2.5.21)(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@swc/helpers@0.5.13)(@types/express@4.17.21)(@types/node@18.16.9)(html-webpack-plugin@5.6.2)(less@4.2.2)(nx@20.1.4)(react-refresh@0.14.2)(typescript@5.5.2)(verdaccio@5.29.2)(webpack@5.93.0): resolution: {integrity: sha512-s1CJLfAsR6Z47LIqBNkL8/SL8VaDBqdUZF0u6WGJkS2IacpSh2IXadEXxh5wGVA+19sukwWw/IROkVXwLCf1yw==} peerDependencies: '@module-federation/enhanced': ~0.6.0 '@module-federation/node': ~2.5.10 dependencies: - '@module-federation/enhanced': 0.6.16(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0) + '@module-federation/enhanced': 0.6.11(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0) '@module-federation/node': 2.5.21(next@14.2.16)(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0) '@nx/devkit': 20.1.4(nx@20.1.4) '@nx/js': 20.1.4(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@types/node@18.16.9)(nx@20.1.4)(typescript@5.5.2)(verdaccio@5.29.2) @@ -14511,7 +14341,7 @@ packages: resolution: {integrity: sha512-ucxJn9q/KboQ4ywtODmOYD9ac9FczdLd/1WDAPctxERuq71bfkwGmZGUzH3fDqolinek0kAIhn6ci3ww2/Qs1A==} dependencies: '@babel/core': 7.26.0 - '@module-federation/enhanced': 0.6.11(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0) + '@module-federation/enhanced': 0.6.9(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.2)(vue-tsc@2.1.6)(webpack@5.93.0) '@module-federation/sdk': 0.6.11 '@nx/devkit': 20.1.1(nx@20.1.4) '@nx/js': 20.1.1(@swc-node/register@1.10.9)(@swc/core@1.7.26)(@types/node@20.12.14)(nx@20.1.4)(typescript@5.5.2)(verdaccio@5.29.2) @@ -15171,7 +15001,7 @@ packages: resolve-from: 5.0.0 rollup: 2.79.2 semver: 7.6.3 - terser: 5.34.1 + terser: 5.37.0 v8-compile-cache: 2.4.0 zod: 3.23.8 transitivePeerDependencies: @@ -16327,7 +16157,7 @@ packages: estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 - magic-string: 0.30.12 + magic-string: 0.30.17 rollup: 4.24.0 dev: true @@ -18671,7 +18501,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - fs-extra: 11.2.0 + fs-extra: 11.3.0 lodash: 4.17.21 semantic-release: 24.2.3(typescript@5.5.2) dev: true @@ -18713,7 +18543,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) execa: 5.1.1 lodash: 4.17.21 parse-json: 5.2.0 @@ -18730,7 +18560,7 @@ packages: dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) dir-glob: 3.0.1 execa: 5.1.1 lodash: 4.17.21 @@ -18753,7 +18583,7 @@ packages: '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) dir-glob: 3.0.1 globby: 14.0.2 http-proxy-agent: 7.0.2 @@ -18804,7 +18634,7 @@ packages: '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 execa: 8.0.1 - fs-extra: 11.2.0 + fs-extra: 11.3.0 lodash-es: 4.17.21 nerf-dart: 1.0.0 normalize-url: 8.0.1 @@ -18987,7 +18817,7 @@ packages: '@storybook/global': 5.0.0 '@storybook/react-dom-shim': 8.3.3(react-dom@18.3.1)(react@18.3.1)(storybook@8.5.6) '@types/react': 18.3.11 - fs-extra: 11.2.0 + fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rehype-external-links: 3.0.0 @@ -19009,7 +18839,7 @@ packages: '@storybook/global': 5.0.0 '@storybook/react-dom-shim': 8.3.5(react-dom@18.3.1)(react@18.3.1)(storybook@8.3.5) '@types/react': 18.3.11 - fs-extra: 11.2.0 + fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rehype-external-links: 3.0.0 @@ -19031,7 +18861,7 @@ packages: '@storybook/global': 5.0.0 '@storybook/react-dom-shim': 8.3.5(react-dom@18.3.1)(react@18.3.1)(storybook@8.4.2) '@types/react': 18.3.11 - fs-extra: 11.2.0 + fs-extra: 11.3.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) rehype-external-links: 3.0.0 @@ -19361,7 +19191,7 @@ packages: es-module-lexer: 1.6.0 express: 4.21.1 fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.5.2)(webpack@5.93.0) - fs-extra: 11.2.0 + fs-extra: 11.3.0 html-webpack-plugin: 5.6.2(@rspack/core@1.0.8)(webpack@5.93.0) magic-string: 0.30.17 path-browserify: 1.0.1 @@ -19592,7 +19422,7 @@ packages: file-system-cache: 2.3.0 find-cache-dir: 3.3.2 find-up: 5.0.0 - fs-extra: 11.2.0 + fs-extra: 11.3.0 glob: 10.4.5 handlebars: 4.7.8 lazy-universal-dotenv: 4.0.0 @@ -19888,7 +19718,7 @@ packages: '@babel/types': 7.26.9 '@storybook/csf': 0.1.11 '@storybook/types': 8.1.11 - fs-extra: 11.2.0 + fs-extra: 11.3.0 recast: 0.23.9 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -20045,7 +19875,7 @@ packages: babel-loader: 9.2.1(@babel/core@7.25.7)(webpack@5.93.0) css-loader: 6.11.0(@rspack/core@1.0.8)(webpack@5.93.0) find-up: 5.0.0 - fs-extra: 11.2.0 + fs-extra: 11.3.0 image-size: 1.1.1 loader-utils: 3.3.1 next: 14.2.16(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1) @@ -20115,7 +19945,7 @@ packages: '@types/node': 22.7.4 '@types/semver': 7.5.8 find-up: 5.0.0 - fs-extra: 11.2.0 + fs-extra: 11.3.0 magic-string: 0.30.17 react: 18.3.1 react-docgen: 7.1.0 @@ -20708,7 +20538,7 @@ packages: '@swc-node/sourcemap-support': 0.5.1 '@swc/core': 1.7.26(@swc/helpers@0.5.13) colorette: 2.0.20 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) oxc-resolver: 1.12.0 pirates: 4.0.6 tslib: 2.6.3 @@ -22035,7 +21865,7 @@ packages: /@types/ws@8.5.12: resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} dependencies: - '@types/node': 18.16.9 + '@types/node': 20.12.14 /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -22163,7 +21993,7 @@ packages: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.2) '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) eslint: 8.57.1 typescript: 5.5.2 transitivePeerDependencies: @@ -22726,22 +22556,6 @@ packages: - supports-color dev: true - /@vitejs/plugin-vue-jsx@4.0.1(vite@5.2.14)(vue@3.5.13): - resolution: {integrity: sha512-7mg9HFGnFHMEwCdB6AY83cVK4A6sCqnrjFYF4WIlebYAQVVJ/sC/CiTruVdrRlhrFoeZ8rlMxY9wYpPTIRhhAg==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^5.0.0 - vue: ^3.0.0 - dependencies: - '@babel/core': 7.25.2 - '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2) - '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.25.2) - vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) - vue: 3.5.13(typescript@5.5.2) - transitivePeerDependencies: - - supports-color - dev: true - /@vitejs/plugin-vue@5.1.4(vite@5.2.14)(vue@3.5.10): resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -22753,23 +22567,12 @@ packages: vue: 3.5.10(typescript@5.5.2) dev: true - /@vitejs/plugin-vue@5.1.4(vite@5.2.14)(vue@3.5.13): - resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^5.0.0 - vue: ^3.2.25 - dependencies: - vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) - vue: 3.5.13(typescript@5.5.2) - dev: true - /@vitest/coverage-istanbul@1.6.0(vitest@1.6.0): resolution: {integrity: sha512-h/BwpXehkkS0qsNCS00QxiupAqVkNi0WT19BR0dQvlge5oHghoSVLx63fABYFoKxVb7Ue7+k6V2KokmQ1zdMpg==} peerDependencies: vitest: 1.6.0 dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 @@ -22790,7 +22593,7 @@ packages: dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -23008,7 +22811,7 @@ packages: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.25.9 '@babel/parser': 7.26.9 - '@vue/compiler-sfc': 3.5.10 + '@vue/compiler-sfc': 3.5.13 transitivePeerDependencies: - supports-color dev: true @@ -23030,7 +22833,6 @@ packages: entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - dev: true /@vue/compiler-dom@3.5.10: resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==} @@ -23043,19 +22845,18 @@ packages: dependencies: '@vue/compiler-core': 3.5.13 '@vue/shared': 3.5.13 - dev: true /@vue/compiler-sfc@3.5.10: resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==} dependencies: - '@babel/parser': 7.26.2 + '@babel/parser': 7.26.9 '@vue/compiler-core': 3.5.10 '@vue/compiler-dom': 3.5.10 '@vue/compiler-ssr': 3.5.10 '@vue/shared': 3.5.10 estree-walker: 2.0.2 - magic-string: 0.30.12 - postcss: 8.4.47 + magic-string: 0.30.17 + postcss: 8.5.2 source-map-js: 1.2.1 /@vue/compiler-sfc@3.5.13: @@ -23104,8 +22905,8 @@ packages: dependencies: '@volar/language-core': 1.11.1 '@volar/source-map': 1.11.1 - '@vue/compiler-dom': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 computeds: 0.0.1 minimatch: 9.0.5 muggle-string: 0.3.1 @@ -23123,9 +22924,9 @@ packages: optional: true dependencies: '@volar/language-core': 2.4.5 - '@vue/compiler-dom': 3.5.10 + '@vue/compiler-dom': 3.5.13 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.10 + '@vue/shared': 3.5.13 computeds: 0.0.1 minimatch: 9.0.5 muggle-string: 0.4.1 @@ -23137,25 +22938,12 @@ packages: dependencies: '@vue/shared': 3.5.10 - /@vue/reactivity@3.5.13: - resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} - dependencies: - '@vue/shared': 3.5.13 - dev: true - /@vue/runtime-core@3.5.10: resolution: {integrity: sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==} dependencies: '@vue/reactivity': 3.5.10 '@vue/shared': 3.5.10 - /@vue/runtime-core@3.5.13: - resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} - dependencies: - '@vue/reactivity': 3.5.13 - '@vue/shared': 3.5.13 - dev: true - /@vue/runtime-dom@3.5.10: resolution: {integrity: sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==} dependencies: @@ -23164,15 +22952,6 @@ packages: '@vue/shared': 3.5.10 csstype: 3.1.3 - /@vue/runtime-dom@3.5.13: - resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} - dependencies: - '@vue/reactivity': 3.5.13 - '@vue/runtime-core': 3.5.13 - '@vue/shared': 3.5.13 - csstype: 3.1.3 - dev: true - /@vue/server-renderer@3.5.10(vue@3.5.10): resolution: {integrity: sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==} peerDependencies: @@ -23182,22 +22961,11 @@ packages: '@vue/shared': 3.5.10 vue: 3.5.10(typescript@5.5.2) - /@vue/server-renderer@3.5.13(vue@3.5.13): - resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} - peerDependencies: - vue: 3.5.13 - dependencies: - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 - vue: 3.5.13(typescript@5.5.2) - dev: true - /@vue/shared@3.5.10: resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} /@vue/shared@3.5.13: resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - dev: true /@vue/tsconfig@0.5.1: resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==} @@ -23646,7 +23414,7 @@ packages: /acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 dev: true @@ -23681,20 +23449,12 @@ packages: acorn: 7.4.1 dev: true - /acorn-jsx@5.3.2(acorn@8.12.1): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.12.1 - /acorn-jsx@5.3.2(acorn@8.14.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.14.0 - dev: false /acorn-loose@8.4.0: resolution: {integrity: sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==} @@ -23711,7 +23471,7 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} dependencies: - acorn: 8.12.1 + acorn: 8.14.0 /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} @@ -25692,6 +25452,7 @@ packages: /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false /chalk@5.4.1: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} @@ -27632,6 +27393,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 + dev: true /debug@4.3.7(supports-color@9.3.1): resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} @@ -27644,7 +27406,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 9.3.1 - dev: true /debug@4.4.0(supports-color@5.5.0): resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} @@ -29467,8 +29228,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 /esprima@4.0.1: @@ -30651,6 +30412,7 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: false /fs-extra@11.3.0: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} @@ -30659,7 +30421,6 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - dev: true /fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} @@ -33029,11 +32790,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /isomorphic-rslog@0.0.5: - resolution: {integrity: sha512-pkU3vvajRJ0LKLaMFy8Cj7ElbFUdkQKVhUk+DQsVCYsLW4uulU65C2s3l+Sm5OtiOwprzkYYcAIJa/COwCYHWA==} - engines: {node: '>=14.17.6'} - dev: true - /isomorphic-rslog@0.0.6: resolution: {integrity: sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==} engines: {node: '>=14.17.6'} @@ -33890,7 +33646,7 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.12.1 + acorn: 8.14.0 acorn-globals: 7.0.1 cssom: 0.5.0 cssstyle: 2.3.0 @@ -33898,7 +33654,7 @@ packages: decimal.js: 10.4.3 domexception: 4.0.0 escodegen: 2.1.0 - form-data: 4.0.0 + form-data: 4.0.1 html-encoding-sniffer: 3.0.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 @@ -34024,7 +33780,7 @@ packages: resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.12.1 + acorn: 8.14.0 eslint-visitor-keys: 3.4.3 espree: 9.6.1 semver: 7.6.3 @@ -34670,7 +34426,7 @@ packages: engines: {node: '>=8.0'} dependencies: date-format: 4.0.14 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) flatted: 3.3.1 rfdc: 1.4.1 streamroller: 3.1.5 @@ -34802,12 +34558,12 @@ packages: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + dev: true /magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 - dev: true /magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -39619,9 +39375,6 @@ packages: /rambda@9.3.0: resolution: {integrity: sha512-cl/7DCCKNxmsbc0dXZTJTY08rvDdzLhVfE6kPBson1fWzDapLzv0RKSzjpmAqP53fkQqAvq05gpUVHTrUNsuxg==} - /rambda@9.4.2: - resolution: {integrity: sha512-++euMfxnl7OgaEKwXh9QqThOjMeta2HH001N1v4mYQzBjJBnmXBh2BCK6dZAbICFVXOFUVD3xFG0R3ZPU0mxXw==} - /ramda@0.29.0: resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} dev: true @@ -42559,7 +42312,7 @@ packages: /rspack-plugin-virtual-module@0.1.13: resolution: {integrity: sha512-VC0HiVHH6dtGfTgfpbDgVTt6LlYv+uAg9CWGWAR5lBx9FbKPEZeGz7iRUUP8vMymx+PGI8ps0u4a25dne0rtuQ==} dependencies: - fs-extra: 11.2.0 + fs-extra: 11.3.0 dev: false /rspress-plugin-annotation-words@0.0.1(rspress@1.34.1): @@ -43999,8 +43752,8 @@ packages: cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 es-module-lexer: 1.5.4 - fs-extra: 11.2.0 - magic-string: 0.30.12 + fs-extra: 11.3.0 + magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 rsbuild-plugin-html-minifier-terser: 1.1.1(@rsbuild/core@1.2.8) @@ -44400,7 +44153,7 @@ packages: /strip-literal@1.3.0: resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} dependencies: - acorn: 8.12.1 + acorn: 8.14.0 dev: true /strip-literal@2.1.0: @@ -44691,7 +44444,6 @@ packages: /supports-color@9.3.1: resolution: {integrity: sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q==} engines: {node: '>=12'} - dev: true /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} @@ -44971,7 +44723,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.17.19) /terser-webpack-plugin@5.3.10(@swc/core@1.7.26)(esbuild@0.18.20)(webpack@5.93.0): @@ -44996,7 +44748,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.18.20) /terser-webpack-plugin@5.3.10(@swc/core@1.7.26)(esbuild@0.18.20)(webpack@5.95.0): @@ -45021,7 +44773,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.95.0(@swc/core@1.7.26)(esbuild@0.18.20) dev: true @@ -45047,7 +44799,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.75.0(@swc/core@1.7.26)(esbuild@0.24.0) dev: true @@ -45073,7 +44825,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.93.0(@swc/core@1.7.26)(esbuild@0.24.0) /terser-webpack-plugin@5.3.10(@swc/core@1.7.26)(esbuild@0.24.0)(webpack@5.95.0): @@ -45098,7 +44850,7 @@ packages: jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 + terser: 5.37.0 webpack: 5.95.0(@swc/core@1.7.26)(esbuild@0.24.0) dev: true @@ -45206,23 +44958,13 @@ packages: webpack: 5.95.0(@swc/core@1.7.26)(esbuild@0.18.20) dev: true - /terser@5.34.1: - resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 - commander: 2.20.3 - source-map-support: 0.5.21 - /terser@5.37.0: resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} engines: {node: '>=10'} hasBin: true dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -45727,7 +45469,7 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 18.16.9 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -45759,7 +45501,7 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 18.16.9 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -45791,7 +45533,7 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.12.14 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -45823,7 +45565,7 @@ packages: '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 '@types/node': 20.12.14 - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -45941,7 +45683,7 @@ packages: bundle-require: 4.2.1(esbuild@0.18.20) cac: 6.7.14 chokidar: 3.6.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) esbuild: 0.18.20 execa: 5.1.1 globby: 11.1.0 @@ -45983,7 +45725,7 @@ packages: cac: 6.7.14 chokidar: 3.6.0 consola: 3.2.3 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) esbuild: 0.23.0 execa: 5.1.1 joycon: 3.1.1 @@ -46797,7 +46539,7 @@ packages: compression: 1.7.4 cookies: 0.9.1 cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) envinfo: 7.11.0 express: 4.18.2 express-rate-limit: 5.5.1 @@ -46963,7 +46705,7 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) kolorist: 1.8.0 local-pkg: 0.5.0 magic-string: 0.30.12 @@ -46990,10 +46732,10 @@ packages: '@volar/typescript': 2.4.5 '@vue/language-core': 2.1.6(typescript@5.5.2) compare-versions: 6.1.1 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.4.0(supports-color@8.1.1) kolorist: 1.8.0 local-pkg: 0.5.0 - magic-string: 0.30.12 + magic-string: 0.30.17 typescript: 5.5.2 vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) transitivePeerDependencies: @@ -47010,7 +46752,7 @@ packages: vite: optional: true dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) globrex: 0.1.2 tsconfck: 2.1.2(typescript@5.5.2) vite: 5.2.14(@types/node@18.16.9)(less@4.2.2)(stylus@0.64.0) @@ -47180,7 +46922,7 @@ packages: acorn-walk: 8.3.4 cac: 6.7.14 chai: 4.5.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 @@ -47237,7 +46979,7 @@ packages: '@vitest/utils': 1.6.0 acorn-walk: 8.3.4 chai: 4.5.0 - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.7(supports-color@9.3.1) execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.11 @@ -47370,22 +47112,6 @@ packages: '@vue/shared': 3.5.10 typescript: 5.5.2 - /vue@3.5.13(typescript@5.5.2): - resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-sfc': 3.5.13 - '@vue/runtime-dom': 3.5.13 - '@vue/server-renderer': 3.5.13(vue@3.5.13) - '@vue/shared': 3.5.13 - typescript: 5.5.2 - dev: true - /w3c-xmlserializer@4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -47889,8 +47615,8 @@ packages: '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) browserslist: 4.24.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 From 11018e15aecf3ae8a82ca103a05f0c1cf6b8cbed Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Fri, 7 Mar 2025 12:25:34 -0800 Subject: [PATCH 20/21] fix(enhanced): use webpack sources from internal webpack obj --- .github/workflows/bundle-size.yml | 81 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml index 63999c7116f..523765b8e1a 100644 --- a/.github/workflows/bundle-size.yml +++ b/.github/workflows/bundle-size.yml @@ -5,7 +5,7 @@ on: branches: [main] jobs: - build: + check-packages: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,52 +33,57 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm-store- - - name: Install dependencies - run: pnpm install - - - name: Build and compare sizes + - name: Check package build sizes uses: preactjs/compressed-size-action@v2 with: - build-script: 'build:pkg && app:next:build' - pattern: '{./packages/**/dist/**/*.{js,mjs,cjs},./apps/**/dist/**/*.{js,mjs,cjs}}' + repo-token: '${{ secrets.GITHUB_TOKEN }}' + build-script: 'build:pkg' + pattern: './packages/**/dist/**/*.{js,mjs,cjs}' exclude: '{**/*.map,**/node_modules/**}' - clean-script: 'rm -rf packages/*/dist apps/*/dist' compression: 'gzip' - save-stats: true - stats-path: 'bundle-stats.json' + comment-key: 'packages' + + check-apps: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - - name: Generate detailed report - uses: NejcZdovc/bundle-size-diff@v1 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - base_path: './bundle-stats.json' - pr_path: './bundle-stats.json' - excluded_assets: ".*\\.map$" + node-version: '18' - - name: Comment PR - uses: actions/github-script@v7 + - name: Install pnpm + uses: pnpm/action-setup@v2 with: - script: | - const stats = require('./bundle-stats.json'); - const comment = `## Bundle Size Report + version: 8.11.0 - ### Package Builds - ${Object.entries(stats.packages || {}) - .map(([name, size]) => `- ${name}: ${size}`) - .join('\n')} + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - ### App Builds - ${Object.entries(stats.apps || {}) - .map(([name, size]) => `- ${name}: ${size}`) - .join('\n')} + - name: Setup pnpm cache + uses: actions/cache@v3 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- - ### Total Changes - - Total Size: ${stats.total} - - Gzipped Size: ${stats.gzipped} - `; + - name: Install dependencies + run: pnpm install - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); + - name: Build packages (prerequisite for building apps) + run: pnpm build:pkg + + - name: Check app build sizes + uses: preactjs/compressed-size-action@v2 + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' + build-script: 'app:next:build' + pattern: './apps/**/dist/**/*.{js,mjs,cjs}' + exclude: '{**/*.map,**/node_modules/**}' + compression: 'gzip' + comment-key: 'apps' + clean-script: 'rm -rf apps/*/dist' From fa3a4f9502de40ef428c93e64d0923a1e651c018 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 10 Mar 2025 19:30:46 -0700 Subject: [PATCH 21/21] Delete .github/workflows/bundle-size.yml --- .github/workflows/bundle-size.yml | 89 ------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 .github/workflows/bundle-size.yml diff --git a/.github/workflows/bundle-size.yml b/.github/workflows/bundle-size.yml deleted file mode 100644 index 523765b8e1a..00000000000 --- a/.github/workflows/bundle-size.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Bundle Size Check - -on: - pull_request: - branches: [main] - -jobs: - check-packages: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8.11.0 - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v3 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Check package build sizes - uses: preactjs/compressed-size-action@v2 - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' - build-script: 'build:pkg' - pattern: './packages/**/dist/**/*.{js,mjs,cjs}' - exclude: '{**/*.map,**/node_modules/**}' - compression: 'gzip' - comment-key: 'packages' - - check-apps: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 8.11.0 - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - name: Setup pnpm cache - uses: actions/cache@v3 - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install - - - name: Build packages (prerequisite for building apps) - run: pnpm build:pkg - - - name: Check app build sizes - uses: preactjs/compressed-size-action@v2 - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' - build-script: 'app:next:build' - pattern: './apps/**/dist/**/*.{js,mjs,cjs}' - exclude: '{**/*.map,**/node_modules/**}' - compression: 'gzip' - comment-key: 'apps' - clean-script: 'rm -rf apps/*/dist'